From 109b832088addd54607de4c188db16cc30b91987 Mon Sep 17 00:00:00 2001 From: andreika-git Date: Wed, 4 Oct 2023 21:51:38 +0300 Subject: [PATCH] Initial firmware commit as of 24/11/2011 --- .gitmodules | 15 + doc/avi-idx.txt | 92 + doc/divx3-discuss.txt | 57 + doc/power.txt | 41 + doc/subtitles.txt | 183 ++ doc/why_our_firmware_is_better.txt | 8 + fonts/Central23.fnt | Bin 0 -> 9701 bytes fonts/Central38.fnt | Bin 0 -> 24233 bytes fonts/Cyr24.fnt | Bin 0 -> 9885 bytes fonts/Cyr27.fnt | Bin 0 -> 13581 bytes fonts/West23.fnt | Bin 0 -> 9080 bytes fonts/West36.fnt | Bin 0 -> 17541 bytes img/adjustment.gif | Bin 0 -> 2927 bytes img/audoff.gif | Bin 0 -> 1511 bytes img/audon.gif | Bin 0 -> 1774 bytes img/cd.gif | Bin 0 -> 1412 bytes img/close.gif | Bin 0 -> 2521 bytes img/defpreview.jpg | Bin 0 -> 2645 bytes img/dvd.gif | Bin 0 -> 1511 bytes img/dvdicon.gif | Bin 0 -> 927 bytes img/foldericon.gif | Bin 0 -> 982 bytes img/fwicon.gif | Bin 0 -> 906 bytes img/lib.jpg | Bin 0 -> 45425 bytes img/logontsc.jpg | Bin 0 -> 71102 bytes img/mediatop.gif | Bin 0 -> 4341 bytes img/movieicon.gif | Bin 0 -> 932 bytes img/movietop.gif | Bin 0 -> 3497 bytes img/musicicon.gif | Bin 0 -> 896 bytes img/musictop.gif | Bin 0 -> 3461 bytes img/mute.gif | Bin 0 -> 1296 bytes img/open.gif | Bin 0 -> 2561 bytes img/pal.act | Bin 0 -> 768 bytes img/photoff.gif | Bin 0 -> 1639 bytes img/photon.gif | Bin 0 -> 1895 bytes img/picsicon.gif | Bin 0 -> 930 bytes img/picstop.gif | Bin 0 -> 3657 bytes img/play-all.gif | Bin 0 -> 1284 bytes img/play-random.gif | Bin 0 -> 1284 bytes img/player/angle.gif | Bin 0 -> 1415 bytes img/player/audio.gif | Bin 0 -> 1437 bytes img/player/cancel.gif | Bin 0 -> 2175 bytes img/player/corrupted.gif | Bin 0 -> 1987 bytes img/player/fwd.gif | Bin 0 -> 1264 bytes img/player/fwd16x.gif | Bin 0 -> 1627 bytes img/player/fwd32x.gif | Bin 0 -> 1646 bytes img/player/fwd48x.gif | Bin 0 -> 1657 bytes img/player/fwd8x.gif | Bin 0 -> 1561 bytes img/player/fwdfast.gif | Bin 0 -> 1348 bytes img/player/invalid.gif | Bin 0 -> 1317 bytes img/player/next.gif | Bin 0 -> 1300 bytes img/player/pause.gif | Bin 0 -> 1139 bytes img/player/play.gif | Bin 0 -> 1142 bytes img/player/popup.gif | Bin 0 -> 2554 bytes img/player/prev.gif | Bin 0 -> 1274 bytes img/player/return.gif | Bin 0 -> 1840 bytes img/player/rev.gif | Bin 0 -> 1252 bytes img/player/rev16x.gif | Bin 0 -> 1636 bytes img/player/rev32x.gif | Bin 0 -> 1661 bytes img/player/rev48x.gif | Bin 0 -> 1667 bytes img/player/rev8x.gif | Bin 0 -> 1561 bytes img/player/revfast.gif | Bin 0 -> 1358 bytes img/player/slowfwd2x.gif | Bin 0 -> 1722 bytes img/player/slowfwd4x.gif | Bin 0 -> 1712 bytes img/player/slowfwd8x.gif | Bin 0 -> 1744 bytes img/player/stepfwd.gif | Bin 0 -> 1300 bytes img/player/stop.gif | Bin 0 -> 1082 bytes img/player/subtitle.gif | Bin 0 -> 1393 bytes img/player/subtitleoff.gif | Bin 0 -> 2204 bytes img/playlist-edit-off.gif | Bin 0 -> 990 bytes img/playlist-edit-on.gif | Bin 0 -> 3872 bytes img/playlist-modes.gif | Bin 0 -> 1193 bytes img/playlistoff.gif | Bin 0 -> 1745 bytes img/playliston.gif | Bin 0 -> 1957 bytes img/playlisttop.gif | Bin 0 -> 4004 bytes img/playoff.gif | Bin 0 -> 1578 bytes img/playon.gif | Bin 0 -> 1616 bytes img/returnoff.gif | Bin 0 -> 2108 bytes img/returnon.gif | Bin 0 -> 2060 bytes img/screensaver.gif | Bin 0 -> 7833 bytes img/scroll.gif | Bin 0 -> 838 bytes img/scrollon.gif | Bin 0 -> 2127 bytes img/selmovieicon.gif | Bin 0 -> 1037 bytes img/selmusicicon.gif | Bin 0 -> 1014 bytes img/selpicsicon.gif | Bin 0 -> 1053 bytes img/selsubticon.gif | Bin 0 -> 1080 bytes img/setup1.jpg | Bin 0 -> 72317 bytes img/setup2.jpg | Bin 0 -> 38925 bytes img/ssaver.act | Bin 0 -> 772 bytes img/subticon.gif | Bin 0 -> 917 bytes img/subtoff.gif | Bin 0 -> 1612 bytes img/subton.gif | Bin 0 -> 1809 bytes img/upfoldericon.gif | Bin 0 -> 902 bytes img/vidoff.gif | Bin 0 -> 1748 bytes img/vidon.gif | Bin 0 -> 1950 bytes img/vmodes/1080iyuv.gif | Bin 0 -> 2212 bytes img/vmodes/480pyuv.gif | Bin 0 -> 2038 bytes img/vmodes/576pyuv.gif | Bin 0 -> 2167 bytes img/vmodes/720pyuv.gif | Bin 0 -> 1993 bytes img/vmodes/ntsccompyc.gif | Bin 0 -> 2462 bytes img/vmodes/ntsccompyuv.gif | Bin 0 -> 2669 bytes img/vmodes/ntscscart.gif | Bin 0 -> 2128 bytes img/vmodes/palcompyc.gif | Bin 0 -> 2349 bytes img/vmodes/palcompyuv.gif | Bin 0 -> 2517 bytes img/vmodes/palscart.gif | Bin 0 -> 1942 bytes img/volbkgrnd.gif | Bin 0 -> 1644 bytes img/volpoint.gif | Bin 0 -> 838 bytes img/wait.gif | Bin 0 -> 2128 bytes img/yes.gif | Bin 0 -> 1070 bytes img/zoomoff.gif | Bin 0 -> 2065 bytes img/zoomon.gif | Bin 0 -> 2048 bytes mmsl/adjust.mmsl | 341 +++ mmsl/doc/examples.txt | 159 ++ mmsl/doc/mmsl-about.txt | 6 + mmsl/doc/mmsl-lang.txt | 332 +++ mmsl/doc/mmsl-misc.txt | 51 + mmsl/doc/mmsl-objects1.txt | 849 ++++++ mmsl/doc/mmsl-objects2.txt | 161 ++ mmsl/doc/mmsl-tricks.txt | 27 + mmsl/dvd.mmsl | 254 ++ mmsl/flash.mmsl | 59 + mmsl/iso-info.mmsl | 206 ++ mmsl/iso-menu.mmsl | 383 +++ mmsl/iso-photo.mmsl | 84 + mmsl/iso-play.mmsl | 397 +++ mmsl/iso.mmsl | 111 + mmsl/list.mmsl | 437 +++ mmsl/osd.mmsl | 379 +++ mmsl/popup.mmsl | 43 + mmsl/search.mmsl | 367 +++ mmsl/setup.mmsl | 688 +++++ mmsl/ssaver.mmsl | 145 + mmsl/startup.mmsl | 284 ++ mmsl/startup.mmso | Bin 0 -> 81047 bytes mmsl/zoom.mmsl | 73 + src/COPYING | 340 +++ src/Makefile | 138 + src/Makefile.inc | 330 +++ src/audio.cpp | 1759 ++++++++++++ src/audio.h | 264 ++ src/avi.cpp | 1003 +++++++ src/avi.h | 178 ++ src/bitstream.cpp | 180 ++ src/bitstream.h | 217 ++ src/cdda.cpp | 553 ++++ src/cdda.h | 131 + src/contrib/libdvdcss | 1 + src/contrib/libdvdnav | 1 + src/contrib/libid3tag | 1 + src/contrib/libjpeg | 1 + src/contrib/libmad | 1 + src/contrib/longjmp.S | 44 + src/contrib/memcpy.S | 318 +++ src/contrib/memset.S | 80 + src/contrib/setjmp.S | 47 + src/contrib/strcmp.S | 49 + src/contrib/strlen.S | 80 + src/divx-tables.h | 2072 ++++++++++++++ src/divx.c | 2612 +++++++++++++++++ src/divx.h | 122 + src/dvd.h | 115 + src/dvd/Makefile | 89 + src/dvd/dvd-internal.h | 83 + src/dvd/dvd-langs.inc.cpp | 170 ++ src/dvd/dvd.cpp | 3157 +++++++++++++++++++++ src/dvd/dvd_css.c | 528 ++++ src/dvd/dvd_misc.c | 696 +++++ src/dvd/dvd_misc.h | 76 + src/dvd/dvd_player.cpp | 139 + src/dvd/main.cpp | 47 + src/dvd/module-dvd.cpp | 1 + src/dvd/module-init.cpp | 1 + src/dvd/module.cpp | 1 + src/gui/console.cpp | 238 ++ src/gui/console.h | 81 + src/gui/console_font.inc.cpp | 800 ++++++ src/gui/font.cpp | 467 +++ src/gui/font.h | 85 + src/gui/giflib.cpp | 881 ++++++ src/gui/giflib.h | 245 ++ src/gui/image.cpp | 304 ++ src/gui/image.h | 111 + src/gui/jpeg.cpp | 819 ++++++ src/gui/jpeg.h | 47 + src/gui/rect.cpp | 417 +++ src/gui/rect.h | 52 + src/gui/res.cpp | 58 + src/gui/res.h | 93 + src/gui/text.cpp | 305 ++ src/gui/text.h | 92 + src/gui/window.cpp | 733 +++++ src/gui/window.h | 248 ++ src/info/player_info.cpp | 290 ++ src/info/player_info.h | 39 + src/info/player_info.make | 52 + src/init | Bin 0 -> 637364 bytes src/init.cpp | 227 ++ src/libsp/KISS/todo | 1 + src/libsp/MG35/arch-jasper/hardware.h | 798 ++++++ src/libsp/MG35/sp_cdrom.cpp | 651 +++++ src/libsp/MG35/sp_eeprom.cpp | 60 + src/libsp/MG35/sp_fip.cpp | 261 ++ src/libsp/MG35/sp_fip_codes-dreamx108.h | 119 + src/libsp/MG35/sp_fip_codes-technosonic.h | 119 + src/libsp/MG35/sp_fip_ioctl.h | 58 + src/libsp/MG35/sp_flash.cpp | 174 ++ src/libsp/MG35/sp_i2c.cpp | 93 + src/libsp/MG35/sp_khwl.cpp | 351 +++ src/libsp/MG35/sp_khwl_ioctl.h | 65 + src/libsp/MG35/sp_module.cpp | 198 ++ src/libsp/MG35/sp_module_crt0.S | 125 + src/libsp/MP/arch-jasper/hardware.h | 798 ++++++ src/libsp/MP/sp_cdrom.cpp | 670 +++++ src/libsp/MP/sp_eeprom.cpp | 60 + src/libsp/MP/sp_fip.cpp | 261 ++ src/libsp/MP/sp_fip_codes-dreamx108.h | 119 + src/libsp/MP/sp_fip_codes-mecotek.h | 119 + src/libsp/MP/sp_fip_codes-technosonic.h | 119 + src/libsp/MP/sp_fip_ioctl.h | 63 + src/libsp/MP/sp_i2c.cpp | 93 + src/libsp/MP/sp_khwl.cpp | 360 +++ src/libsp/MP/sp_khwl_ioctl.h | 65 + src/libsp/MP/sp_module.cpp | 198 ++ src/libsp/MP/sp_module_crt0.S | 125 + src/libsp/containers/clist.h | 494 ++++ src/libsp/containers/dllist.h | 695 +++++ src/libsp/containers/hashlist.h | 336 +++ src/libsp/containers/list.h | 459 +++ src/libsp/containers/membin.cpp | 322 +++ src/libsp/containers/membin.h | 45 + src/libsp/containers/salist.h | 259 ++ src/libsp/containers/string.cpp | 290 ++ src/libsp/containers/string.h | 816 ++++++ src/libsp/sp_bswap.h | 48 + src/libsp/sp_cdrom.h | 100 + src/libsp/sp_eeprom.h | 48 + src/libsp/sp_fip.h | 170 ++ src/libsp/sp_flash.cpp | 276 ++ src/libsp/sp_flash.h | 51 + src/libsp/sp_i2c.h | 42 + src/libsp/sp_io.h | 55 + src/libsp/sp_khwl.h | 190 ++ src/libsp/sp_khwl_colors.cpp | 278 ++ src/libsp/sp_khwl_prop.h | 393 +++ src/libsp/sp_memory.h | 267 ++ src/libsp/sp_misc.cpp | 95 + src/libsp/sp_misc.h | 200 ++ src/libsp/sp_module.h | 60 + src/libsp/sp_mpeg.cpp | 2396 ++++++++++++++++ src/libsp/sp_mpeg.h | 456 +++ src/libsp/sp_msg.cpp | 177 ++ src/libsp/sp_msg.h | 80 + src/libsp/sp_video.cpp | 753 +++++ src/libsp/sp_video.h | 96 + src/libsp/win32/dirent.c | 57 + src/libsp/win32/fip.bmp | Bin 0 -> 46344 bytes src/libsp/win32/include/alloca.h | 1 + src/libsp/win32/include/byteswap.h | 105 + src/libsp/win32/include/dirent.h | 59 + src/libsp/win32/include/endian.h | 4 + src/libsp/win32/include/inttypes.h | 43 + src/libsp/win32/include/linux/cdrom.h | 178 ++ src/libsp/win32/include/linux/mtd/mtd.h | 41 + src/libsp/win32/include/mntent.h | 0 src/libsp/win32/include/pty.h | 0 src/libsp/win32/include/pwd.h | 0 src/libsp/win32/include/sched.h | 0 src/libsp/win32/include/stdint.h | 2 + src/libsp/win32/include/strings.h | 3 + src/libsp/win32/include/sys/io.h | 0 src/libsp/win32/include/sys/ioctl.h | 0 src/libsp/win32/include/sys/mman.h | 0 src/libsp/win32/include/sys/mount.h | 0 src/libsp/win32/include/sys/param.h | 0 src/libsp/win32/include/sys/poll.h | 0 src/libsp/win32/include/sys/socket.h | 0 src/libsp/win32/include/sys/sysinfo.h | 0 src/libsp/win32/include/sys/time.h | 2 + src/libsp/win32/include/sys/uio.h | 0 src/libsp/win32/include/sys/un.h | 0 src/libsp/win32/include/sys/wait.h | 0 src/libsp/win32/include/termios.h | 0 src/libsp/win32/include/unistd.h | 5 + src/libsp/win32/include/win32-stuff.h | 424 +++ src/libsp/win32/libsp.aps | Bin 0 -> 81832 bytes src/libsp/win32/libsp.rc | 161 ++ src/libsp/win32/manifest.xml | 23 + src/libsp/win32/resource.h | 21 + src/libsp/win32/resrc1.h | 15 + src/libsp/win32/sp_cdrom.cpp | 536 ++++ src/libsp/win32/sp_css.cpp | 229 ++ src/libsp/win32/sp_css.h | 32 + src/libsp/win32/sp_eeprom.cpp | 61 + src/libsp/win32/sp_fip.cpp | 167 ++ src/libsp/win32/sp_i2c.cpp | 39 + src/libsp/win32/sp_khwl.cpp | 1350 +++++++++ src/libsp/win32/sp_memory.cpp | 995 +++++++ src/libsp/win32/sp_module.cpp | 100 + src/libsp/win32/win32-stuff.c | 247 ++ src/media.cpp | 647 +++++ src/media.h | 87 + src/mmsl/mmsl-file.cpp | 149 + src/mmsl/mmsl-file.h | 91 + src/mmsl/mmsl-parser.cpp | 1320 +++++++++ src/mmsl/mmsl-parser.h | 264 ++ src/mmsl/mmsl.cpp | 1105 ++++++++ src/mmsl/mmsl.h | 537 ++++ src/module-dvd.cpp | 130 + src/module-init.cpp | 377 +++ src/module.cpp | 292 ++ src/module.h | 76 + src/mpg.cpp | 739 +++++ src/mpg.h | 90 + src/player.cpp | 931 ++++++ src/player.h | 67 + src/script-dummyobjs.inc.cpp | 33 + src/script-dummyvars.inc.cpp | 34 + src/script-explorer.cpp | 1190 ++++++++ src/script-internal.h | 510 ++++ src/script-objects.cpp | 534 ++++ src/script-objs.h | 84 + src/script-objs.inc.c | 74 + src/script-player.cpp | 1375 +++++++++ src/script-pobjs.inc.c | 76 + src/script-pvars.inc.c | 168 ++ src/script-vars.h | 187 ++ src/script-vars.inc.c | 167 ++ src/script.cpp | 1998 +++++++++++++ src/script.h | 147 + src/settings.cpp | 231 ++ src/settings.h | 143 + src/subtitle.cpp | 1416 +++++++++ src/subtitle.h | 280 ++ src/version-DX.h | 5 + src/version-MP.h | 4 + src/version-MT.h | 4 + src/version-WIN.h | 4 + src/version.h | 22 + src/video.cpp | 2125 ++++++++++++++ src/video.h | 260 ++ win32/dvd.dsp | 123 + win32/dvd.vcproj | 284 ++ win32/dvd_mod.dsp | 186 ++ win32/dvd_mod.vcproj | 458 +++ win32/dvdcss.dsp | 151 + win32/dvdcss.vcproj | 324 +++ win32/dvdnav.dsp | 248 ++ win32/dvdnav.vcproj | 664 +++++ win32/gui.dsp | 173 ++ win32/gui.vcproj | 442 +++ win32/init.dsp | 254 ++ win32/init.dsw | 167 ++ win32/init.sln | 90 + win32/init.vcproj | 726 +++++ win32/libid3tag.dsp | 266 ++ win32/libid3tag.vcproj | 697 +++++ win32/libjpeg.dsp | 244 ++ win32/libjpeg.vcproj | 782 +++++ win32/libmad.dsp | 213 ++ win32/libmad.vcproj | 501 ++++ win32/libsp.dsp | 256 ++ win32/libsp.vcproj | 636 +++++ 361 files changed, 75369 insertions(+) create mode 100644 .gitmodules create mode 100644 doc/avi-idx.txt create mode 100644 doc/divx3-discuss.txt create mode 100644 doc/power.txt create mode 100644 doc/subtitles.txt create mode 100644 doc/why_our_firmware_is_better.txt create mode 100644 fonts/Central23.fnt create mode 100644 fonts/Central38.fnt create mode 100644 fonts/Cyr24.fnt create mode 100644 fonts/Cyr27.fnt create mode 100644 fonts/West23.fnt create mode 100644 fonts/West36.fnt create mode 100644 img/adjustment.gif create mode 100644 img/audoff.gif create mode 100644 img/audon.gif create mode 100644 img/cd.gif create mode 100644 img/close.gif create mode 100644 img/defpreview.jpg create mode 100644 img/dvd.gif create mode 100644 img/dvdicon.gif create mode 100644 img/foldericon.gif create mode 100644 img/fwicon.gif create mode 100644 img/lib.jpg create mode 100644 img/logontsc.jpg create mode 100644 img/mediatop.gif create mode 100644 img/movieicon.gif create mode 100644 img/movietop.gif create mode 100644 img/musicicon.gif create mode 100644 img/musictop.gif create mode 100644 img/mute.gif create mode 100644 img/open.gif create mode 100644 img/pal.act create mode 100644 img/photoff.gif create mode 100644 img/photon.gif create mode 100644 img/picsicon.gif create mode 100644 img/picstop.gif create mode 100644 img/play-all.gif create mode 100644 img/play-random.gif create mode 100644 img/player/angle.gif create mode 100644 img/player/audio.gif create mode 100644 img/player/cancel.gif create mode 100644 img/player/corrupted.gif create mode 100644 img/player/fwd.gif create mode 100644 img/player/fwd16x.gif create mode 100644 img/player/fwd32x.gif create mode 100644 img/player/fwd48x.gif create mode 100644 img/player/fwd8x.gif create mode 100644 img/player/fwdfast.gif create mode 100644 img/player/invalid.gif create mode 100644 img/player/next.gif create mode 100644 img/player/pause.gif create mode 100644 img/player/play.gif create mode 100644 img/player/popup.gif create mode 100644 img/player/prev.gif create mode 100644 img/player/return.gif create mode 100644 img/player/rev.gif create mode 100644 img/player/rev16x.gif create mode 100644 img/player/rev32x.gif create mode 100644 img/player/rev48x.gif create mode 100644 img/player/rev8x.gif create mode 100644 img/player/revfast.gif create mode 100644 img/player/slowfwd2x.gif create mode 100644 img/player/slowfwd4x.gif create mode 100644 img/player/slowfwd8x.gif create mode 100644 img/player/stepfwd.gif create mode 100644 img/player/stop.gif create mode 100644 img/player/subtitle.gif create mode 100644 img/player/subtitleoff.gif create mode 100644 img/playlist-edit-off.gif create mode 100644 img/playlist-edit-on.gif create mode 100644 img/playlist-modes.gif create mode 100644 img/playlistoff.gif create mode 100644 img/playliston.gif create mode 100644 img/playlisttop.gif create mode 100644 img/playoff.gif create mode 100644 img/playon.gif create mode 100644 img/returnoff.gif create mode 100644 img/returnon.gif create mode 100644 img/screensaver.gif create mode 100644 img/scroll.gif create mode 100644 img/scrollon.gif create mode 100644 img/selmovieicon.gif create mode 100644 img/selmusicicon.gif create mode 100644 img/selpicsicon.gif create mode 100644 img/selsubticon.gif create mode 100644 img/setup1.jpg create mode 100644 img/setup2.jpg create mode 100644 img/ssaver.act create mode 100644 img/subticon.gif create mode 100644 img/subtoff.gif create mode 100644 img/subton.gif create mode 100644 img/upfoldericon.gif create mode 100644 img/vidoff.gif create mode 100644 img/vidon.gif create mode 100644 img/vmodes/1080iyuv.gif create mode 100644 img/vmodes/480pyuv.gif create mode 100644 img/vmodes/576pyuv.gif create mode 100644 img/vmodes/720pyuv.gif create mode 100644 img/vmodes/ntsccompyc.gif create mode 100644 img/vmodes/ntsccompyuv.gif create mode 100644 img/vmodes/ntscscart.gif create mode 100644 img/vmodes/palcompyc.gif create mode 100644 img/vmodes/palcompyuv.gif create mode 100644 img/vmodes/palscart.gif create mode 100644 img/volbkgrnd.gif create mode 100644 img/volpoint.gif create mode 100644 img/wait.gif create mode 100644 img/yes.gif create mode 100644 img/zoomoff.gif create mode 100644 img/zoomon.gif create mode 100644 mmsl/adjust.mmsl create mode 100644 mmsl/doc/examples.txt create mode 100644 mmsl/doc/mmsl-about.txt create mode 100644 mmsl/doc/mmsl-lang.txt create mode 100644 mmsl/doc/mmsl-misc.txt create mode 100644 mmsl/doc/mmsl-objects1.txt create mode 100644 mmsl/doc/mmsl-objects2.txt create mode 100644 mmsl/doc/mmsl-tricks.txt create mode 100644 mmsl/dvd.mmsl create mode 100644 mmsl/flash.mmsl create mode 100644 mmsl/iso-info.mmsl create mode 100644 mmsl/iso-menu.mmsl create mode 100644 mmsl/iso-photo.mmsl create mode 100644 mmsl/iso-play.mmsl create mode 100644 mmsl/iso.mmsl create mode 100644 mmsl/list.mmsl create mode 100644 mmsl/osd.mmsl create mode 100644 mmsl/popup.mmsl create mode 100644 mmsl/search.mmsl create mode 100644 mmsl/setup.mmsl create mode 100644 mmsl/ssaver.mmsl create mode 100644 mmsl/startup.mmsl create mode 100644 mmsl/startup.mmso create mode 100644 mmsl/zoom.mmsl create mode 100644 src/COPYING create mode 100644 src/Makefile create mode 100644 src/Makefile.inc create mode 100644 src/audio.cpp create mode 100644 src/audio.h create mode 100644 src/avi.cpp create mode 100644 src/avi.h create mode 100644 src/bitstream.cpp create mode 100644 src/bitstream.h create mode 100644 src/cdda.cpp create mode 100644 src/cdda.h create mode 160000 src/contrib/libdvdcss create mode 160000 src/contrib/libdvdnav create mode 160000 src/contrib/libid3tag create mode 160000 src/contrib/libjpeg create mode 160000 src/contrib/libmad create mode 100644 src/contrib/longjmp.S create mode 100644 src/contrib/memcpy.S create mode 100644 src/contrib/memset.S create mode 100644 src/contrib/setjmp.S create mode 100644 src/contrib/strcmp.S create mode 100644 src/contrib/strlen.S create mode 100644 src/divx-tables.h create mode 100644 src/divx.c create mode 100644 src/divx.h create mode 100644 src/dvd.h create mode 100644 src/dvd/Makefile create mode 100644 src/dvd/dvd-internal.h create mode 100644 src/dvd/dvd-langs.inc.cpp create mode 100644 src/dvd/dvd.cpp create mode 100644 src/dvd/dvd_css.c create mode 100644 src/dvd/dvd_misc.c create mode 100644 src/dvd/dvd_misc.h create mode 100644 src/dvd/dvd_player.cpp create mode 100644 src/dvd/main.cpp create mode 100644 src/dvd/module-dvd.cpp create mode 100644 src/dvd/module-init.cpp create mode 100644 src/dvd/module.cpp create mode 100644 src/gui/console.cpp create mode 100644 src/gui/console.h create mode 100644 src/gui/console_font.inc.cpp create mode 100644 src/gui/font.cpp create mode 100644 src/gui/font.h create mode 100644 src/gui/giflib.cpp create mode 100644 src/gui/giflib.h create mode 100644 src/gui/image.cpp create mode 100644 src/gui/image.h create mode 100644 src/gui/jpeg.cpp create mode 100644 src/gui/jpeg.h create mode 100644 src/gui/rect.cpp create mode 100644 src/gui/rect.h create mode 100644 src/gui/res.cpp create mode 100644 src/gui/res.h create mode 100644 src/gui/text.cpp create mode 100644 src/gui/text.h create mode 100644 src/gui/window.cpp create mode 100644 src/gui/window.h create mode 100644 src/info/player_info.cpp create mode 100644 src/info/player_info.h create mode 100644 src/info/player_info.make create mode 100644 src/init create mode 100644 src/init.cpp create mode 100644 src/libsp/KISS/todo create mode 100644 src/libsp/MG35/arch-jasper/hardware.h create mode 100644 src/libsp/MG35/sp_cdrom.cpp create mode 100644 src/libsp/MG35/sp_eeprom.cpp create mode 100644 src/libsp/MG35/sp_fip.cpp create mode 100644 src/libsp/MG35/sp_fip_codes-dreamx108.h create mode 100644 src/libsp/MG35/sp_fip_codes-technosonic.h create mode 100644 src/libsp/MG35/sp_fip_ioctl.h create mode 100644 src/libsp/MG35/sp_flash.cpp create mode 100644 src/libsp/MG35/sp_i2c.cpp create mode 100644 src/libsp/MG35/sp_khwl.cpp create mode 100644 src/libsp/MG35/sp_khwl_ioctl.h create mode 100644 src/libsp/MG35/sp_module.cpp create mode 100644 src/libsp/MG35/sp_module_crt0.S create mode 100644 src/libsp/MP/arch-jasper/hardware.h create mode 100644 src/libsp/MP/sp_cdrom.cpp create mode 100644 src/libsp/MP/sp_eeprom.cpp create mode 100644 src/libsp/MP/sp_fip.cpp create mode 100644 src/libsp/MP/sp_fip_codes-dreamx108.h create mode 100644 src/libsp/MP/sp_fip_codes-mecotek.h create mode 100644 src/libsp/MP/sp_fip_codes-technosonic.h create mode 100644 src/libsp/MP/sp_fip_ioctl.h create mode 100644 src/libsp/MP/sp_i2c.cpp create mode 100644 src/libsp/MP/sp_khwl.cpp create mode 100644 src/libsp/MP/sp_khwl_ioctl.h create mode 100644 src/libsp/MP/sp_module.cpp create mode 100644 src/libsp/MP/sp_module_crt0.S create mode 100644 src/libsp/containers/clist.h create mode 100644 src/libsp/containers/dllist.h create mode 100644 src/libsp/containers/hashlist.h create mode 100644 src/libsp/containers/list.h create mode 100644 src/libsp/containers/membin.cpp create mode 100644 src/libsp/containers/membin.h create mode 100644 src/libsp/containers/salist.h create mode 100644 src/libsp/containers/string.cpp create mode 100644 src/libsp/containers/string.h create mode 100644 src/libsp/sp_bswap.h create mode 100644 src/libsp/sp_cdrom.h create mode 100644 src/libsp/sp_eeprom.h create mode 100644 src/libsp/sp_fip.h create mode 100644 src/libsp/sp_flash.cpp create mode 100644 src/libsp/sp_flash.h create mode 100644 src/libsp/sp_i2c.h create mode 100644 src/libsp/sp_io.h create mode 100644 src/libsp/sp_khwl.h create mode 100644 src/libsp/sp_khwl_colors.cpp create mode 100644 src/libsp/sp_khwl_prop.h create mode 100644 src/libsp/sp_memory.h create mode 100644 src/libsp/sp_misc.cpp create mode 100644 src/libsp/sp_misc.h create mode 100644 src/libsp/sp_module.h create mode 100644 src/libsp/sp_mpeg.cpp create mode 100644 src/libsp/sp_mpeg.h create mode 100644 src/libsp/sp_msg.cpp create mode 100644 src/libsp/sp_msg.h create mode 100644 src/libsp/sp_video.cpp create mode 100644 src/libsp/sp_video.h create mode 100644 src/libsp/win32/dirent.c create mode 100644 src/libsp/win32/fip.bmp create mode 100644 src/libsp/win32/include/alloca.h create mode 100644 src/libsp/win32/include/byteswap.h create mode 100644 src/libsp/win32/include/dirent.h create mode 100644 src/libsp/win32/include/endian.h create mode 100644 src/libsp/win32/include/inttypes.h create mode 100644 src/libsp/win32/include/linux/cdrom.h create mode 100644 src/libsp/win32/include/linux/mtd/mtd.h create mode 100644 src/libsp/win32/include/mntent.h create mode 100644 src/libsp/win32/include/pty.h create mode 100644 src/libsp/win32/include/pwd.h create mode 100644 src/libsp/win32/include/sched.h create mode 100644 src/libsp/win32/include/stdint.h create mode 100644 src/libsp/win32/include/strings.h create mode 100644 src/libsp/win32/include/sys/io.h create mode 100644 src/libsp/win32/include/sys/ioctl.h create mode 100644 src/libsp/win32/include/sys/mman.h create mode 100644 src/libsp/win32/include/sys/mount.h create mode 100644 src/libsp/win32/include/sys/param.h create mode 100644 src/libsp/win32/include/sys/poll.h create mode 100644 src/libsp/win32/include/sys/socket.h create mode 100644 src/libsp/win32/include/sys/sysinfo.h create mode 100644 src/libsp/win32/include/sys/time.h create mode 100644 src/libsp/win32/include/sys/uio.h create mode 100644 src/libsp/win32/include/sys/un.h create mode 100644 src/libsp/win32/include/sys/wait.h create mode 100644 src/libsp/win32/include/termios.h create mode 100644 src/libsp/win32/include/unistd.h create mode 100644 src/libsp/win32/include/win32-stuff.h create mode 100644 src/libsp/win32/libsp.aps create mode 100644 src/libsp/win32/libsp.rc create mode 100644 src/libsp/win32/manifest.xml create mode 100644 src/libsp/win32/resource.h create mode 100644 src/libsp/win32/resrc1.h create mode 100644 src/libsp/win32/sp_cdrom.cpp create mode 100644 src/libsp/win32/sp_css.cpp create mode 100644 src/libsp/win32/sp_css.h create mode 100644 src/libsp/win32/sp_eeprom.cpp create mode 100644 src/libsp/win32/sp_fip.cpp create mode 100644 src/libsp/win32/sp_i2c.cpp create mode 100644 src/libsp/win32/sp_khwl.cpp create mode 100644 src/libsp/win32/sp_memory.cpp create mode 100644 src/libsp/win32/sp_module.cpp create mode 100644 src/libsp/win32/win32-stuff.c create mode 100644 src/media.cpp create mode 100644 src/media.h create mode 100644 src/mmsl/mmsl-file.cpp create mode 100644 src/mmsl/mmsl-file.h create mode 100644 src/mmsl/mmsl-parser.cpp create mode 100644 src/mmsl/mmsl-parser.h create mode 100644 src/mmsl/mmsl.cpp create mode 100644 src/mmsl/mmsl.h create mode 100644 src/module-dvd.cpp create mode 100644 src/module-init.cpp create mode 100644 src/module.cpp create mode 100644 src/module.h create mode 100644 src/mpg.cpp create mode 100644 src/mpg.h create mode 100644 src/player.cpp create mode 100644 src/player.h create mode 100644 src/script-dummyobjs.inc.cpp create mode 100644 src/script-dummyvars.inc.cpp create mode 100644 src/script-explorer.cpp create mode 100644 src/script-internal.h create mode 100644 src/script-objects.cpp create mode 100644 src/script-objs.h create mode 100644 src/script-objs.inc.c create mode 100644 src/script-player.cpp create mode 100644 src/script-pobjs.inc.c create mode 100644 src/script-pvars.inc.c create mode 100644 src/script-vars.h create mode 100644 src/script-vars.inc.c create mode 100644 src/script.cpp create mode 100644 src/script.h create mode 100644 src/settings.cpp create mode 100644 src/settings.h create mode 100644 src/subtitle.cpp create mode 100644 src/subtitle.h create mode 100644 src/version-DX.h create mode 100644 src/version-MP.h create mode 100644 src/version-MT.h create mode 100644 src/version-WIN.h create mode 100644 src/version.h create mode 100644 src/video.cpp create mode 100644 src/video.h create mode 100644 win32/dvd.dsp create mode 100644 win32/dvd.vcproj create mode 100644 win32/dvd_mod.dsp create mode 100644 win32/dvd_mod.vcproj create mode 100644 win32/dvdcss.dsp create mode 100644 win32/dvdcss.vcproj create mode 100644 win32/dvdnav.dsp create mode 100644 win32/dvdnav.vcproj create mode 100644 win32/gui.dsp create mode 100644 win32/gui.vcproj create mode 100644 win32/init.dsp create mode 100644 win32/init.dsw create mode 100644 win32/init.sln create mode 100644 win32/init.vcproj create mode 100644 win32/libid3tag.dsp create mode 100644 win32/libid3tag.vcproj create mode 100644 win32/libjpeg.dsp create mode 100644 win32/libjpeg.vcproj create mode 100644 win32/libmad.dsp create mode 100644 win32/libmad.vcproj create mode 100644 win32/libsp.dsp create mode 100644 win32/libsp.vcproj diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..e502ef6 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,15 @@ +[submodule "src/contrib/libdvdnav"] + path = src/contrib/libdvdnav + url = https://github.com/xbmc/libdvdnav +[submodule "src/contrib/libdvdcss"] + path = src/contrib/libdvdcss + url = https://github.com/xbmc/libdvdcss +[submodule "src/contrib/libid3tag"] + path = src/contrib/libid3tag + url = https://github.com/tenacityteam/libid3tag +[submodule "src/contrib/libjpeg"] + path = src/contrib/libjpeg + url = https://github.com/thorfdbg/libjpeg +[submodule "src/contrib/libmad"] + path = src/contrib/libmad + url = https://github.com/markjeee/libmad diff --git a/doc/avi-idx.txt b/doc/avi-idx.txt new file mode 100644 index 0000000..ecf633d --- /dev/null +++ b/doc/avi-idx.txt @@ -0,0 +1,92 @@ +Индексы в АВИ. + +ВНИМАНИЕ!!!!!!!!!!!! ИНДЕКС - БЕЗ ДЫРОК!!!!!!!!! + +Варианты: +1) у файла нет индексов +2) есть только расширенный индекс (indx) +3) есть обычный индекс: + а) + - оффсеты относительные (с начала movi) + - оффсеты абсолютные (с начала файла) + б) + - кейфреймам можно доверять + - кейфреймам нельзя доверять + +Из этого всего нам нужно строить наш внутренний индекс. + +--------------------------------- +индекс нужен: +1) для перемотки вперед +2) для перемотки назад +3) для поиска + +ВСЕ ключевые кадры, на которых мы были (при проигрывании), попадают в индекс! + +------------------------------ +А МОЖЕТ, НАМ НУЖЕН ЕДИНЫЙ СВЯЗАННЫЙ СПИСОК КЛЮЧЕВЫХ КАДРОВ??! +- плюсы: + - легко вставлять кадры произвольно, не надо аллокейтить лишнее место + - можно использовать единую кучу-хранилище кадров, и делать ссылки на неё в связанном списке + - список только растёт, не надо ничего удалять. +- минусы: + - нужна доп. инфа о том, является ли след./пред. кадр действительно следующим, или между ними + могут быть ещё кадры + - для поиска нужно обойти в цикле весь список += ДА! +---------------------------------- + +ИНДЕКС = связанный список КЛЮЧЕВЫХ КАДРОВ + +КЛЮЧЕВОЙ КАДР: + - абс. номер кадра, int + * по номеру определяем время кадра (pts) + * находим данные в индексах AVI + - смещение к этому кадру относительно начала файла, int64 + - индексы к next,prev, int + * старший бит = 1 --> между ними больше нет кадров + +--------------------------- +ЗАДАЧИ: +1) добавлять в индекс текущий (проигрываемый) ключевой кадр +2) искать и добавлять следующий(-ие)/предыдущий(-ие) КК +3) искать и добавлять КК по временной метке + +ЗАДАЧА 1. +- храним указатель на последний проигрываемый кадр +- ищем след. кадр в индексе, больший по номеру, чем текущий. +- если текущего в индексе нет, вставляем его перед следущим. +- присваиваем: последний = текущий. + +ЗАДАЧА 2. +1) Если AVI-индекса нет, то: + - предыдущий кадр искать не надо - он уже должен быть в списке + (мы не могли прыгнуть, не заполнив весь индекс от начала до текущего момента) + - для след.кадра - идём по файлу вперёд, кадр за кадром, ищем ключевой + - добавляем его в индекс, если его ещё нет (см. флаг в next/prev). +2) Если есть AVI-индекс, то: + - прыгаем к данным индекса по номеру текущего кадра + - читаем блок данных для последующих/предыдущих индексов + - находим в блоке все ключевые кадры + - добавляем их в наш индекс, если их там ещё нет (см. флаги в next/prev). + +ЗАДАЧА 3. +- вначале пробегаем индекс и ищем 2 КК, между которыми должна находится временная метка. + если между ними больше нет кадров, возвращаем 1-й. +- иначе: +1) Если AVI-индекса нет, то: + - в индексе ищем ближайший к временной метке КК (перед). + - сканируем, начиная с него, вперёд все кадры, и ищем КК _после_ временной метки. + - добавляем всех их в индекс + - возвращаем предпоследний КК. +2) Если есть AVI-индекс, то: + - в индексе ищем ближайший к временной метке КК (перед). + - прыгаем к данным индекса по этому номеру КК + - загружаем весь индекс, начиная с него блоками + - парсим блоки и добавляем все КК, пока не встретим номер кадра, больший чем временная метка + + + + + + diff --git a/doc/divx3-discuss.txt b/doc/divx3-discuss.txt new file mode 100644 index 0000000..4c3aa94 --- /dev/null +++ b/doc/divx3-discuss.txt @@ -0,0 +1,57 @@ +Задача: транскодировать поток дивх3 в мпег4. +Поток состоит из отдельных кадров, каждый из которых транскодируется независимо. Задача сводится к транскодированию кадра. + +Чтобы это сделать, надо дать ответы на 2 вопроса: +1) Что именно хранится в кадре +2) Как именно хранится. +Ответ на 1-й вопрос _почти_ одинаков для дивх3 и мпег4. +Ответ на 2-й вопрос - существенные отличия. + +Хранятся данные в виде последовательности битов. Суть процесса - прочитать биты кадра дивх3, понять, что именно там хранится, и записать это в виде битов в формате мпег4 + +Кадр делится на макроблоки размером 16 x 16 пикселей. +Кадр хранит: +- заголовок (хранится тип кадра, индексы в таблицы с данными, необходимые для работы и т.п.) +- данные всех макроблоков. + +Т.е. фильм разрешения 640x480 содержит 40*30=1200 макроблоков в каждом кадре. + +Каждый макроблок (в дальнейшем - МБ) хранит: +- заголовок МБ +- 6 каналов данных (4 на яркость и 2 на цветность) + +МБ может быть двух видов: +- интра (независим от других кадров) +- интер (строится на основе предыдущих кадров) +Кадры I-frame состоят только из интра-макроблоков, а кадры P-frame могут иметь как те, так и другие. + +Данные каждого канала МБ зависят от типа МБ: +- для интра-МБ хранится DC-уровень (что-то типа "усреднённого цвета" блока) и AC-коэффициенты (отклонения от этого уровня) +- для интер-МБ хранится вектор движения (т.е. куда мы должны передвинуть старый МБ, чтобы получить нынешний) и AC-коэффициенты (чтобы устранить огрехи при таком передвижении и добавить новые детали картинки) +Т.е. общий принцип хранения данных - базовое значение и данные для его коррекции. + +И это справедливо и для дивх3, и для мпег4. Более того, похожий принцип работает и в mpeg1-2. + +Стандарт мпег4 умеет больше, но мы это не рассматриваем. Я рассказываю лишь в рамках, необходимых для транскодера. + +АС-коэффициенты - это как раз то, где заложен "битрейт". Чем больше этих коэффициентов, тем более качественное видео, и тем больше оно занимает. Эти коэффициенты как бы распределяются между всеми пикселями - каждому пикселю достаётся чуть-чуть. Если захочешь узнать про это побольше, вот ключевые слова: +- дискретное косинусное преобразование (DCT) +- преобразование Фурье + +Далее. Основные уровни DC и motion vectors (MV) записываются не напрямую. Для каждого нового МБ по специальному алгоритму определяется "предсказание" - т.е. какое бы там могло бы быть значение DC или MV (на основании соседних МБ и МБ нескольких прошлых кадров). И записывается разница между предсказанным и реальным. +Поэтому, для декодирования, чтобы получить реальное значение этих уровней, нужно параллельно тоже делать предсказания, чтобы прибавить к ним записанную разницу. + +Все эти исхищрения делаются только с одной целью - чтобы записанные в файл данные занимали как можно меньше. Достигается это с помощью особого метода кодирования этих всех чисел. Называется оно: "код переменной длины" (VLC), и используется во всех алгоритмах сжатия данных. Смысл в том, что разные числа кодируются разным количеством бит. + +Все числа в потоке бит идут одно за другим, но, поскольку каждое занимает разное число бит, нам необходимо читать их все подряд. Если хотя бы в одном числе ошибка - все остальные числа будут прочитаны совершенно другими. + +Для работы этого кодирования нужны VLC-таблицы, которые говорят, какие числа какой последовательностью бит кодировать. +Вот вроде бы и всё. +Теперь какие отличия. Отличаются: +- формат записи заголовков кадров и МБ +- методы предсказания DC и MV +- порядок следования AC-коэффициентов +- VLC-таблицы + +------------------------------------- + diff --git a/doc/power.txt b/doc/power.txt new file mode 100644 index 0000000..b80bb4f --- /dev/null +++ b/doc/power.txt @@ -0,0 +1,41 @@ + +В рамках тестирования плеерного кода нашей прошивки провёл +маленький эксперимент, связанный с программным отключением. +(см. также опрос на сайте). + +Померял (косвенно и очень грубо, погрешность оценить трудно ;-)) +потребляемую мощность плеера (даю средние значения): +1. "Родная" прошивка. +- включен, в ждущем режиме: [b]12.5[/b] Вт +- проигрывание DVD: [b]14..15[/b] Вт (*) +- отключен по кнопке пульта ДУ + - с диском: [b]13,2[/b] Вт + - без диска: [b]11,4[/b] Вт + +2. Наша прошивка. +- включен, в ждущем режиме: [b]12,6[/b] Вт +- проигрывание DVD: [b]14..15[/b] Вт (*) +- отключен по кнопке пульта ДУ + - с диском: [b]11,4[/b] Вт (**) + - без диска: [b]11,4[/b] Вт + +3. Процесс прошивки (flash): [b]14.5[/b] Вт + +(*) - сильно колеблется в зависимости от потребления DVD-привода. +(**) - это стало возможным благодаря специально сделанной программной +остановке шпинделя привода при переходе в спящий режим. + +Температурные режимы процессора не мерял, но на ощупь, в спящем режиме +процессор заметно холоднее, как и должно быть (и как у родной прошивки) +- в основном, я думаю, за счёт отключения видео-тракта. + +Выводы: +1) Энергосберегающие возможности спящего режима нашей прошивки не хуже +"родной", и кнопкой Power можно пользоваться. +2) При выключении с пульта (спящий режим) с диском внутри +энергопотребление у нашей прошивки сравнимо со спящим режимом без диска, +шум также отсутствует. Это, как мне кажется, - очко в пользу нашей прошивки +по сравнению с "родной". +3) Кое-какие тонкости отключения, сделанные в "родной" прошивке, могли быть +упущены. В первую очередь, это касается DVI-выхода, которого у меня нет. +Также не уверен на счёт SCART'а. diff --git a/doc/subtitles.txt b/doc/subtitles.txt new file mode 100644 index 0000000..4952abe --- /dev/null +++ b/doc/subtitles.txt @@ -0,0 +1,183 @@ +АЛГОРИТМ: +- читаем весь файл в память +- детектим тим пубтитров по первым 512 байтам. Пробегаем побайтово по данным, для каждого из форматов субтитра. +- тот формат, который оказался _первым_ и есть искомый формат. +- если НЕПУСТЫЕ субтитры пересекаются, надо склеивать их (проверять в отдельном проходе). +- если 2 субтитра идут в одно и то же время, то надо показывать их по очереди кадр за кадром (а не все сразу) + (некоторые субтитры в файлах - с точностью до секунды, и происходят накладки) +- работаем примерно так: + 1) задаём фильтр для поиска записи в виде набора лексем (массив). + - лексема = набор спец.символов маски: + 0 = число + 1 = 1 цифра + 2 = 2 цифры + ... + " " = пробел + \n = \n + - строка? + - для каждой лексемы задаём: + - как трактовать значение + - никак + - секунда начала + - минисекунда конца + - дельта времени начала + ... + - флаги + - обязательно ли должна присутствовать + - + +=========================================================== +Разделители строк: +"\n", "|", "[br]", "\\N" + +=========================== + +ВИДЫ СУБТИТРОВ: + +1) SubRip (.srt) +------------------------------------ +408 +00:57:23,678 --> 00:57:29,845 +I've been looking for you for two days. There are five +wraiths behind you. Where the other four are I do not know. + +------------------------------------ +SUB_SECTION_START: +{ SUB_TOKEN_HOUR1, ":", SUB_TOKEN_MIN1, ":", SUB_TOKEN_SEC1, SUB_TOKEN_OPTIONAL_NEXT, ",", SUB_TOKEN_OPTIONAL_NEXT, SUB_TOKEN_MSEC1," --> ", + SUB_TOKEN_HOUR2, ":", SUB_TOKEN_MIN2, ":", SUB_TOKEN_SEC2, SUB_TOKEN_OPTIONAL_NEXT, ",", SUB_TOKEN_OPTIONAL_NEXT, SUB_TOKEN_MSEC2, "\n", + NULL +} + +SUB_SECTION_END: +"\n\n" + +2) SubViewer 1.0 (.sub) +------------------------------------ +[00:57:23] +I've been looking for you for two days. There are five |wraiths behind you. Where the other four are I do not know. +------------------------------------ +SUB_SECTION_START: +{ "[", SUB_TOKEN_HOUR1, ":", SUB_TOKEN_MIN1, ":", SUB_TOKEN_SEC1, "]\n", + NULL +} + +SUB_SECTION_END: +"\n[" + +3) SubViewer 2.0 (.sub) +------------------------------------ +00:57:23.67,00:57:29.84 +I've been looking for you for two days. There are five [br]wraiths behind you. Where the other four are I do not know. + +------------------------------------ +SUB_SECTION_START: +{ SUB_TOKEN_HOUR1, ":", SUB_TOKEN_MIN1, ":", SUB_TOKEN_SEC1, ".", SUB_TOKEN_DSEC1, ",", + SUB_TOKEN_HOUR2, ":", SUB_TOKEN_MIN2, ":", SUB_TOKEN_SEC2, ".", SUB_TOKEN_DSEC2, "\n", + NULL +} + +SUB_SECTION_END: +"\n\n" + +4) DVDSubtitle (.sub) +------------------------------------ +{T 00:00:21:51 +this is a test! +} +------------------------------------ +SUB_SECTION_START: +{ "{T ", SUB_TOKEN_HOUR1, ":", SUB_TOKEN_MIN1, ":", SUB_TOKEN_SEC1, ":", SUB_TOKEN_DSEC1, "\n", + NULL +} +SUB_SECTION_END: +"}" + +5) DVD Architect (.sub) +------------------------------------ +0407 00:57:23:67 00:57:29:84 I've been looking for you for two days. There are five +wraiths behind you. Where the other four are I do not know. +------------------------------------ +SUB_SECTION_START: +{ SUB_TOKEN_SKIP_DIGITS_4, "\t", SUB_TOKEN_HOUR1, ":", SUB_TOKEN_MIN1, ":", SUB_TOKEN_SEC1, ":", SUB_TOKEN_DSEC1, "\t", + SUB_TOKEN_HOUR2, ":", SUB_TOKEN_MIN2, ":", SUB_TOKEN_SEC2, ":", SUB_TOKEN_DSEC2, "\t", + NULL +} + +SUB_SECTION_END: +"\n\n" + +6) MicroDVD (.sub) +------------------------------------ +{1}{1}29.997 +{103300}{103485}I've been looking for you for two days. There are five |wraiths behind you. Where the other four are I do not know. +------------------------------------ + * N = secs*25.000+msecs*25.000/1000 +SUB_SECTION_START: +{ "{", SUB_TOKEN_FRAMES1, "}{", SUB_TOKEN_FRAMES2, "}", + NULL +} +SUB_SECTION_END: +"\n" + +7) MPSub (.sub) +------------------------------------ +1.67 6.17 +I've been looking for you for two days. There are five +wraiths behind you. Where the other four are I do not know. + +------------------------------------ + * 1st - delta_T (in secs) to wait + * 2nd - delta_T (in secs) to display +SUB_SECTION_START: +{ SUB_TOKEN_DELTA_SECS1, " ", SUB_TOKEN_DELTA_SECS2, "\n", + NULL +} +SUB_SECTION_END: +"\n\n" + +================================================= + +8) TMPlayer (.sub) +------------------------------------ +00:57:23,1=I've been looking for you for two days. There are five +00:57:23,2=wraiths behind you. Where the other four are I do not know. +00:57:29,1= +00:57:29,2= +------------------------------------ +SUB_SECTION_START: +{ + SUB_TOKEN_HOUR1, ":", SUB_TOKEN_MIN1, ":", SUB_TOKEN_SEC1, ",", SUB_TOKEN_LINE_NUMBER, "=", + NULL +} +SUB_SECTION_END: +"\n" + +9) SubSonic (.sub) +------------------------------------ +1 115.68 \ ~:\I've been looking for you for two days. There are five wraiths behind you. Where the other four are I do not know. +1 121.84 +------------------------------------ + ***** период в 256 секунд? +SUB_SECTION_START: +{ + "1 ", SUB_TOKEN_SEC1_256, ".", SUB_TOKEN_DSEC1_256, SUB_TOKEN_SKIP_NEXT_FOR_2, " \ ~:\", + NULL +} +SUB_SECTION_END: +"\n" + +10) SubStation Alpha (.ssa) +------------------------------------ +Dialogue: Marked=0,0:57:23.67,0:57:29.84,Default,NTP,0000,0000,0000,!Effect,I've been looking for you for two days. There are five \Nwraiths behind you. Where the other four are I do not know. +------------------------------------ +SUB_SECTION_START: +{ + SUB_TOKEN_SKIP_TO_NEXT, ",", SUB_TOKEN_HOUR1_1, ":", SUB_TOKEN_MIN1, ":", SUB_TOKEN_SEC1, ".", SUB_TOKEN_DSEC1, ",", + SUB_TOKEN_HOUR2_1, ":", SUB_TOKEN_MIN2, ":", SUB_TOKEN_SEC2, ".", SUB_TOKEN_DSEC2, ",", + SUB_TOKEN_SKIP_TO_NEXT, ",", SUB_TOKEN_SKIP_TO_NEXT, ",", SUB_TOKEN_SKIP_TO_NEXT, ",", + SUB_TOKEN_SKIP_TO_NEXT, ",", SUB_TOKEN_SKIP_TO_NEXT, ",", SUB_TOKEN_SKIP_TO_NEXT, ",", + NULL +} +SUB_SECTION_END: +"\n" + diff --git a/doc/why_our_firmware_is_better.txt b/doc/why_our_firmware_is_better.txt new file mode 100644 index 0000000..04623ce --- /dev/null +++ b/doc/why_our_firmware_is_better.txt @@ -0,0 +1,8 @@ +- возможность подключить жёсткий диск к вашему плееру: полноценная поддержка нескольких разделов в NTFS и FAT +- поддержка файлов AVI размером более 2 Гбайт +- окошко поиска по фильмам AVI и DVD имеет расширенный интерфейс +- более быстрая перемотка для фильмов AVI +- более быстрое воспроизведение фильмов с видео-кодеком DivX3 +- при выключении плеера с пульта, плеер не шумит (если был вставлен диск) и меньше греется во время сна +- список файлов имеет полосу прокрутки для более удобной навигации +- главное: непрекращающаяся поддержка прошивки со стороны разработчиков :-) \ No newline at end of file diff --git a/fonts/Central23.fnt b/fonts/Central23.fnt new file mode 100644 index 0000000000000000000000000000000000000000..fa756cbdefeccae7678bec6985a73dfb8a5c2c34 GIT binary patch literal 9701 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

8e-7+6HuJ)2gz=>2Y4qc8{&G8+h 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 SKlQyQ4nOj^_$TxWxBNdE9%jD) literal 0 HcmV?d00001 diff --git a/fonts/Cyr24.fnt b/fonts/Cyr24.fnt new file mode 100644 index 0000000000000000000000000000000000000000..a4642b56d2f676eee25c3b0f480baf43a32482b1 GIT binary patch literal 9885 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 literal 0 HcmV?d00001 diff --git a/fonts/Cyr27.fnt b/fonts/Cyr27.fnt new file mode 100644 index 0000000000000000000000000000000000000000..27069b4b96ca64e558d28aae972751aaa5d7d122 GIT binary patch literal 13581 zcmeHOeT-aH6+g?gJG1TXdo#1U-7QRKw%>(*u%#tz+3~$1pb|rrK>UctY*j!ElmTj- z%G;TRk~Au|5+ekIUD2p0@gp(D8WAQ`f>CG@LX4jstdWQjhE(m8PG|g`d*6Ha-ubdy z`;XYOIq!a)`*H5M=iGbly|Z-Az{Nz@UiJ2?FT3)JtFF6r?An`dyk*<^+|Ao|UVr=8 z4YzLJL1W#4$fha$jgSwY?|FpiJfZ}pXsj_s&-E~&zFCsrh#-v78<7|d&l{led2zY{ z_|UvX^!&U*T0Xy*cFgaiy}+mDFQgOm28Gkdo6ka@xExBT;L~wj{;u; zj>O`0HEE*zo{F0|=8 z3oSabFiDI1hv?(|ee{$5etNw>Nvj8J$`6dtcLxTkF)&Q;w&L`#HA26(lC;LQXs4Z| z@7aSy@g!XnAEB?t6Lb{uD-v<~Vxo_JnXsrQ*+-Wo<8({1pT36hH%Xi9!3147I6(I! zd}h$5B|{78+Mxt}8u;6xK3Y4xfVK_C>4D)sdUkk}B8w7q-J&>s9pUeQ>qh!%dc>w5 zj4YtPjwC2E8mHXoFnwdxqGO{rtzT@@M;7mScmViE;PF(F zhL$X*>zDM=ua?9pnvT&j;9G$21Kt7L1N;{7XTZb2H`2W{oQctR#-?{7ybJgc@JGPs zGg10G!q`}hmXGz4gYf-heY6ANw}DRq%fJ)BMN4hkxD+}Ayc_ss;C|o>z*m8N%LZx1 zGUyKQ(Pb9Bv}}k*mfQ5P<>%49Xfrj;Xf&&nZsapf?rS~WzU zTotFEuS!sTRg&JdI!RwyJwmUqPS9J|4AB?X4AP(1r0C-FQ}pop2^zTIT>8)jar(st z38J+rx@K*Xe!A8sYh9eK0DfxS0DXU5lK!&Jrc2h3&@JmNdVGC?{sFvlL!9p4kfdie z4AGJc6SV!pI6Z;zpTM_`56}a^XUA=dY#gC28!h_G#srmsiHjD~hk*w#O46m9M(MMg z;`IAX79}^2(mOUM=)uh~dc`+KX_W20jt?mS5fWjc>FJ$2CjeoFic|n)fXY#^o}Ng= zb$feztzxmie{^(Ik?y@WHQH#51M0P@sdB00lX`l3qIM>o&SdNe;yp`UH-mtwrBbO@ ztKnNJSE{wg_wB3IDoFShonqt2kwy_b_e7#mJDv6O5%ffM?cTdDo4@1OYX|pOc9hp6 zAIhZ}5krd@UbRy8^AUgzFkxrhJC9eZwW4b)61vZ3tCb^1kDh2W3iZYzMe<|dYi5Sy z;=>X003Boqj_tJLWKn`4>o{p}DF!X7w*PFM%H_Ez*ks@a^0}$4?F*}1Yr~2=Z(A@q znN(R}Y-R@Xl#GXYVT7lnr&uhC&}0t7EtMLLh6qbx4I|<(SgGu|kxOXM%uuO%y{;0i zG0|XCHh7C*`YkJ!nwZGvDuSHlSh*ZbiBTnDXPhi#&X9GWbc$N8)->VPs%1s8Z98Is zBC#zPph%fwaf(ws-HvAroHZI8Kml1Hq2^ppg6+Cvh@ne-$6*dKo4?=?wW^g$)pMLe ztyWef*-IKKVf4cVJT%Xpc^+nr162!Z1lz}#v*J*HMUsN6Nz)ZFnsKu{PfYg;hE+lu9-AdkTiP)= zL@n^<6m02$5tVb*GGk1W@^3YXtvOHh6R}+{w^g*Ff6!08c}#Axcp1vD8bTa~3OUY^v#0#&z?#Tv-vJ(N)Z5!Esm>?5L(us*vC>T>p{px-QR!3RKx@tqzJ;fz+XQ zje1p)dY~K*Ylp}BkfkB4laKL&6UHbJ#UBNL6vl_P358k(77 zG}zcAV5L%Z!Uo%o%U>xVEv{f_hbk@^!Oj?fSu4mDA*7KBq~Wxe1{B7&_d+(RA_OqQ zDGnJp?t|aC&ka%mL1-Q~C8n4o+G-=4(Y~sd-=GQLJTrbBm%iw5Ui>&Rqu%965U8M59>5ur$-trb9+mE!~Kk)CM{ajTcS> zm~GN|XuQuNmU`7(sPoLHl$j#d>J2voEv*~tjG)hfjRyS7uuY8(UQp__s_&)v-G^Lg z+KYu1oM9)d*1Q77tnJHTyE39H0CsZ^TR#Mgy)X>^gQjmCY*> zZ22K<{k-ecJT_)Uno&^^)6%l29IcogRTSG=I`xHJf{-~8pU1Ya)KL!)RHg+Vp1`6GDA0Nvk336%{fLIG9ROiTlBo5n>HwL)}W07 zV3{Gwk>D>-JP#pBMDq1IR1ScZN^H+2xkC!xNZ_mD0>l7t5fd3P@M2I18WXxO%2FhQ z^?E@>3iZde<;$$!l43ubk0_o4C%~lQtdjSjk2DWD8It%6z}}kdlfNMeCLm7lVMbF# z93NoQ9!$@sxNy?_BKvgan~oh#t;nR`J!Zp?DKDLDPS(OgfkN? zqJHT6k789eR#K6}-3C1%^!YcLfya^KRSF)5qfA_@BlyBO$WF*xnsO<~lwsb(?&hR3qMAv9R=}F;2M-?3A{NZU|CkDTSpv0gL9g{z+e|J)UtR^OR+n4-pcm=#;Hzr!(X|5vCpIvV z5vN)9(qvg2ZTB+BBL0P(B$JWBRpSjgL2ENTrxsR4Ln z#}gS2{8g=%?`>|GoWz)L;?u4^@NSMa8z)X2J#yf{E+~=e%M?_2?Bxgf4h^Qz>D(LS z9$DT1W=?`))ykbU+Pqo>5-9In$f z6~(FYKi%s4oa$DoSo3*3B@TnJ2BB`|r}j*M+ur~F$q&qn8{qJxmo0RZ(060&BTYLdQeb_#XAHWMi zkjY^|vWF4#uWio2b$X~1B7zB3y_v@43tN&+`|^b>NrG+9E9m!$ew!HyZlf_PW2&z% zu`V3?4f+8%RL!(7uSh2IhB};~zE~k;e`a5Xpun!lzv58Fs`Un6+GNZZ@UWCC3ij85^s`CyI!?kgylgfyph$h0h!SqQc8$PRZ#VU7+)Y7SBM=>0bIVzDIlLsS!_c5xMS><@`r)b(~ZXTG~^g` zD?|LCk1Z8zFm%TxL4z+7UK_NxXp%~}zAWRz&Rf|e2{&ttaN2aAjSHpuH&FFg5ANB+ zXPr|4{tZxv1}Cst%)LSGR8Fw(wV7PBfBq0}Ml*6rsxIn89Vsow}7PQW_r73WnjgCAea6@u*U_4bE)1& zrYAd0RlE{W5ycpc*CyTu4X80IM(rp9DsJKt-QYp5O9UfoqZmdrVf?*URj;b1dt_Eu z|Kgkc-toQP`|5jVy?WDhN^64XlJoXnu;-lF^DaHBbjfw|H_g4mzHx5x>I0=~Zr*Dw8J-cpcEnWa+aRm42Qn&?#A&%D|Obh2ERZ)BV7cNDt=}n$1acAeW(c=VUsR z%h8eCAgw~bC$G}Yd6_mQJ7}9?DT;NTN>yUk82+-Qza1a12-h)U6qM39uWu6u1F+8}Jd}Uf?0%x4_>4`P2+e z0A~Sj0Imfb;KRU|fCqqI0e=Ou!zn5a=jlw~>fsFCh4_oZ5*4qFz&WE?x?;3I zw*!ZP-ve963iO&Wh3v5*`ox$_$H#IsIG!U5xC!{^c#3`ibbx08Ma$4>nnG3Jdf;y0 zLEsr+M=?Ve0rSNIEd%!ej{+GzLsP&7z*TyV4gy~QehH+E98DT|nllFI{YI9)h5R3Z z(Nd1~0&gv)=u^P`z)yfDOBq@#$uzt@PqV-Q;1F;GSlynXm+#2Xo*j956YwtJYryZI z8<@z@PM`){<}XLSCN~+_WnZXl+vT8=a^qFAv$HcnR>XyDcGPjk#~o*K@{B2=b|LLH z5kn}FWLPb`RWs7b#6HKu0`diHq*#?UOZcMIruOaIH&rVpl1WJlv@|kOF4yZv9=q$l zT@^zS>LB6S3UZ_xm0i0GrMA#)G%Q`H@i08oEZf~qr`rU1`$ZQJ#FynDf1TH1hKvh9*6l4Upzhgq8< zCKPMB;mfoY=~zB4Z6K$Zxv^q)1Bn5$S|Sys7RvyusBJDd>S z<6(K7ZPhT%1?M38W!h4O5zioJw_TwnG!1h#omLc0GfkoAMk6%8K(U(5=0VG7HXF_8 z)FfDU5yV~5`RE+9uo}`8;NG((BB-m16sbl8Ec zaK>+*+n$MVS(1%nbR_*^HeuIK+8Mj5CMl^_?HQjXA~-J6EKzm#CM8+cG#cSv#tM5f zNv*tn{@nA=fBl8?WiuR|j+uGn@!8o29yw}LsUvEss$q;*DizDI?U;+`ZurseU@Nc% z+i@B;W|R$SILks!jK;Z$LTyqtY9`~hss>*0fq)z&qjb-#XFAe{F18{Q(!#FP4(hR9 zh8H|wq0qx?u^=LY9}t;!J8TYTQ-tT)NvlTHv>OfET<`Y)QMU^V-ELbvl3DaUK_ag1 za(0iJ)!-u7V$V?Y)zuZDh7-*NIDfF&0AHxl6E4eOggTv2?HgrLv`<8}pl3WqwOXr9 zO)V^7y`iasr0cb*GZ*IP=O2QEgC@%3iB@ZAY57*)Mrb7P1nq~2D(F~@Bcx>-Mrhk* zuNywbLBn_rv;nQgJmc1c5wyAkX@?4)VuR+{uJ*OfrcZZez^}FJnRZyyBOS{pGCJ3r z%i;;DW!d(Ur6v<|v8~siKUgNwIkdL677l0l$^c6kj3jvDc(7y{;c^YtXsRptSd6x9 zRw|>0VW?3y>k;NgNJX7v$1u3fre)P?x~>T$-#h-q6Z7-8E-%-r5TTCO73!z4%a0q1 zZcO7%*b!=Ygeb|crx*=u!uFK9?AD4*LpNu;d<&$z1+VX zTHTsR8hE!kBID5d^m{(I0+;Y z1;biwG!`wZ5a=;+q7tZitXk{{VVjZnJlDId1`a4L&moYts%fsSvSrL9Q1iIx9ikTT zo}P%j)FTh|ef1mf_$RKI5wypz5UA^Rop=o|2-NK9+y_5c>2_EVCp8xqmX{suSB-$p z;u+V=;R#;CXNSfEHIFem{Tw{}+!0@5>U}V!u}{s~ED83O$Lbk3HRNPFj|(hkGn5XS-mG=Vgv#%bULq`yGyX5z(}} z4^z_+YvYKAIFk&g1$dwkjBWU6VJ<7nDQ^jPi&n@RjLKVmJK>@*sa(}shC zC4(sGV2eS0) z*Iopl&gzx#2Yx=*jhC2(HFz-#fejswMw9X{E{SWRT*k^s@$j?7nF)`ctBAKSRKQsN2{!_|wv|jE^Ff z-Q$dy?1R!%!(Y#Er&G7PU2H0zZwSFI6pN$mat0?OpR39w~-`~=vO)4Tey*`v|&zz3d$ zM_s!W{R{{?7I%65)V6)JpZe&^!~N7S-&dp*zAt;PKHwl8&jm+H2(O$@C#mn%hf8kn z)dy~b`?2{5^s?D%=w}=BKDG&dZ=S8H3S+OoR$=|#&vxIA+DNIQPlB$BeY=J|$Totu z%MIX;Ai@?GC9E(sBG~CvA)=0a7x%TzS}BaQs=-$@Yo$M5lX=56c?@uSrN?y%LJ;t!#O^a}kDN`Eu!9i)_udD_|8H}B1x_vX!;KWAF=^9P9@{r0!MbN@FV{PuV6oqhCsPyJx; z-PP~!J@Vw!v-^K|@W(VeI1y!O4*zDzOTS9|p6GU>b+nOYJ9G4IfG4#W&ZNfZpLlLdkI`f4 zJS_mmM)PzS@Bv`YSf2g_*m6srP6FD11LJ9W4{&2VPo;@Gy$HBIF+uw>8|bynBu!=4 z(NVya?0R}Um!~ti9Bs?5r)L3|@}u-fVS-*RtfLPKIoh*snqFGBp0-R*(aFgIwedW# zzCiD-&(i}_d3t?nJxy1oJ)NPkji9|T zM=x(I&<7hcv}dM3FU{m=&8A!E;Y|fP321K`r30l=dZU!58+h*5rs;L9K*nr=UIR?s zR-mJ|jnkFeGPGm!EmYfFpfj7%{?BiqXFs2#OMrWB&(h)BN9pwKX&Sj>lG zrVD^IThg=z@G#&Y;3VLWfWHFTfX!b>(?fs*fa8ES0Pg{A07_qk?g0A%F9Kc%`~`3w zF#V-88GwC&V}REH=Kvo8rtVDBy?`eGM**+inWuLFJHDKz-+dYVkLR|#^7PnUqxAM& zlQedBnhpbA2Al?baCeqQ?n%=gz|(-20Dl5pxM!5sdwtaRCh6R^0^Pg4NUv@$P~odNssYXbGGEKlV_!?t0^n`H zCBWE@G~KgfJw38xicSOW{Q4BV1Xxpd&v)Y{=9f1z%E}MoiPpWHR&f^USnZRB9B9#Y&~pZnw+Ts_VLQb4{cW zP7*1rX}YePnwEuInyr9j7NtEjG{!;gag;P2w_Pih4A*tIkv*K@a#_vXS><%Z)Uu*V zA`xIDiPRf!End8M`O>+wuiUzG>!HO)qh43YSLd3tP@nHy{xj0$PEA*Eg0FQ>!2+32 zq_P?mmd&QJrmkC-T{BHB6~=2hj*hOioYvyitIy4&mpYw)FNSftZ!L5$H@9AIG+W(L zk+CdG=sg!M4!xzIO4g$erHRCxXHclLDO4%urHaa2vxKT537bS=H3oS=QM$5eV&a+k zXC@{#VKm35Iy6DmR+Z`yJRU`sCS-!Cdbt>6m=vc@rz10s7@V6d($Se#wF=F|S44Z_ zKLko+jVJKk&(4$#!w8Ref`KDpsInrU6V_{0(StM@pGu!64^u<8Z5R?P)z%F;UPIW8 zmgC|Jdm#)`U^`B$5yBYmC7IGR?N(1u{zIwCt|XEaoMB#5=Db;%m6!<#X+k5aO5k?f z8l;jEHR}YI)*gnqf|G@B;{ljO;!kBT(-fShjJNhs8^VDuf49fr7q18u>P&1eu$e2J3_9^A!8NaePWfXmVDIg;Fv!JF8jiSv z1(OFE3@h+3K(1zL>Rf>7&w**^Uq1Fx#y}FDsh9H^^`2e-1sSE&?0xIz5-H877+Oj!Xu74n%$kf*b1+1S z>P@F+WY+XP-E>ZRf~Y?T5<>6bRsIIcZzexmF%a{J8p*6_`0gk|qS11VjNvZJr-B%} zpFFgU=|Fg(5&j(sbE!9*EhJ&d@=MI@lSqw>BevVRbvx45t;OPaRt*tqowtZe2#?-5 zcI@~<`{Rp<7(46$f~nE0R*Ey@oQf4IUMw+zajeOvh%tgeV%tF?$UCsrQ>PXd7BG0A z2_T08NCg{~frJ@eos%{64@?iSsQ2WI?*Kd^Ew|kPmn_CZo1a;;({`H?4uOK0{^PH5 zKE*)igh>+vQSCz;vivkaiJnJ++K2Ue5JTkE5|%>9)%}T)3#7BDWI_xp^m$6_blOdo zG~!BWAHqa9^ok^(ng5LGL-OW=?z5MPkQ-Rw^YEWSKBY#4m-3xoSr?|E!z1?9#XS0_ zE~>%^HN+&W3m*A_#5UCs@8~5z1-^@{r-M+Fbp^=_d6?M_y0uM62%cqw@f-NgS>a6<7Z)#HyLRp4XXfYq$u5XitF_x#u3R~H z?%atJC)jat3L*@dXE983_wL=@#zfl#>wNyLw-&Kt{N%__c#20CsxeZ?Zu}Re4T!W5 zi^yTo$49v^g=AnZP9#wf@lg$)R2(t9yf~?@uwtR4M7^Ba&%F9K(QBH~US8be^Nf=# zAOoYZT)`Ma$kkCIOD2k$6x96DVEuBPMu<1L;{y+ToCoou3dwnqizyGho7Rn zxQE{qORGLUY6+19MH5M6#F|($4Ypo|7aOZ))@%rdxTY>b`i_DV>A*+QetCHVY3k``wSzejkyn&CkEt@@T92IPUIkt`AheZmOBw_Bp z7vu29;1>g#-Fh4yOmrPF5-|p`zEJ&GuL^5?zzFac`j%yzXtEju2s%7rc?8q!%6i0m zIW$9T7=TXARQDy=O(LDg00Ivr-lP8TM6dSE<-p6Zc+1MNjDBs#E|R6|F7GI%9c++= zI3(Fal}bFI@#v-y4!eI84D39wLP&P8KVOF4!s_qIj`d*Y!}B*Cq6BP1n1=TrYP0uJ zl^baBa>Z3p%Zeb5lNz6;|5VYId>=;>a46GlNH=3(`U$IKAT7TEcgK^RAoKhT)-EMb&%R|RYaX7=24bz^M z>$Z(vb7I>w46lH1=|4`{R;5o^QOq8Yi=C#Yx^OEk^dYYmIc`~r&lKZbEP8B~OIdqL zp65U8`Z^x(C#2fK?HT5+veTan`r8l3JxkHzHuDiGJUpC}8uQEA4XmkW5t#Cl1!E{e zavV)ypXqfjWbZS_N!Kardu}vB%cFq@)TPcB9(K zH3Lo+E2AoJY&HY!58}33Rm(cEbLUPp7@~(Ayobns$8rAl_rL$msHHqQV4?f`qZ7DiSj+h-milLW-DCEiR2@iTP z@dC%gGB!5iVq-y%C5PQK>UiUPhzY?0u|Xmd+O2!pnLgtuT(|e;SL^qbm{rU65zvX^ zaGBq2tJ=3==9`d1RlTIn_asr!6rf zW0R~sG=rN%bTIoN7|Eo>{;0P}s*un$#3LLhwA%lu5mUEAB`+$S^4lABVQnabAf zPwhu~YWvp8Ow0|mRy&)#>i$A<`K9|wGOKI@$S-wt@SI4oflr@CetN~J@#5EaKa5^uJdayj$FLY*5E^xs`sKLC>{>#lRi&wZ0 zp(GZId9l`^a9&S`^)?X5Rdtls9v0vYRb_=*U*q-J; z8SvZTKSS*)TUHr3etrMhEnqriBblM@!H-bnV(9 zc|BO}C zBD~o&fR|R%E33o{GtXdNmV4!$RpEsb@TGWN{J6b#6?nlictRC&;XH*EhTX-mzK)$g ze+KbJ<0>ogIuY$do`-`e zcB)(j#ScpYmH_bvpV9Lq)}(=F^m2M%f{wKD!F14Tn8cQ753Wn(mTJ#P7JofEm=2Zy z(7bO6Iym$#%J!5=++nch)kAEWYqjREx@2E%V=9z)7<5$w$&s}q=xMD|p(IY~#UInD zIQ7PT@B;7*_{%2%ejH4XF=;RvS>c_Ii^Ge~d=)3(zm@S%gi;->IMgDl^tngQmwZkB zqX)P<;p~#BL?We-Yr%newA1K@Cc$FrU1r!`f**q89B@BD+E{bY{RGhCQ9qatAJH{v zFdb=QgXzFM!^Q^Fk&zOt0^QT^-i3^mK*#p)UBE*8zVdp=jg6GL-6t~QFOni|D2$`) zwaXC{#SZ0G6GE_13ey?lEs4Km09xRDS@_~m&EoqG2Z<{`H<$LK*dcs!FtLlmVr4bX z%`tNi=7kN&CTwiQybw{O(o67aHe2ujE9Zr~j?ma(xkzYi<-9W4EPQxE1e?-tV6s&7 zCH?Bkc?E4?C3$h5l9IiM0=`cDJ2Giwo!-b<{`oschXZ+4d9nTGCYPeJ%}_vfkQx4G(L(pKzcPl zi#9-mH9&?oL5(;=gEvBtIYWs!Ly$Q{l1WEeIz^N_Mvp>CbUjFlJ4cl~NS8fHls!qA zPfcGwOPfGUlRr$EKun!RO^iTIo=i}2LQbGVP?}UyV?$4(Lr|kiQiw!RqC`=pMpK_x zRc2IIX+~3~Yg#5*S7=97s7O|&R#|UYT4zXBt4UX=TUu&KSglJ~s##ogOIfQ^Ta8Ou zuU=hjOj@o?TCr4Il}uZ+Olzf@(wRb{|cWx-Zu!D?xCRc63dX2Diw!GLWkR%gOi zXTw}+tygHoSZKvJ)b&_{?gKKrKl6@>`cf@*nhIf08n1CO4d!2oKiJ5~efPjmhg&%^0j-iMk zeS@i>i7bVNk)?|tsE!||jw^$Tv8|FHtCA~;jiZ5!#E+1hvX&lAJnrQqpQ`~wGpbV!PmAM+PE34ugTuJ4&AyI z;k^^&z6#~P53jc1=fMrOxWnnf3A4D=?ZgG~#{j;;*TKWf$jjQ#(&*LJ-r(Wr=IHS7 z@$>cd`2YX^A^8LV00000EC2ui03HF1000R8009UDKmv%tg9sBUT*$DY!-o(fN}Ncs zqQ#3CGiuxzV&K3K8$*g5NwTELlPFUnG%;bI2NNn|%A85Frp=Nj4gi<{^QOOSY`pi(}8KUCY*B+O}}x zZhb4au3fH1uT~_&m+#%bfHBgI$ajfh!+rw`El@yl(7_A~8?HjR3*{<@6FaWScyrUd zgo!Mn9LJ6wLcTbewnKRd5zm|(!?p=oq39~5IiZS$OMtIfs8m~aJzHky;xua~B!U}P zFz3&(3F!8$7%oGcx)4X6@;LXH$`OwLw&P1cJahNrnZIXFfB@B1UVoooHoQU6aX8^> z?@7RcZte*n$TG1If|-2uB{&yo4>4y4C&8E_-9P_4@=tsBbdy>f@hRwGR`%UyM=Z;z z=Z`C{AR`Mh%VZEwF{rS%T|*`?X5)=G=BVS2Jof10k3a?~afQ4T;tT zD#e%s;6E!OgN!cynAZ)A2Rc+El3<1@=9pxbX{MNZJrv?-z8sMX83Isg<(2;&kjW!r z;L;_HXZGpmpMVDXXPOSJS;dGY#%ao&!*oN&i!tuW(3gT%YU!nz21;l{hS~vTJnH>p z%P_7Jlb!&9UL=JBp z5wiE$dKhI=Am*_}9iLoiz!FK8vC0*t4D&)DFQnPbIGa>+&OD37a?e2fv~$ozlQeYE zNNaR-(o9#hbkk5n^mNoz)BJPQSVO#NgQ6YwGSo5yz4L9jt!DAUMTgyU%_1Va8!Gq? z$F$l!gN&c2xDC$R)W3oRK4kii_wV1o=S(KvkNfEGpNLYdqS+n>izfNn>njQwJw3E4E*#n{qO-E zfK80odKv!w^w)3X)>cQ{komDlr+)DFU|)c+5eqxSk5BeXpaK`j82l-ue4fI_0i-uQ z(FI5b?=#>-y2ro{deBu4Oi20iQw%a-Lw)=RUG+=>Bh+p1gD{MtpvLzx<{gAN`awn} z?xBx;1aNjx=*Hy#I;1?z+3-H*gAy%t5f6M&&pjDXh3$w~M3^B}b3UcS)V-j)%J0&!@2IlJ7<*ft({ zy$xSz3z{Iys6UB~rfjC+kjdso$D|$2Wv-E9+A4{(sgbdcX8dI8JSobek+PJk3neP4 zcFI*QuaT})UMpicNLA9(lD520*Df>3rY%x;xx7tjPGcH<`I3{SRAsz&gNtUyf)j*@ z;{i7bH9HvQ!>#|YG$m0 zLF2)SQ_SKN)DQCest^wL^r~15QV0=JE1}|os6{=6DsqtvQ8@Lf zP>`wy<(JjAy0u4JJxHyjU{G@u6&AD*h8pf5ick2p6+2KXRk`=p!Wy=Lv0vk9)1Ruzj7x|EDxMKKVLagrTX@19Mj?t($RHlLFi=TN z%AJD$rnDPa_<|L58->+^p&J?0ByCS>##`yf9I5Dm8lv!pFJwUpz@6w|hf7>oD&z-- z$qz2bB?u*4!35-bMl8fAT%3W^W7f=17Q11N_DT*Fgy@14W(%|PZj6wd(@iY)i^%c@ zGM=flZe+-|8aqhRzyGafE|KW9zC_Kw51S=*`6b~Ax2wVyCg_4Q9L^1M_`V+gFvBn` zVg-@7#Pm5aioY|oiM0m7JcICvgE^XE5~7%igr|Z9Y@0$d)0xneCM92T;@K2*KU8*a1^_^d_Db~s zc4am*XgyVo^Q3thS`Y*ju%XT4b%DNdjtffDOyy^Qlj!y8SUs!f3V0v=;~0@JPcYq0tJcI^jZ<<=~gcg)N`#Xl~vI=07Q$>n`W!4 zcU?bND>kK69tIQ~mj*FQ=^2=v?n-#g?C(LEQ=k z9+;K0{4Xwlxm{u|bGgvG=5V<=&dVL*ofk*vK0gc4gI=GW6MZv7KRQ^Fu5_;y1pqH; ZI<|815vdo8B_0t-L99-njtnFq06Xo*MTGCYPeJ%}_vfkQx4G(L(pKzcPl zi#9-mH9&?oL5(;=gEvBtIYWs!Ly$Q{l1WEeIz^N_Mvp>CbUjFlJ4cl~NS8fHls!qA zPfcGwOPfGUlRr$EKun!RO^iTIo=i}2LQbGVP?}UyV?$4(Lr|kiQiw!RqC`=pMpK_x zRc2IIX+~3~Yg#5*S7=97s7O|&R#|UYT4zXBt4UX=TUu&KSglJ~s##ogOIfQ^Ta8Ou zuU=hjOj@o?TCr4Il}uZ+Olzf@(wRb{|cWx-Zu!D?xCRc63dX2Diw!GLWkR%gOi zXTw}+tygHoSZKvJ)b&_{?gKKrKl6@>`cf@*nhIf08n1CO4d!2oKiJ5~efPjmhg&%^0j-iMk zeS@i>i7bVNk)?|tsE!||jw^$Tv8|FHtCA~;jiZ5!#E+1hvX&lAJnrQqpQ`~wGpbV!PmAM+PE34ugTuJ4&AyI z;k^^&z6#~P53jc1=fMrOxWnnf3A4D=?ZgG~#{j;;*TKWf$jjQ#(&*LJ-r(Wr=IHS7 z@$>cd`2YX^A^8LV00000EC2ui0BHaq000R8009UbNU)&6g9sBUT*$DY!-o(fN}Ncs z;=l|7GG@%c(W1wXA2DhiIkF?jlPC|0B$?r2OO_~Frkv@KWX6R8ILe4gv!}$85JhI# zApnXDpGXs~oS0K2Q4}bWN+qa~B1st;)5x59kW9#%6-(wUx+7}BN7&MOB%@YfT5L6Q zTC|BVgVwDE-(u5aku5d>0$pJn1~_bC!x~o}$shwS!L+Id_f2~kt3{5E4PtJ5kY(Z> zS`lJHCLpaAf@D$;RE^q|D*%2Ar}m0kmnzx|GnhrlQnzE9iJ^ox$W~QpY`O^QLO!sq zK)<5}|8?se05ZLg7&1B(#z8^*WFvAHUo_9%61TJ+L) z{#MU0_zUfj&=Fe9kqIf87-9%`T2ytOO_>Zw5L(mu72kZ+*|!{FfKj*~T+~4U7!+xx zu@FxOK{1CR->IU?BXc|=%Mpoj1RN>sU56hTkzr#W0Lx(rKz#yTr-y8m-S=FQ)MclT zTn3>q#~g1Q=!$ow7%~SGhO9*1N&=`NnqF@qGftoCbMB-oq z|qv6cca)q!FVkve-f;U{&D+FaQlh3>H}C-4rp!6+avlm_%uW z!XuAlB66sMa%J*GBORGosCvNoXUQkWEK;E!9{~jvF~J=3&UDG(bGJJOE%eYt7j5*> NNGGlI(o+Ql06P%}fA#TGCYPeJ%}_vfkQx4G(L(pKzcPl zi#9-mH9&?oL5(;=gEvBtIYWs!Ly$Q{l1WEeIz^N_Mvp>CbUjFlJ4cl~NS8fHls!qA zPfcGwOPfGUlRr$EKun!RO^iTIo=i}2LQbGVP?}UyV?$4(Lr|kiQiw!RqC`=pMpK_x zRc2IIX+~3~Yg#5*S7=97s7O|&R#|UYT4zXBt4UX=TUu&KSglJ~s##ogOIfQ^Ta8Ou zuU=hjOj@o?TCr4Il}uZ+Olzf@(wRb{|cWx-Zu!D?xCRc63dX2Diw!GLWkR%gOi zXTw}+tygHoSZKvJ)b&_{?gKKrKl6@>`cf@*nhIf08n1CO4d!2oKiJ5~efPjmhg&%^0j-iMk zeS@i>i7bVNk)?|tsE!||jw^$Tv8|FHtCA~;jiZ5!#E+1hvX&lAJnrQqpQ`~wGpbV!PmAM+PE34ugTuJ4&AyI z;k^^&z6#~P53jc1=fMrOxWnnf3A4D=?ZgG~#{j;;*TKWf$jjQ#(&*LJ-r(Wr=IHS7 z@$>cd`2YX^A^8LV00000EC2ui0BHaq000R8009UbNU)&6g9sBUT*$DY!-o(T3YbVy zK*Wm}Ga5vZVPb|1D9DgODBz=v9Wp*z)X1`BLKGEamIOI+U`ZKJNa>mbCFM$&Kr@n& znV}{CkpMb=%y6WPpA0h6$cm>gUN==Teg@s@uw%sx3qc;yGe=KUTJiSz^S2Khl#X2E zD)cG<1w=9+wdhjSEv>(Q{NkzF0amVIf>|eG%&Ffy5A9yUq7qJbiq6*{NFN`#s3xm&b_a2#^21L&_Xus5#bC_1kvio@1MVW!%*vj zf{=2&%HUCE-W=v(ubp_$JphDPm@>wwNKh_SwYU%&TFi9-Xj)`4%w+%IN6UJ+{iP3TnFxZ* zGBO+l%s=sv_>VpW>2u|A!wobgW&PwPoSqQ*7q9g$rs^hiRStm2C{aL!{+T7a4J ziYt;?cMmeOXpxdO!6XPkaASu5mt~QVGOA^HV8IG$rIfh>mMfGEvyc|93WSO;&A@37 zKK4*$pFOeQGTD90^qEov

|RF3SA(PbmW-TB|_SEmN2=0!k{-m*!EH=3F5fG>I?1 z2*c?+_Pm(kmFe7L8i%gsPRKe{YssJDIqpyfY|!YV8P)r|>&q3#OgZ6*x~Ajl*D5G_zK z!U%&4r<}%Pr+t4Fxy&%6$l_IxQd0KTM>($uW_UDn`48AS^K2a01tt_5d_W77m;eVD zQVcfTKq#C)@eIS>BPfTAt!asVjE!WMl}6?y{5K;|rEfenlxNjxLL Q3R>`j7|fssEf^30I~NQW1ONa4 literal 0 HcmV?d00001 diff --git a/img/cd.gif b/img/cd.gif new file mode 100644 index 0000000000000000000000000000000000000000..0332da25a7dbd4bb17877a5cc79d4b1ac99f1e3e GIT binary patch literal 1412 zcmV-~1$+8ONk%w1Vfp|y0QUd@0000E5lRvjFc1??5*1Mt7*!STGCYPeJ%}_vfkQx4G(L(pKzcPl zi#9-mH9&?oL5(;=gEvBtIYWs!Ly$Q{l1WEeIz^N_Mvp>CbUjFlJ4cl~NS8fHls!qA zPfcGwOPfGUlRr$EKun!RO^iTIo=i}2LQbGVP?}UyV?$4(Lr|kiQiw!RqC`=pMpK_x zRc2IIX+~3~Yg#5*S7=97s7O|&R#|UYT4zXBt4UX=TUu&KSglJ~s##ogOIfQ^Ta8Ou zuU=hjOj@o?TCr4Il}uZ+Olzf@(wRb{|cWx-Zu!D?xCRc63dX2Diw!GLWkR%gOi zXTw}+tygHoSZKvJ)b&_{?gKKrKl6@>`cf@*nhIf08n1CO4d!2oKiJ5~efPjmhg&%^0j-iMk zeS@i>i7bVNk)?|tsE!||jw^$Tv8|FHtCA~;jiZ5!#E+1hvX&lAJnrQqpQ`~wGpbV!PmAM+PE34ugTuJ4&AyI z;k^^&z6#~P53jc1=fMrOxWnnf3A4D=?ZgG~#{j;;*TKWf$jjQ#(&*LJ-r(Wr=IHS7 z@$>cd`2YX^A^8LV00000EC2ui0Qvwm000R80ObiBNU)&6g9sBUT*$DY!-o(fN}Ncs zqQ#3CGiuz(v7^V2AVZ2ANwTELga%ToT*AbSg;u173qs#1t7qFKmg(Y?uFk@Uue4C0s^qq zWLp4%mDB0B5+RHUSl@v?>64xSEfvt*e<}sYQgVxVfZ&BYF?NN4D-AFofa!6Vl7<+T zc$0=A#v~z28h&Wvi!W&iAx$f;BqNMCx^!brHLA2@jzAiq;6Ja-s8Srop+}^MN-%bS zkwLjll<_O?om?oOGm^mxSN#=T6 zs;SbL|7gM>oH=&4m_D(@fy-(CU?ac)Tiz4PUbqbT&p!D9nqvj{v_|Fue@3Y3Vs9*J zX^tjj3R*uqRJvtres((Qiv=`c@Cu@d_V*@hjNyY#e~iA0qk{tM@LP3Y22kCxqWYR+ z0mfDb-b>9Q`>e82vIi!$*k-Hkw%m5>?YH2DEAF`DmTT_0=%%agx=@|u?z`~DEAPDY S)@$#*_~xtczWf>#5CA*GxQ}iC literal 0 HcmV?d00001 diff --git a/img/close.gif b/img/close.gif new file mode 100644 index 0000000000000000000000000000000000000000..9bf46edc62fd90ca5c06832bd1707f80416a5f55 GIT binary patch literal 2521 zcmeIy`8yPN0LO7{X=Q6u5$hUvV@Alhg<;H?VKB@XbGc#;gHW!7RTG5b8!}?NRUZ92$BGXTs1I_H!w{!w1_-wo@{85Vq|&M*2crcCc@Z?WpX~| zoOLS1HqqpK8pQUNsr@w=6lZFeVGd0&v(Ge#vdo}q<_>I27|X&j+X|L$3Cpo^%0)So ztl{}Kh-^nxv@J5j#<}1;@}4a!-xgKm;pT7WS`0+eK>5^? zNlX->(Unk7q(r+C8?a=yE0O2t8-^x5LX(T;}z764SIqN?#2c8;;4Pz`&WRc3Ya0W-n2e% zngCA|q=f6^=}+AJb#N(b3m)t^mTKOCw`))7ji>nFHB5#VvQ$Z|BrC z-39TBK)Lzl?f2E&i}efcRd<%^Wfcr|mg(mecJ)-KvJ1z%E6?;*=?&H>_t)qal#LaZ z50zE&hU#>RIYMEB(#S)ts+yM3M~Y)SgOIbD7yT|c*529w$I~PH&hd8x(reG8dIXb#e&KWB^yqKP z6EEjP;^o)NtE+44o15EvdwZID-?_taSeOF_ZenPB@PK3wnja5I$xB{?{c-HiGnlCaVGb}VxX-dRU>T~Yma5@AAhw<|+)^#P zXB;$Ag5uZwTx@xH<<&6%!Du;@C9CGrT06!?=7D@iTI*gskhEq-tf-%8AT+oqjqu7R zc>(=HGb`isy?n-tdNtR(b<-Wp%{FRABe^3qZhc`kRB`fEwSr@!j6-Krpgz7aSqmvEF;3ID6>5s>Oz<^e&RS6g&<5#+`=x0Q>c?WynM`6W|6X z%^#gbfJH9HEEK{;aPR;=^G}OQT<*LbcJ{KPoEur&f` zT>WXj=j}@Ng0?&$#Cm&k{muMnZ|~lo)M20~< z4Bp8$Pp0Hve$5%pNLP2eztMs#XFv7bD!#1raeg99I(ALYFhE# ziP^FpjzZbT8TzMW&%_(sbe#3>5lO6HX!m2aoIw0bo2D8Y?+N|-bpH?czx&JD{0*Ml Bv+@7{ literal 0 HcmV?d00001 diff --git a/img/defpreview.jpg b/img/defpreview.jpg new file mode 100644 index 0000000000000000000000000000000000000000..55f40e6a1a2672bfac65ea67188ba4bdd57d080f GIT binary patch literal 2645 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 V>bLwGUSxPcJp+}~kYlC&<9}1(MbiKP literal 0 HcmV?d00001 diff --git a/img/dvd.gif b/img/dvd.gif new file mode 100644 index 0000000000000000000000000000000000000000..f4ac26b72e453d3e6ab493fa572240187046efb3 GIT binary patch literal 1511 zcmVTGCYPeJ%}_vfkQx4G(L(pKzcPl zi#9-mH9&?oL5(;=gEvBtIYWs!Ly$Q{l1WEeIz^N_Mvp>CbUjFlJ4cl~NS8fHls!qA zPfcGwOPfGUlRr$EKun!RO^iTIo=i}2LQbGVP?}UyV?$4(Lr|kiQiw!RqC`=pMpK_x zRc2IIX+~3~Yg#5*S7=97s7O|&R#|UYT4zXBt4UX=TUu&KSglJ~s##ogOIfQ^Ta8Ou zuU=hjOj@o?TCr4Il}uZ+Olzf@(wRb{|cWx-Zu!D?xCRc63dX2Diw!GLWkR%gOi zXTw}+tygHoSZKvJ)b&_{?gKKrKl6@>`cf@*nhIf08n1CO4d!2oKiJ5~efPjmhg&%^0j-iMk zeS@i>i7bVNk)?|tsE!||jw^$Tv8|FHtCA~;jiZ5!#E+1hvX&lAJnrQqpQ`~wGpbV!PmAM+PE34ugTuJ4&AyI z;k^^&z6#~P53jc1=fMrOxWnnf3A4D=?ZgG~#{j;;*TKWf$jjQ#(&*LJ-r(Wr=IHS7 z@$>cd`2YX^A^8LV00000EC2ui0Qvwm000R80ObiBNU)&6g9sBUT*$DY!-o(fN}Ncs zqQ#3CGiuz(v7^V2AVZ2ANwTELga%ToT* NNGGlI(gpTGCYPeJ%}_vfkQx4G(L(pKzcPl zi#9-mH9&?oL5(;=gEvBtIYWs!Ly$Q{l1WEeIz^N_Mvp>CbUjFlJ4cl~NS8fHls!qA zPfcGwOPfGUlRr$EKun!RO^iTIo=i}2LQbGVP?}UyV?$4(Lr|kiQiw!RqC`=pMpK_x zRc2IIX+~3~Yg#5*S7=97s7O|&R#|UYT4zXBt4UX=TUu&KSglJ~s##ogOIfQ^Ta8Ou zuU=hjOj@o?TCr4Il}uZ+Olzf@(wRb{|cWx-Zu!D?xCRc63dX2Diw!GLWkR%gOi zXTw}+tygHoSZKvJ)b&_{?gKKrKl6@>`cf@*nhIf08n1CO4d!2oKiJ5~efPjmhg&%^0j-iMk zeS@i>i7bVNk)?|tsE!||jw^$Tv8|FHtCA~;jiZ5!#E+1hvX&lAJnrQqpQ`~wGpbV!PmAM+PE34ugTuJ4&AyI z;k^^&z6#~P53jc1=fMrOxWnnf3A4D=?ZgG~#{j;;*TKWf$jjQ#(&*LJ-r(Wr=IHS7 z@$>cd`2YX^A^8LV00000EC2ui0384t000Pl009UbNU)&6g9sBUT*$DY!-o(fN;GJ$ zV#R?L1!lB(ks?QP8arqC|(j6v-5)(WN+FV&rL4>dciSv0@cEHLKRDSGPL-dUT@MvuM+*U8^=A06U70 BiNOE> literal 0 HcmV?d00001 diff --git a/img/foldericon.gif b/img/foldericon.gif new file mode 100644 index 0000000000000000000000000000000000000000..782ef4a880cd8b57a01444c9b7231c186bd79044 GIT binary patch literal 982 zcmV;{11bDRNk%w1VI2S(0QUd@0000E5lRvjFc1??5*1Mt7*!STGCYPeJ%}_vfkQx4G(L(pKzcPl zi#9-mH9&?oL5(;=gEvBtIYWs!Ly$Q{l1WEeIz^N_Mvp>CbUjFlJ4cl~NS8fHls!qA zPfcGwOPfGUlRr$EKun!RO^iTIo=i}2LQbGVP?}UyV?$4(Lr|kiQiw!RqC`=pMpK_x zRc2IIX+~3~Yg#5*S7=97s7O|&R#|UYT4zXBt4UX=TUu&KSglJ~s##ogOIfQ^Ta8Ou zuU=hjOj@o?TCr4Il}uZ+Olzf@(wRb{|cWx-Zu!D?xCRc63dX2Diw!GLWkR%gOi zXTw}+tygHoSZKvJ)b&_{?gKKrKl6@>`cf@*nhIf08n1CO4d!2oKiJ5~efPjmhg&%^0j-iMk zeS@i>i7bVNk)?|tsE!||jw^$Tv8|FHtCA~;jiZ5!#E+1hvX&lAJnrQqpQ`~wGpbV!PmAM+PE34ugTuJ4&AyI z;k^^&z6#~P53jc1=fMrOxWnnf3A4D=?ZgG~#{j;;*TKWf$jjQ#(&*LJ-r(Wr=IHS7 z@$>cd`2YX^A^8LV00000EC2ui0384t000QH009UbNU)&6g9sBUT*$DY!-weL>60jt z-L8iO%jv7fv12`W7zfhxNU|QZUC;pRA_jn^OM~S`mgGp!9lMY2;OT_7uimtR?cmKE zT5?jsayp5Qlv(l=P@+&fI?WhWX}zff%T*mZ601P1TT6C58g{5x0AR~{YTGCYPeJ%}_vfkQx4G(L(pKzcPl zi#9-mH9&?oL5(;=gEvBtIYWs!Ly$Q{l1WEeIz^N_Mvp>CbUjFlJ4cl~NS8fHls!qA zPfcGwOPfGUlRr$EKun!RO^iTIo=i}2LQbGVP?}UyV?$4(Lr|kiQiw!RqC`=pMpK_x zRc2IIX+~3~Yg#5*S7=97s7O|&R#|UYT4zXBt4UX=TUu&KSglJ~s##ogOIfQ^Ta8Ou zuU=hjOj@o?TCr4Il}uZ+Olzf@(wRb{|cWx-Zu!D?xCRc63dX2Diw!GLWkR%gOi zXTw}+tygHoSZKvJ)b&_{?gKKrKl6@>`cf@*nhIf08n1CO4d!2oKiJ5~efPjmhg&%^0j-iMk zeS@i>i7bVNk)?|tsE!||jw^$Tv8|FHtCA~;jiZ5!#E+1hvX&lAJnrQqpQ`~wGpbV!PmAM+PE34ugTuJ4&AyI z;k^^&z6#~P53jc1=fMrOxWnnf3A4D=?ZgG~#{j;;*TKWf$jjQ#(&*LJ-r(Wr=IHS7 z@$>cd`2YX^EC2ui0384t000PY009UbNKl|Ta|IJBTzC-S!iNeQI)q3OfW(Ov8D4Cd zG2%vv9SMHC7;@l9kR>g0REe_WN|pyxniTm`X2F&TacYdY6XV00H+A~#NYdociW`j{ grI@s6$dfveLgo2%s?C!&X#&l0Ap-&cJLw6582|tP literal 0 HcmV?d00001 diff --git a/img/lib.jpg b/img/lib.jpg new file mode 100644 index 0000000000000000000000000000000000000000..5c4722fd4fe808f2cb678596c50157bdbdbb70f9 GIT binary patch literal 45425 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®g`h+M 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 OKtdV+$9Jp$Tl{~U9auI1 literal 0 HcmV?d00001 diff --git a/img/logontsc.jpg b/img/logontsc.jpg new file mode 100644 index 0000000000000000000000000000000000000000..735a3748f716b5af1cf18c5517f27f99bcdcbc02 GIT binary patch literal 71102 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 R#CQCgzu~MF@>ail|Jgu=P(T0x literal 0 HcmV?d00001 diff --git a/img/mediatop.gif b/img/mediatop.gif new file mode 100644 index 0000000000000000000000000000000000000000..30856a43d179b9b32869b4f1ca7ed220c7d81066 GIT binary patch literal 4341 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 WDzqZeq7sSH3`(=9)29Rj0suQs2X@f_ literal 0 HcmV?d00001 diff --git a/img/musictop.gif b/img/musictop.gif new file mode 100644 index 0000000000000000000000000000000000000000..74a7414cf968a68ed6f8e12ed14f2f2f9bfd8d67 GIT binary patch literal 3461 zcmV;04SMoNNk%w1VJrbg0QUd@0000E5lRvjFc1??5*1Mt7*!STGCYPeJ%}_vfkQx4G(L(pKzcPl zi#9-mH9&?oL5(;=gEvBtIYWs!Ly$Q{l1WEeIz^N_Mvp>CbUjFlJ4cl~NS8fHls!qA zPfcGwOPfGUlRr$EKun!RO^iTIo=i}2LQbGVP?}UyV?$4(Lr|kiQiw!RqC`=pMpK_x zRc2IIX+~3~Yg#5*S7=97s7O|&R#|UYT4zXBt4UX=TUu&KSglJ~s##ogOIfQ^Ta8Ou zuU=hjOj@o?TCr4Il}uZ+Olzf@(wRb{|cWx-Zu!D?xCRc63dX2Diw!GLWkR%gOi zXTw}+tygHoSZKvJ)b&_{?gKKrKl6@>`cf@*nhIf08n1CO4d!2oKiJ5~efPjmhg&%^0j-iMk zeS@i>i7bVNk)?|tsE!||jw^$Tv8|FHtCA~;jiZ5!#E+1hvX&lAJnrQqpQ`~wGpbV!PmAM+PE34ugTuJ4&AyI z;k^^&z6#~P53jc1=fMrOxWnnf3A4D=?ZgG~#{j;;*TKWf$jjQ#(&*LJ-r(Wr=IHS7 z@$>cd`2YX^A^8LV00000EC2ui04xDU000R8009UbNU)&6g9sBUT*$DY!-o(fN}Ncs zqQ#3CGiuz(v7^V2AVZ2ANwTELlPFWFT*7N}WozBN?Mfvue$1G=q#CPoq{HOSY`Qp;p-qP>zPcFgRi%3~px6O+2bsZ`GiS8BRxyCf zLqCi5>^Z~d#ghH#nKLKMjw7x#%bwkkbK23TJqu`@J9p^DP%Q7zX%vdCT(pxXx2;ul z;>Ep7H~vlfYUB(@GG&rc6qhRH;4!cN^$nUjXo;r#UME8ry29`ml6H?Et zLW+eb%CyyIrh(RCNLtBY7k2I)#YQw;D8UMaDRM*x3X%oc)Iu2;V1RzJVYnNM2!>Rj z03jBY24~lxG6NKxZ~|D57!_&cmq#Xq0+>c3iJ6l5r5V~tN+NXxR%E1sl_0`gA;S@d zKw*-XWPXXlTzrCQW5Wfm$I!bZ+}&;q??mIf{T^)?YylT6}ACK*Gt z?_qo)^+q#ST){~&Kz@4>#O@ZlX<59g3?5vnLKhJkZ&J2H22i-7OBg6*QpT3M-X%ku zGBiU-9I+I0v}hjnxhbz^F>HXt1Ns5QBeS0B&{YTIMHE;v=%R<72|Y>bR?ncKNi5Io z?6pP=|4BxnmsS;XihhLukqK`P5hYo@u8{JGA3!EWh8B8|B7=+v3I6euzTkoiF3&ub z_eLy(IZ&YuI20t1q${)+=|KjbE0R`YJ<#c+%UsZpF>k&S8KhWt-a|X6f{i)$;FFI& z`|z{RJ>Ag4S%^EJAOoo5ZuO>CvG`IwHgd}@k-`FX?s-F*x(?k{GadYlv;gn_Mvcu z?fRA%5kkHZzAPn8A;s<*QVL7-ffK6`Ml-Zw4szI$9L*3$4}D<{eDq@q>*xFP@p^iEUjE+2d)67zsS%pe`(?!&lm=2IC4dUxD1nh;^JqjDG&mL&md(K9{?y? zNs4Y$B}enaBqVB(N2J14&)8uz0+9Mw=_VUz(< zl)@ptXxk3`cUEnL^|EBO##x)OzdUjx4y$;^G736ZD9CB9kg|(5-k~9jX~e07JuE0Q zX^^7-8a1|`+*KB_#Z*hGjC{8%3Mo#+kpEq-a&o-JJIQL+Yc%7z{F|&=34zbdX$3o+ ziBmzlG7nTXQzNz=<(G!~*V|I#DbMv6-EH<_{y_B zBc+pV#VS@oL0n|X7sco)sZxfWIc!$|D2PXSJhDyks_eZ43GXFwd#i-(ZMN&>umQ4) zVUsjy4!YnG08}iHbl_s0{p(@=?C4zn^>4tdhy^N8VF>~Ypaq2r zqZI3;1q$lq#Bq}gPkadP8kT5=Dy|c*=tx{5melCyc3TkgRouzzz8Xet6;*d79UYufU#!%!wX}IY@L}g?bVV_{saUDL%TT5kB9%9{se)WUT&0hTFFBxX@mV3|rJ zk$xRV>(1-&E=0q9LV#kWmHZ8R7}w{N`lP!;#OrqD8Q7o;w4e*f06@_|XVZlXw6;c) z6nqqsakU|MpaMSyFmz5wB=Dmj8dQ1>!G2%zcVuyLbCm(epkRn+7pf2puHZAS5DaX# z3iz}%D03MzWD*kCL2lPQ@RBz7Q%K`;eaN_g>w-a;xY-tFoIbj2Dp$3kDv)}W-dGM21>AiEwW@> zQio%K7IS4}yKzh~m?cZoFHlf32V#eW7#PHqeMO-Oab|6C@CF=Wz-mI}g@rhYH-Q?8 zgKn5u9hA6Z8_vOityqh9NQ<_Z ni@Laryx5Dr_=~_8jKVmK#8`~Rc#O!HjLNu-%-D=25fA`7%tad- literal 0 HcmV?d00001 diff --git a/img/mute.gif b/img/mute.gif new file mode 100644 index 0000000000000000000000000000000000000000..072bb843d0996624a6d86ef94ffd769536f0711d GIT binary patch literal 1296 zcmV+r1@HPtNk%w1VSNBK0QUd@0000E5lRvjFc1??5*1Mt7*!STGCYPeJ%}_vfkQx4G(L(pKzcPl zi#9-mH9&?oL5(;=gEvBtIYWs!Ly$Q{l1WEeIz^N_Mvp>CbUjFlJ4cl~NS8fHls!qA zPfcGwOPfGUlRr$EKun!RO^iTIo=i}2LQbGVP?}UyV?$4(Lr|kiQiw!RqC`=pMpK_x zRc2IIX+~3~Yg#5*S7=97s7O|&R#|UYT4zXBt4UX=TUu&KSglJ~s##ogOIfQ^Ta8Ou zuU=hjOj@o?TCr4Il}uZ+Olzf@(wRb{|cWx-Zu!D?xCRc63dX2Diw!GLWkR%gOi zXTw}+tygHoSZKvJ)b&_{?gKKrKl6@>`cf@*nhIf08n1CO4d!2oKiJ5~efPjmhg&%^0j-iMk zeS@i>i7bVNk)?|tsE!||jw^$Tv8|FHtCA~;jiZ5!#E+1hvX&lAJnrQqpQ`~wGpbV!PmAM+PE34ugTuJ4&AyI z;k^^&z6#~P53jc1=fMrOxWnnf3A4D=?ZgG~#{j;;*TKWf$jjQ#(&*LJ-r(Wr=IHS7 z@$>cd`2YX^A^8LV00000EC2ui0DS;8000R80ObiBNU)&6g9sBUT*$DY!-o(fN}Ncs zqQ#3CGitjt} z6?}L7_V?Xn9)02UCSHI$amQIat%VmGfC$ngon-jL)0J23MVM1O9L9r<0jb@y4?gJ` zsF;R06`-PuoS~t~GaNQ}Ac{Ikw#796y9e$Vs!|MPsmKSzuM(maR(I0eWB ze4QVTI?E{<$;!LQDS9X*4{SiwF^Gj21?d`|hrr_@ zMlleh1bsxP-pNEW6DO!~5)_f5k4S}?B*9G549#K;Ow$d`GU3QnD+_mHi)bTrrm2%o&bIA%*?d}?2?NWonmY=~^qp)=ju0Nq%>#?|4Tek)@ z?h3&(813GO#<86}^Xxnt?L3+t@GNgH3c;J|fNzN)fqlKh9lcteh|NUbXdhCT6On`Q zZuKM4Fg{!tpLSnzoQp39?^opF%kw`=$C5g+q%H!v0_(?jC39WL{Gh;Sx3j(O{(W8n zO^KlpT)+S>um=~|9}@hdN8lhixYYC95QP%&89am!zKIVRB7}?(LPx#6u7DUSpivXN zD5G8!0g)oejMO8B-6DpKk*Qqo@Nu8;iAZ`fm44BOIvEmC>r1;G9hK%!?H~G+hqCx09#4 zn}wH+f@f+m{Fd1oRrun~!SSY59x^qwK9NVcJdUmF#?cOgqs}{UmFU>jaVh2s# zccprSLFg~oxX)f1$@n-~JdyB`uGULpY)9;f)E>1p~v0A3eM&PQPUTy%%76%h#?ZX zO5uaO?T)H9OQTtEm)_sQq`sSS1;c;k+vXk7Y;$?Cvz=MDfxGc6y8>lG+zg#Ar6{xGb8@9l+h*&@xStFlf=l^ z)n!nUBVn_SM`Mg14YES+*eN1kV=Lupjp@Dt=~e{OC+B*Y*7}n-`lFYGo~s8*qo~;O zsob3o*{A3{@g26=5f7W~cQ5ZYTkxXlbo-**A9rR0;lq|m?98x4V7M1{bG! ztlgbYTS8U|KU4CBb`6;EKeWqCS%)vQTah7u(dMWntgYl8JFx%T0iQOmlWW0+Jw2h& zOB2e7K5wMbde^5lcK3Gq;$9erer{s&q;P=^&!9@GBZNveh>nf6KJziE}&8amdQktZZiZ33)--#P#RL(02U&hT1a|~{P zRd%Af9@w%Jf1kaP_$IV1=8!>P<^bl|tqh{{rsl;h@tx}2*3!?qvCp-;yq5-tlHacp zXWOF1`CPyL2=P63K(xkUQho2USE(cC<2I7s$G=;j8X1vDKCR~BrTzroFY{M+{|5d$ jyH!gLe*%MYAW!qOE0a7>-_Cia=bs?|^MwDePuSwW5$?Us literal 0 HcmV?d00001 diff --git a/img/pal.act b/img/pal.act new file mode 100644 index 0000000000000000000000000000000000000000..679445f54e743f4a6908d4fae9f78f6c3baf90a2 GIT binary patch literal 768 zcmV+b1ONO0000gVN)igTNgX0% z9wcQTCR`sSXCNhJA}LZJCTSulVIe4LA}MSnDrYY&KqD${Br9(!FH9vYa3?QYCM{|u zEpaF?VJ9wgC@*y=FmNa^b}KVwDKU39G)O8kcq%h`D>Hj6GNk>{bMU*>6k3vawJxGc>N0mHCmpw_8JxQ5QOi;cMpLG1S|(Xn zXh&73NLHm*S#MZcXGm77Nmr;_T53sHtxH&{SzL5WS*uf9jZ0atUR`WVTCPl5u~b}@ zOk1-}T&+!9vteLwPF=N6Uba%ka-_$be(W@l6QB5 zYjv-ZeJp8r#Cm#$cYBVQfFE{yoqc_YnS(5VfQz1mAA*C9p@<-TgQ=j2EQN-VrHdb^ zjvuCuD}#!$t&$(Bk}HXgqk)UWkC2TGCYPeJ%}_vfkQx4G(L(pKzcPl zi#9-mH9&?oL5(;=gEvBtIYWs!Ly$Q{l1WEeIz^N_Mvp>CbUjFlJ4cl~NS8fHls!qA zPfcGwOPfGUlRr$EKun!RO^iTIo=i}2LQbGVP?}UyV?$4(Lr|kiQiw!RqC`=pMpK_x zRc2IIX+~3~Yg#5*S7=97s7O|&R#|UYT4zXBt4UX=TUu&KSglJ~s##ogOIfQ^Ta8Ou zuU=hjOj@o?TCr4Il}uZ+Olzf@(wRb{|cWx-Zu!D?xCRc63dX2Diw!GLWkR%gOi zXTw}+tygHoSZKvJ)b&_{?gKKrKl6@>`cf@*nhIf08n1CO4d!2oKiJ5~efPjmhg&%^0j-iMk zeS@i>i7bVNk)?|tsE!||jw^$Tv8|FHtCA~;jiZ5!#E+1hvX&lAJnrQqpQ`~wGpbV!PmAM+PE34ugTuJ4&AyI z;k^^&z6#~P53jc1=fMrOxWnnf3A4D=?ZgG~#{j;;*TKWf$jjQ#(&*LJ-r(Wr=IHS7 z@$>cd`2YX^A^8LV00000EC2ui0BHaq000R8009UbNU)&6g9sBUT*$DY!-o(fN}Nbh zVTOwsQD{UVgN%%hWI~D@Ns>&(lPD1ikb$wK%a<88f)t4&rA?axGUVLBljo5mGJt** zDud%j6fJAsTuL!#&X+r6ew-Q<3K=L^v9e^!HKNn60-RkTgYtbGIV6e5mzxY zq=BNetKEfPb%q72C5U6Qlv5=bCt=J z?9d2pyK-e)mG0UCD3alA^SAJ)Vf70Ccx20!s^rR%lX^}Y`%2r_v1OOIoqI~1_{E3M zz%m1r)vl0|#(>B?GT;%4$&e6y0x`Iod;qxe$U!0LlG{KJVsoKDWURJfL&EXbQh$1V zmWC^ykfDbb1wKLs3JES~pM$k&#^6Bp-I$|&5!!~0SNWY59#%)7u)`y+5SGC*P+WvU zlL|&RBX=44XkCM~4WkExWDFx+XtrsgN^QtI^c7{p1f~iKj~Fsy8ipVVSY{{`8RbDf z`Z(W%d)-)Lc2LxohCpf978U?w%9ov&KRR?~ng%)vBStd7`N$)xxB_VgI}|0OkI3k; zr+WZ!NouLsOb9@Z10jQ*K#$b_1|42{9qCGe7&YnNMU-8tN~W&NN!OhP844MOucF#$ zeeHdd}7%ss;VZsLw2mfIwgb+ zef5?qPCmt1w`pAZ?R2^fm_fPfB9yMO=GJDCq0lOvurBI0yU=$>TGmfT?79$>r^@J4afVK}{dD*Far^{WaHN9}CfU7rhLcD!QV^tIpH8 zqHAxXp)HqepB?eteyRCHd#P)4)7_Ze7s;Rn6orl0Q(Hap^wC9v4_-A)lvj>XFd>ChOQNT4`K+n8?)vMn$No@206QEW+XMgr literal 0 HcmV?d00001 diff --git a/img/photon.gif b/img/photon.gif new file mode 100644 index 0000000000000000000000000000000000000000..6fd9b7253bacfac1f93ad0c5196f34ab3b154012 GIT binary patch literal 1895 zcmV-t2blOrNk%w1VQBy$0QUd@0000E5lRvjFc1??5*1Mt7*!STGCYPeJ%}_vfkQx4G(L(pKzcPl zi#9-mH9&?oL5(;=gEvBtIYWs!Ly$Q{l1WEeIz^N_Mvp>CbUjFlJ4cl~NS8fHls!qA zPfcGwOPfGUlRr$EKun!RO^iTIo=i}2LQbGVP?}UyV?$4(Lr|kiQiw!RqC`=pMpK_x zRc2IIX+~3~Yg#5*S7=97s7O|&R#|UYT4zXBt4UX=TUu&KSglJ~s##ogOIfQ^Ta8Ou zuU=hjOj@o?TCr4Il}uZ+Olzf@(wRb{|cWx-Zu!D?xCRc63dX2Diw!GLWkR%gOi zXTw}+tygHoSZKvJ)b&_{?gKKrKl6@>`cf@*nhIf08n1CO4d!2oKiJ5~efPjmhg&%^0j-iMk zeS@i>i7bVNk)?|tsE!||jw^$Tv8|FHtCA~;jiZ5!#E+1hvX&lAJnrQqpQ`~wGpbV!PmAM+PE34ugTuJ4&AyI z;k^^&z6#~P53jc1=fMrOxWnnf3A4D=?ZgG~#{j;;*TKWf$jjQ#(&*LJ-r(Wr=IHS7 z@$>cd`2YX^A^8LV00000EC2ui0BHaq000R8009UbNU)&6g9sBUT*$DY!-o(fN}Nbh zAqp}W$@rPXaZ)K(r9RHeHH%fQlOs=(oW&A=009+a$}E^+4wQ^IbK>-~k>f{JKv_)! zAY;eOqcJm(5#T42Q>UItm695gYAKH$C-KDSCxfkSkJ8A1iL`9P41NS$)X3A%qmCit zhE(N>l`C3!X=%-Luiw6Vs>qrhj8JI?r7tt6Rf;X1xqJBf;nRoBQzljSplCTV?A5To z|M)FKLAdlm!-!uBSo%^IvUvIW>D!0x5t%Uj{AeK~E1152|7goFO`ITV)-RC@C^KxC zY<=;D;pI1MmM&fR`~@GDp}2PdD0ZO#Fsxc}d8NzpLAQl%SU+ULf|&!ZPgi$?!1ISz zHt_eq?+%f%!wyQ3A%l5}U6jEN0t{o%J@$}8jyC+f^UOT^{L@c2_u&WJJ@@1nk3jx) z7?Co-Aw|YT0_?EjVJM83!FV$0agRRtm_v>=+U#=;h5z`|&2R|ici0^HkqDfL522Xi zQDh({!x4!kNJc4SJo1}A_gpi~mz3S3oj#ciRpgTZBnd!?0Nm5fLAfZS4}S#t_Ya)| z$*E5+5wVDZAVp!MM@A@Ik>Nl0Jd?{Zw%{Y?Kl&i!M=1%ZndX!J(bA8l0s*7ne*!tl zX^5PjwG@j29A+L0rf8DRnC2}1lZhXZ1*wZElU9nRns(|pqColWvy7U+9EvCuzyhaB zJizty5Mmi1v&=sHKsQM@*!V$)DImeMyJzLHMfL-;&9K>STu>hWaj= zPR^lnS{cyNGDef2Fl#B;ka2Hl{A{|9ImO&~-6X7>393PCUOF~#AnTh+cOsj-E6`^# zjafWUE9J*9&g>wIcK&Gp_YXeUJf_c=euT|4ngx}8^gqU!eP1s5;YnZ6OeTavS^|*L z&C56)_J{ytfCijD=Cpj{mt+|3;n<18Z7CVX0tZEhCj)0D&G~Wo(B&_Y63;#HAUs}_ zY3-nkfBxvR`R|b;le*XlDUNsshwfuvL7V=AtUyQt7tdi2Vf50qn`6|?_ffy31*zV4 zV-EA(3{!AI;1^au0QV21YUTLnzkfs;c?nNAGz>q)gBIFSj8c#RoQS>ecOZ&c$^hgY z@iFCYnHYvDG>DP?%>fza&_ysdx4HI6kb)It&}uYtC0)2;RvGbw9t>d!G(h1EPkF>6 z^e_jEps+aFdP#Uhc5s9v;KW4&+JOvW#6%{_04F~rjs^EeMJmP!Cs_H&RVWoj(;OyM hL)?j0w6c{6b}@=bWT6_(c*Zum@r`hdBSZoM06V9|W8nY* literal 0 HcmV?d00001 diff --git a/img/picsicon.gif b/img/picsicon.gif new file mode 100644 index 0000000000000000000000000000000000000000..88d0c972e074913c34c780ba89bf37c40b423f24 GIT binary patch literal 930 zcmV;T16}+_Nk%w1VI2S(0QUd@0000E5lRvjFc1??5*1Mt7*!STGCYPeJ%}_vfkQx4G(L(pKzcPl zi#9-mH9&?oL5(;=gEvBtIYWs!Ly$Q{l1WEeIz^N_Mvp>CbUjFlJ4cl~NS8fHls!qA zPfcGwOPfGUlRr$EKun!RO^iTIo=i}2LQbGVP?}UyV?$4(Lr|kiQiw!RqC`=pMpK_x zRc2IIX+~3~Yg#5*S7=97s7O|&R#|UYT4zXBt4UX=TUu&KSglJ~s##ogOIfQ^Ta8Ou zuU=hjOj@o?TCr4Il}uZ+Olzf@(wRb{|cWx-Zu!D?xCRc63dX2Diw!GLWkR%gOi zXTw}+tygHoSZKvJ)b&_{?gKKrKl6@>`cf@*nhIf08n1CO4d!2oKiJ5~efPjmhg&%^0j-iMk zeS@i>i7bVNk)?|tsE!||jw^$Tv8|FHtCA~;jiZ5!#E+1hvX&lAJnrQqpQ`~wGpbV!PmAM+PE34ugTuJ4&AyI z;k^^&z6#~P53jc1=fMrOxWnnf3A4D=?ZgG~#{j;;*TKWf$jjQ#(&*LJ-r(Wr=IHS7 z@$>cd`2YX^EC2ui0384t000Pw009UbNU)&6g9sBUT*$DY!-oaqNt`J0;Y5KHGsbhc zv7^R}04^Tp3R6BO9SD__Kx}8h6?!kZn EI}Ia`&j0`b literal 0 HcmV?d00001 diff --git a/img/picstop.gif b/img/picstop.gif new file mode 100644 index 0000000000000000000000000000000000000000..717aaf5b2038d768280425458135cf65d9d3718a GIT binary patch literal 3657 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 bltfvSMtPJ-nUqSoluX%_PWhAp6A%D9v0R}U literal 0 HcmV?d00001 diff --git a/img/play-all.gif b/img/play-all.gif new file mode 100644 index 0000000000000000000000000000000000000000..97a11d1833ecb854366079c0d5647fc26c221e60 GIT binary patch literal 1284 zcmV+f1^fC(Nk%w1VPpUg0QUd@0000E5lRvjFc1??5*1Mt7*!STGCYPeJ%}_vfkQx4G(L(pKzcPl zi#9-mH9&?oL5(;=gEvBtIYWs!Ly$Q{l1WEeIz^N_Mvp>CbUjFlJ4cl~NS8fHls!qA zPfcGwOPfGUlRr$EKun!RO^iTIo=i}2LQbGVP?}UyV?$4(Lr|kiQiw!RqC`=pMpK_x zRc2IIX+~3~Yg#5*S7=97s7O|&R#|UYT4zXBt4UX=TUu&KSglJ~s##ogOIfQ^Ta8Ou zuU=hjOj@o?TCr4Il}uZ+Olzf@(wRb{|cWx-Zu!D?xCRc63dX2Diw!GLWkR%gOi zXTw}+tygHoSZKvJ)b&_{?gKKrKl6@>`cf@*nhIf08n1CO4d!2oKiJ5~efPjmhg&%^0j-iMk zeS@i>i7bVNk)?|tsE!||jw^$Tv8|FHtCA~;jiZ5!#E+1hvX&lAJnrQqpQ`~wGpbV!PmAM+PE34ugTuJ4&AyI z;k^^&z6#~P53jc1=fMrOxWnnf3A4D=?ZgG~#{j;;*TKWf$jjQ#(&*LJ-r(Wr=IHS7 z@$>cd`2YX^EC2ui0Av6U000R80O1K7NU)&6g9sBUT*$CtLI4mWN}NcsqQHt6Giuz( zv7<+i7e9&|NfINjfB&RJ~=&kwacFRRQcc!eQ^(x^n_+Ogfa4QLstJHdPBzhSZ&=aALdb)8YqLUI#BBA0B|&{Qi{1JI!(J-q6{&zgahCU;JD9B5HtPU;TkQr9cBS= zgnZLcmQgw=CHN06x4*|JWl@S9X*a8E$0N zWFUXo$Pk<^N7$4hR>uvw8FU&_cSL5g6nIvNYG%|TDiUe)k2X?1h+dVtov5EJ*?lGz u2Rvc}5t+yY34nz-Ro5n)N@k_ej2;~U7e{hcB$o`1W*QNsns&-jKma=-rY24R literal 0 HcmV?d00001 diff --git a/img/play-random.gif b/img/play-random.gif new file mode 100644 index 0000000000000000000000000000000000000000..eab5cc60af9730a606aaecf410c1e09cfe34ab2b GIT binary patch literal 1284 zcmV+f1^fC(Nk%w1VPpUg0QUd@0000E5lRvjFc1??5*1Mt7*!STGCYPeJ%}_vfkQx4G(L(pKzcPl zi#9-mH9&?oL5(;=gEvBtIYWs!Ly$Q{l1WEeIz^N_Mvp>CbUjFlJ4cl~NS8fHls!qA zPfcGwOPfGUlRr$EKun!RO^iTIo=i}2LQbGVP?}UyV?$4(Lr|kiQiw!RqC`=pMpK_x zRc2IIX+~3~Yg#5*S7=97s7O|&R#|UYT4zXBt4UX=TUu&KSglJ~s##ogOIfQ^Ta8Ou zuU=hjOj@o?TCr4Il}uZ+Olzf@(wRb{|cWx-Zu!D?xCRc63dX2Diw!GLWkR%gOi zXTw}+tygHoSZKvJ)b&_{?gKKrKl6@>`cf@*nhIf08n1CO4d!2oKiJ5~efPjmhg&%^0j-iMk zeS@i>i7bVNk)?|tsE!||jw^$Tv8|FHtCA~;jiZ5!#E+1hvX&lAJnrQqpQ`~wGpbV!PmAM+PE34ugTuJ4&AyI z;k^^&z6#~P53jc1=fMrOxWnnf3A4D=?ZgG~#{j;;*TKWf$jjQ#(&*LJ-r(Wr=IHS7 z@$>cd`2YX^EC2ui0Av6U000R80O1K7NU)&6g9sBUT*$CtLI4mWN}NcsqQHt6Giuz( zv7<+i7e9&|NfM(NY}jN(Jc7*@07KXeWeiyn-@ltT_1P&I@!7v;JUiM#DH9?~moST3 zgqP8#K79E2{p+VAs866lHOjz-jO)slFJsPRiqR&gi2DAW1%T?=vz-8>aFQbB87_&q zo^iSgQAb`}RRP%Hq+{>dy1oKyOgi+4QLstJHdPBziqxIyaQ?gN)8@@|5HsJD3e-+N zoc(q>#VPuqK8{^woHX?qsaT9ODSFln+A=->zW)98$*=HD!Bgp;1}fDT09^n00kCOL zM{u<|q^`vX1)DAa98hxU0B|&{Qi{1JI!(J-q7+>AgahCU;J8n15VifZ;~MQhJ7xjE ziG0&hmXRqaC5Vj}zRT*vKMLS38s!8E$0N zWFUY4ND-VqoY<5hR>uvw8FU&_$4O@P6nIvNYG%|T6cS~EO(s%4h+dVtov0r^*?lIJ u4m|z?5t;l134nz-Ro5n)N@k_ej2>|U7e{hcB$pJ9W*QNsns&-jKma>|e=s`$ literal 0 HcmV?d00001 diff --git a/img/player/angle.gif b/img/player/angle.gif new file mode 100644 index 0000000000000000000000000000000000000000..c9b742ddbcf9a8326dc1343336734bfd2d02415e GIT binary patch literal 1415 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 literal 0 HcmV?d00001 diff --git a/img/player/cancel.gif b/img/player/cancel.gif new file mode 100644 index 0000000000000000000000000000000000000000..de31e1f20437fde76ea316d7b77ca13a3b32a492 GIT binary patch literal 2175 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 G1OPh?Fmy%$ literal 0 HcmV?d00001 diff --git a/img/player/invalid.gif b/img/player/invalid.gif new file mode 100644 index 0000000000000000000000000000000000000000..2b79e0b55fb65eeeb3fbbf81251c947afc922a83 GIT binary patch literal 1317 zcmV+=1={*YNk%w1VSNBK0QUd@0000E5lRvjFc1??5*1Mt7*!STGCYPeJ%}_vfkQx4G(L(pKzcPl zi#9-mH9&?oL5(;=gEvBtIYWs!Ly$Q{l1WEeIz^N_Mvp>CbUjFlJ4cl~NS8fHls!qA zPfcGwOPfGUlRr$EKun!RO^iTIo=i}2LQbGVP?}UyV?$4(Lr|kiQiw!RqC`=pMpK_x zRc2IIX+~3~Yg#5*S7=97s7O|&R#|UYT4zXBt4UX=TUu&KSglJ~s##ogOIfQ^Ta8Ou zuU=hjOj@o?TCr4Il}uZ+Olzf@(wRb{|cWx-Zu!D?xCRc63dX2Diw!GLWkR%gOi zXTw}+tygHoSZKvJ)b&_{?gKKrKl6@>`cf@*nhIf08n1CO4d!2oKiJ5~efPjmhg&%^0j-iMk zeS@i>i7bVNk)?|tsE!||jw^$Tv8|FHtCA~;jiZ5!#E+1hvX&lAJnrQqpQ`~wGpbV!PmAM+PE34ugTuJ4&AyI z;k^^&z6#~P53jc1=fMrOxWnnf3A4D=?ZgG~#{j;;*TKWf$jjQ#(&*LJ-r(Wr=IHS7 z@$>cd`2YX^A^8LV00000EC2ui0DS;8000R80ObiBNU)&6g9sBUT*$DY!-o(fN}Ncs zqQ#3CGitmI>UE5Dng0fB% zT=BQI?%H@2;4+QFc3-?$ny}&Pw-(=6y+|Q+OY8668wDQ`%)qC%Ul+tj5pYV|FIZlX z2ngw0doKj$q9#WF!v+~NA%1poohFJZ?LHSGyQuzG%p6^O7-kE-_s?H3kxGcC#RkCz zY=4A=2ZhZ~Nk|j+)W$Py0PVk6(t*v-=`euCO zMAsL5pTCvY5d+1jwU2%u#p2$7JrTA~933GL3tj*0V;xB7{qr7!I_*=OY?{ILPd`dn zeK^g#blRObRkth*+0*;mg7wbWJsYuhM4G4 z0?*Lp4}wSHZqhLwb7}lrmE_ythVavtFT@I6c7MA-SJZO literal 0 HcmV?d00001 diff --git a/img/player/next.gif b/img/player/next.gif new file mode 100644 index 0000000000000000000000000000000000000000..033f6b4c7ad2f4423e22a5ca7eb422172f934f6e GIT binary patch literal 1300 zcmV+v1?&1pNk%w1VSNBK0QUd@0000E5lRvjFc1??5*1Mt7*!STGCYPeJ%}_vfkQx4G(L(pKzcPl zi#9-mH9&?oL5(;=gEvBtIYWs!Ly$Q{l1WEeIz^N_Mvp>CbUjFlJ4cl~NS8fHls!qA zPfcGwOPfGUlRr$EKun!RO^iTIo=i}2LQbGVP?}UyV?$4(Lr|kiQiw!RqC`=pMpK_x zRc2IIX+~3~Yg#5*S7=97s7O|&R#|UYT4zXBt4UX=TUu&KSglJ~s##ogOIfQ^Ta8Ou zuU=hjOj@o?TCr4Il}uZ+Olzf@(wRb{|cWx-Zu!D?xCRc63dX2Diw!GLWkR%gOi zXTw}+tygHoSZKvJ)b&_{?gKKrKl6@>`cf@*nhIf08n1CO4d!2oKiJ5~efPjmhg&%^0j-iMk zeS@i>i7bVNk)?|tsE!||jw^$Tv8|FHtCA~;jiZ5!#E+1hvX&lAJnrQqpQ`~wGpbV!PmAM+PE34ugTuJ4&AyI z;k^^&z6#~P53jc1=fMrOxWnnf3A4D=?ZgG~#{j;;*TKWf$jjQ#(&*LJ-r(Wr=IHS7 z@$>cd`2YX^A^8LV00000EC2ui0DS;8000R80ObiBNU)&6g9sBUT*$DY!-o(fN}Ncs zqQ#3CGitDRIBn*DVeP1|4q;?^mn~ zhReT!pA?Rp-@RZ;Dk!J^FJ7$d2BJ&xFUei_zGvOazwU=kiTLp&)yto68oo*To%ij- z&zE03e|KqSUwr)O^UM{v6xa4S|Xk%{!;hr;25ixNGOhTA={%wVFA9>NHc zXF6&!!vd0Z#!oPWm_VgTKR(#lJwrlJ+ewq*lS++7Zlh51OPkjdstrp literal 0 HcmV?d00001 diff --git a/img/player/pause.gif b/img/player/pause.gif new file mode 100644 index 0000000000000000000000000000000000000000..c2c88733154fd58108e19616597d84cf7483b4e6 GIT binary patch literal 1139 zcmV-(1dRJfNk%w1VSNBK0QUd@0000E5lRvjFc1??5*1Mt7*!STGCYPeJ%}_vfkQx4G(L(pKzcPl zi#9-mH9&?oL5(;=gEvBtIYWs!Ly$Q{l1WEeIz^N_Mvp>CbUjFlJ4cl~NS8fHls!qA zPfcGwOPfGUlRr$EKun!RO^iTIo=i}2LQbGVP?}UyV?$4(Lr|kiQiw!RqC`=pMpK_x zRc2IIX+~3~Yg#5*S7=97s7O|&R#|UYT4zXBt4UX=TUu&KSglJ~s##ogOIfQ^Ta8Ou zuU=hjOj@o?TCr4Il}uZ+Olzf@(wRb{|cWx-Zu!D?xCRc63dX2Diw!GLWkR%gOi zXTw}+tygHoSZKvJ)b&_{?gKKrKl6@>`cf@*nhIf08n1CO4d!2oKiJ5~efPjmhg&%^0j-iMk zeS@i>i7bVNk)?|tsE!||jw^$Tv8|FHtCA~;jiZ5!#E+1hvX&lAJnrQqpQ`~wGpbV!PmAM+PE34ugTuJ4&AyI z;k^^&z6#~P53jc1=fMrOxWnnf3A4D=?ZgG~#{j;;*TKWf$jjQ#(&*LJ-r(Wr=IHS7 z@$>cd`2YX^A^8LV00000EC2ui0DS;8000R80ObiBNU)&6g9sBUT*$DY!-o(fN}Ncs zqQ#3CGitPu^2EnBy5g|fvu*Q4FDc=G~{d$zCIuy+Lq-TTjQUBrMD58mteuh_tnc`~jH`Eq8T zgvD~+i}`cT&2%kmEp7T{XtaJiuWr2)b!^hKb-vCzI&p5Cwh!a}?V56G*;!c=j~V>& z@Z~gj^F96?y3FRPlUt8&{I>7y%=hk4yMn*~f-C#PgO{m~-@f&(BFXwCaBTGCYPeJ%}_vfkQx4G(L(pKzcPl zi#9-mH9&?oL5(;=gEvBtIYWs!Ly$Q{l1WEeIz^N_Mvp>CbUjFlJ4cl~NS8fHls!qA zPfcGwOPfGUlRr$EKun!RO^iTIo=i}2LQbGVP?}UyV?$4(Lr|kiQiw!RqC`=pMpK_x zRc2IIX+~3~Yg#5*S7=97s7O|&R#|UYT4zXBt4UX=TUu&KSglJ~s##ogOIfQ^Ta8Ou zuU=hjOj@o?TCr4Il}uZ+Olzf@(wRb{|cWx-Zu!D?xCRc63dX2Diw!GLWkR%gOi zXTw}+tygHoSZKvJ)b&_{?gKKrKl6@>`cf@*nhIf08n1CO4d!2oKiJ5~efPjmhg&%^0j-iMk zeS@i>i7bVNk)?|tsE!||jw^$Tv8|FHtCA~;jiZ5!#E+1hvX&lAJnrQqpQ`~wGpbV!PmAM+PE34ugTuJ4&AyI z;k^^&z6#~P53jc1=fMrOxWnnf3A4D=?ZgG~#{j;;*TKWf$jjQ#(&*LJ-r(Wr=IHS7 z@$>cd`2YX^A^8LV00000EC2ui0DS;8000R80ObiBNU)&6g9sBUT*$DY!-o(fN}Ncs zqQ#3CGithIq_e%<1@KrnV{KeKT4 z6^m1~?bEk#;r7FaYX)1pOY!E-%gCXDPb@|Jhrt8;?{pHgnw)V{1xXtRrc4dKYo4`%)!=`CQ zf#Wxm59SvSbZ_X$gP*+@Y=-gY*l_|=_qT6Zaq!_=Qznls&W!XwC+{9Cqm8haiS1;)o=c Icu_zAJ8^FYCjbBd literal 0 HcmV?d00001 diff --git a/img/player/popup.gif b/img/player/popup.gif new file mode 100644 index 0000000000000000000000000000000000000000..74098e0e09a212335521e4eb539b2e283b6ff177 GIT binary patch literal 2554 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 literal 0 HcmV?d00001 diff --git a/img/player/return.gif b/img/player/return.gif new file mode 100644 index 0000000000000000000000000000000000000000..e8e3e51dd97dda297161f9354eeffc744aa09652 GIT binary patch literal 1840 zcmV-02haFNNk%w1Vfp|y0QUd@0000E5lRvjFc1??5*1Mt7*!STGCYPeJ%}_vfkQx4G(L(pKzcPl zi#9-mH9&?oL5(;=gEvBtIYWs!Ly$Q{l1WEeIz^N_Mvp>CbUjFlJ4cl~NS8fHls!qA zPfcGwOPfGUlRr$EKun!RO^iTIo=i}2LQbGVP?}UyV?$4(Lr|kiQiw!RqC`=pMpK_x zRc2IIX+~3~Yg#5*S7=97s7O|&R#|UYT4zXBt4UX=TUu&KSglJ~s##ogOIfQ^Ta8Ou zuU=hjOj@o?TCr4Il}uZ+Olzf@(wRb{|cWx-Zu!D?xCRc63dX2Diw!GLWkR%gOi zXTw}+tygHoSZKvJ)b&_{?gKKrKl6@>`cf@*nhIf08n1CO4d!2oKiJ5~efPjmhg&%^0j-iMk zeS@i>i7bVNk)?|tsE!||jw^$Tv8|FHtCA~;jiZ5!#E+1hvX&lAJnrQqpQ`~wGpbV!PmAM+PE34ugTuJ4&AyI z;k^^&z6#~P53jc1=fMrOxWnnf3A4D=?ZgG~#{j;;*TKWf$jjQ#(&*LJ-r(Wr=IHS7 z@$>cd`2YX^A^8LV00000EC2ui0Qvwm000R80ObiBNU)&6g9sBUT*$DY!-o(fN}Ncs zqQ#3CGiuz(v7^V2AVZ2ANwTELga%ToT*Af}IW^1P|2U6DgBx@|$}3G5|7UBZ zt>vH5IR9*Wy6l$9i$7~gJU8Z)-GdAN58DO$zi!x}?@k$ed6(7HX~(|}9eK*~pw}C~ zFMj;j@K9WFMSz<016q3jU^AeA0S;)wN+tvtpgx}|xF7`9Rj1N(*U^Vfg8`b8A4}o= z)6aL|`3Dw%1rXpO0RvnC+G{E)7t4z{5>UWOExu?Yjyu9OAAB7q_gO4Gx+s8h8t!-6 zdi<&Q-+?R<0Gf10GMAHb&Ar57lLmZ=V3#Y!G9-AXorvaVQhF67OEZ8*T1v|4S5uuZ z)h1t&HTuR9HlLZp5`Unjavzm#Zk6InN>FHGkbNe27>zD*36q#CVVWhG22{{qEF^*k z=s)|c*%EAi(6Xs9^JCaPMM*qS~Iw2IrSpK+S$ znJH-+X>poBYH9(ambxggSg9J?KCcKMEuGLI`;VPOf=TPMsF@=J0WC$j>u0FEiS1S4 zz4D5q{|qz0OUiXCz`p}# z95C%E1rYH3{{{e)`~ui7lg1njC`11Dt6xhrCP0<2?_b}-pSRTal#oeoWGoSx1SKdF z2iBxT7K}*-Be+4Mi4SBSWS|H~NWv1D@PsH#p$b>X!WO#lg)oev3};Bg8rtxNI6UD= ecF4mX`tXN945APXhe*UC8u5roOrk*o0suQR6H`V2 literal 0 HcmV?d00001 diff --git a/img/player/rev.gif b/img/player/rev.gif new file mode 100644 index 0000000000000000000000000000000000000000..ad31780ba15318ef9e6280152292eb0cf6b34621 GIT binary patch literal 1252 zcmVTGCYPeJ%}_vfkQx4G(L(pKzcPl zi#9-mH9&?oL5(;=gEvBtIYWs!Ly$Q{l1WEeIz^N_Mvp>CbUjFlJ4cl~NS8fHls!qA zPfcGwOPfGUlRr$EKun!RO^iTIo=i}2LQbGVP?}UyV?$4(Lr|kiQiw!RqC`=pMpK_x zRc2IIX+~3~Yg#5*S7=97s7O|&R#|UYT4zXBt4UX=TUu&KSglJ~s##ogOIfQ^Ta8Ou zuU=hjOj@o?TCr4Il}uZ+Olzf@(wRb{|cWx-Zu!D?xCRc63dX2Diw!GLWkR%gOi zXTw}+tygHoSZKvJ)b&_{?gKKrKl6@>`cf@*nhIf08n1CO4d!2oKiJ5~efPjmhg&%^0j-iMk zeS@i>i7bVNk)?|tsE!||jw^$Tv8|FHtCA~;jiZ5!#E+1hvX&lAJnrQqpQ`~wGpbV!PmAM+PE34ugTuJ4&AyI z;k^^&z6#~P53jc1=fMrOxWnnf3A4D=?ZgG~#{j;;*TKWf$jjQ#(&*LJ-r(Wr=IHS7 z@$>cd`2YX^A^8LV00000EC2ui0DS;8000R80ObiBNU)&6g9sBUT*$DY!-o(fN}Ncs zqQ#3CGitOtkEwq$*JaJ+S%Pr*&n8Sh&0jRZ@)KzkmG7 zqBOzD-al+=eIj7H@9o>TC&A*n=NE6@oow~_^$VD!g2apO9?r=(GUE>;8Fazc*D^K) zlWWc>rr0uLxgHS^GUwK`y1vb0+*JF(Oxz%Aw3ZNm@ zT?`)i(hM!CXi@@7*kh!WE){TKj9+po=9pxbY37+|rm5zdY_{pTGCYPeJ%}_vfkQx4G(L(pKzcPl zi#9-mH9&?oL5(;=gEvBtIYWs!Ly$Q{l1WEeIz^N_Mvp>CbUjFlJ4cl~NS8fHls!qA zPfcGwOPfGUlRr$EKun!RO^iTIo=i}2LQbGVP?}UyV?$4(Lr|kiQiw!RqC`=pMpK_x zRc2IIX+~3~Yg#5*S7=97s7O|&R#|UYT4zXBt4UX=TUu&KSglJ~s##ogOIfQ^Ta8Ou zuU=hjOj@o?TCr4Il}uZ+Olzf@(wRb{|cWx-Zu!D?xCRc63dX2Diw!GLWkR%gOi zXTw}+tygHoSZKvJ)b&_{?gKKrKl6@>`cf@*nhIf08n1CO4d!2oKiJ5~efPjmhg&%^0j-iMk zeS@i>i7bVNk)?|tsE!||jw^$Tv8|FHtCA~;jiZ5!#E+1hvX&lAJnrQqpQ`~wGpbV!PmAM+PE34ugTuJ4&AyI z;k^^&z6#~P53jc1=fMrOxWnnf3A4D=?ZgG~#{j;;*TKWf$jjQ#(&*LJ-r(Wr=IHS7 z@$>cd`2YX^A^8LV00000EC2ui0DS;8000R80ObiBNU)&6g9sBUT*$DY!-o(fN}Ncs zqQ#3CGit2ncF@y z%n%Dc{@7_?PDQ%4-*gd%MBRpzm1KfBe&(i6Dh2vUrcHPCl@3ZxvdLL0gpk*dl_Qz3 zXQRNy5lp0FqG=Ost(~-{q0M#wWTsoLnG`}Qd^XBY1qJjFjFw_fN|TJzne-}1hk{C8 zq9zp(!k&GK30?siqUxQhG%2T=63K)lDW0aM=peUGm{a6gzvR8Rro7# z0h4qEHbH{?63HzedKkp^js)dq`m_v_(W1pXk^)ORW=@ST$@p*uzd1>@+V91uBytAy zK=6?&2^P=K7#BbQ*&P)@;5XlW|J|$-Xg~lsO@6zSI8`GZ&iLbyM=tr~lvi%~<(Ox# i`R1H=?)m4Shc3EF88Pi!`s%E=?)pUm0RTIq_VcL# literal 0 HcmV?d00001 diff --git a/img/player/rev32x.gif b/img/player/rev32x.gif new file mode 100644 index 0000000000000000000000000000000000000000..fbe45d99a48aefb14e85d0820f9b0944aa1f42f7 GIT binary patch literal 1661 zcmV-@27>uVNk%w1VSNBK0QUd@0000E5lRvjFc1??5*1Mt7*!STGCYPeJ%}_vfkQx4G(L(pKzcPl zi#9-mH9&?oL5(;=gEvBtIYWs!Ly$Q{l1WEeIz^N_Mvp>CbUjFlJ4cl~NS8fHls!qA zPfcGwOPfGUlRr$EKun!RO^iTIo=i}2LQbGVP?}UyV?$4(Lr|kiQiw!RqC`=pMpK_x zRc2IIX+~3~Yg#5*S7=97s7O|&R#|UYT4zXBt4UX=TUu&KSglJ~s##ogOIfQ^Ta8Ou zuU=hjOj@o?TCr4Il}uZ+Olzf@(wRb{|cWx-Zu!D?xCRc63dX2Diw!GLWkR%gOi zXTw}+tygHoSZKvJ)b&_{?gKKrKl6@>`cf@*nhIf08n1CO4d!2oKiJ5~efPjmhg&%^0j-iMk zeS@i>i7bVNk)?|tsE!||jw^$Tv8|FHtCA~;jiZ5!#E+1hvX&lAJnrQqpQ`~wGpbV!PmAM+PE34ugTuJ4&AyI z;k^^&z6#~P53jc1=fMrOxWnnf3A4D=?ZgG~#{j;;*TKWf$jjQ#(&*LJ-r(Wr=IHS7 z@$>cd`2YX^A^8LV00000EC2ui0DS;8000R80ObiBNU)&6g9sBUT*$DY!-o(fN}Ncs zqQ#3CGitQ@LC^jO8T4CwGD#QP2RQXf1U+#5m3*@R z@qhO^#?hM!WM#N$3ZT)v3?wSc1t4_)(<_>#l4ANm8z-sYe0spIZ>G{q$iVx31wh9B zci8~lz#xtHk3RVsP=IFrt#saV>ZukI16^RlAYhZ>wA?=5z4b;&1t^hichhv6-BTb)I~tJLTh;McK#%XGSdt08yw@UX4jNDa zA@R_-WQEP8vzZGf;iVUWCtdeXc_V!gi;_z+*-;3b*rQpQ|JZnw2^5}$BXkg;RNFo$ zVSoxgY;LyC3^T-nk3W6tMI}yFz6EAT^Xb)@N&o#5BuOTiQ|M;;q%z^9ZrY>-U?ZB8 z3N8Y2R#Jx0on(Tbm%127Fq`)Od8kdfy|o)jLl&^#KZ<#$S3g*y6hbP4Udm4e1@sUM znQV4S)0}!mhZ3A`p(4_!p{-Ov2!ayI=6VIJ7^`oy()8RtpH-WZWg*S5=AzTGTIP!e zARunDIPDro0TBY71&6r@=GqmY{0I)R=_ZqA1&8n)jul{%xOZi z$8}`p^a#_OMpp0~a36_U_bV=T8^9*pKO4|P#KERiSUgC-G$a9m8<;DH5TMb;kRs)( zd9IM-g!#{rQziQ7q?c~`>8Pi!`s%E=?)vMn$1eNqwAXICOdI8{`|iB=?)&e+2QU0Z H0RaFzp>+sz literal 0 HcmV?d00001 diff --git a/img/player/rev48x.gif b/img/player/rev48x.gif new file mode 100644 index 0000000000000000000000000000000000000000..5ed4a675f1ee89b7a2064c0baa21a9e04a923c77 GIT binary patch literal 1667 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 QEA6z@R%`9G7X<_WJ986m761SM literal 0 HcmV?d00001 diff --git a/img/player/slowfwd2x.gif b/img/player/slowfwd2x.gif new file mode 100644 index 0000000000000000000000000000000000000000..9192dcf58314ab90759a227387074ea658dd960f GIT binary patch literal 1722 zcmV;r21WTtNk%w1VSNBK0QUd@0000E5lRvjFc1??5*1Mt7*!STGCYPeJ%}_vfkQx4G(L(pKzcPl zi#9-mH9&?oL5(;=gEvBtIYWs!Ly$Q{l1WEeIz^N_Mvp>CbUjFlJ4cl~NS8fHls!qA zPfcGwOPfGUlRr$EKun!RO^iTIo=i}2LQbGVP?}UyV?$4(Lr|kiQiw!RqC`=pMpK_x zRc2IIX+~3~Yg#5*S7=97s7O|&R#|UYT4zXBt4UX=TUu&KSglJ~s##ogOIfQ^Ta8Ou zuU=hjOj@o?TCr4Il}uZ+Olzf@(wRb{|cWx-Zu!D?xCRc63dX2Diw!GLWkR%gOi zXTw}+tygHoSZKvJ)b&_{?gKKrKl6@>`cf@*nhIf08n1CO4d!2oKiJ5~efPjmhg&%^0j-iMk zeS@i>i7bVNk)?|tsE!||jw^$Tv8|FHtCA~;jiZ5!#E+1hvX&lAJnrQqpQ`~wGpbV!PmAM+PE34ugTuJ4&AyI z;k^^&z6#~P53jc1=fMrOxWnnf3A4D=?ZgG~#{j;;*TKWf$jjQ#(&*LJ-r(Wr=IHS7 z@$>cd`2YX^A^8LV00000EC2ui0DS;8000R80ObiBNU)&6g9sBUT*$DY!-o(fN}Ncs zqQ#3CGitMy7c0(;sB!AX1&NEThmZQ|O70f2G26o)yRq#TLZv^p z{_Z_<;Z(`qe{TwmjQ4LJ0o4X9ORh@@WQD!=JSJv~>vhV{lDl}Ep+v#aIH&)0fgN(+ zzjOpVI{&B71c5huG~KzNL{HAuz+XedGriu{NVEpCrXsX({9w&CEr`9!8SuT_w=)0M-(!rTV1hC zch83iN>*M+3(n@xKIs%ghzSVjv|Be2)U}*QmO&ume&PwZS&$!n7oTq_*0rJmEzVX~ zgM=x8fpIm(v5$TPSh!kiNOn{}0YSnComjJV)8%+7dbEs_Y`*BAVG_cW)qVx&D57-F z5g8JN1&##YWoM!UoRSK%X((tg7KYhNU#g~MNc8<s%4H$}UDSowO3mVW zs6Y6a6U-GJ9Q<0vFFpDZs&`_r*FF#o5N=1ZDPf?j{ls}v$7;^j&pz=4qsa^i@3+9U zG1Uuz}NiaCN`QVLGgYEG}@EO9&Hk<{`%~< Q@BaJn$1nf<7X<_WJGm|(vH$=8 literal 0 HcmV?d00001 diff --git a/img/player/slowfwd4x.gif b/img/player/slowfwd4x.gif new file mode 100644 index 0000000000000000000000000000000000000000..52ef37cb109abaf005142f1170e5ea08f8aa6c18 GIT binary patch literal 1712 zcmV;h22c4%Nk%w1VSNBK0QUd@0000E5lRvjFc1??5*1Mt7*!STGCYPeJ%}_vfkQx4G(L(pKzcPl zi#9-mH9&?oL5(;=gEvBtIYWs!Ly$Q{l1WEeIz^N_Mvp>CbUjFlJ4cl~NS8fHls!qA zPfcGwOPfGUlRr$EKun!RO^iTIo=i}2LQbGVP?}UyV?$4(Lr|kiQiw!RqC`=pMpK_x zRc2IIX+~3~Yg#5*S7=97s7O|&R#|UYT4zXBt4UX=TUu&KSglJ~s##ogOIfQ^Ta8Ou zuU=hjOj@o?TCr4Il}uZ+Olzf@(wRb{|cWx-Zu!D?xCRc63dX2Diw!GLWkR%gOi zXTw}+tygHoSZKvJ)b&_{?gKKrKl6@>`cf@*nhIf08n1CO4d!2oKiJ5~efPjmhg&%^0j-iMk zeS@i>i7bVNk)?|tsE!||jw^$Tv8|FHtCA~;jiZ5!#E+1hvX&lAJnrQqpQ`~wGpbV!PmAM+PE34ugTuJ4&AyI z;k^^&z6#~P53jc1=fMrOxWnnf3A4D=?ZgG~#{j;;*TKWf$jjQ#(&*LJ-r(Wr=IHS7 z@$>cd`2YX^A^8LV00000EC2ui0DS;8000R80ObiBNU)&6g9sBUT*$DY!-o(fN}Ncs zqQ#3CGit|iuKs7ZNCsI{k8RX@0kmy zO7gaSQ($DgfBOiO4!N&!u+<(bh`#5suw&IJH8=j(#p4Vm3XaAR-L?yCkgd}Z5E%>c!1CO?|6TDIOkcWKT}&kZ2#TR{_BSIjv<-IsBL?EPMYjH@&j91LsR%5{ zoPB%8XPfbZ&oi7TkyTH&>C+f`aE71j*|jTy&WTM#@qLtFPyhYN)UP`BF~xbe4}hahIxn1F#PfkALJ#j%ej)-7mS zb|6Dr;l-d|KR#|HSAK2Cel2fqstS(q?Vh(bm$d+Icx^ zru>+biV||B1Zqg9rS?%|`rr}_E*J(gmq$8EcWi%oF58$t0v2RCQ8V9_~mZ>>=REgn#@3O0oE;GuuEz}2IF%)6HI`O z?soKETUstRqg@-4+tD@uh>49RN_ar<0fGJ;6TBW(TWQe*+jzj1=JlhqM-fWKrNte^ z5>G5j1pINqK~4KnYxA0o|M>&{-|)FMa<4@;L_S30^61P2Ih z7|d1Qg9{-Ld=#k9N$vbm1#!Hg>q$jFXgWwnr}PZg9-IxbQUshGQtK%3%r!^_*e;UC zl8cl88Un literal 0 HcmV?d00001 diff --git a/img/player/slowfwd8x.gif b/img/player/slowfwd8x.gif new file mode 100644 index 0000000000000000000000000000000000000000..f2c00f4d3236a255673ccd277a6640e18f2cc599 GIT binary patch literal 1744 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 literal 0 HcmV?d00001 diff --git a/img/player/stop.gif b/img/player/stop.gif new file mode 100644 index 0000000000000000000000000000000000000000..9b014b69322bdc2fede5b97ffa28b49ea13d09a4 GIT binary patch literal 1082 zcmV-A1jYMDNk%w1VSNBK0QUd@0000E5lRvjFc1??5*1Mt7*!STGCYPeJ%}_vfkQx4G(L(pKzcPl zi#9-mH9&?oL5(;=gEvBtIYWs!Ly$Q{l1WEeIz^N_Mvp>CbUjFlJ4cl~NS8fHls!qA zPfcGwOPfGUlRr$EKun!RO^iTIo=i}2LQbGVP?}UyV?$4(Lr|kiQiw!RqC`=pMpK_x zRc2IIX+~3~Yg#5*S7=97s7O|&R#|UYT4zXBt4UX=TUu&KSglJ~s##ogOIfQ^Ta8Ou zuU=hjOj@o?TCr4Il}uZ+Olzf@(wRb{|cWx-Zu!D?xCRc63dX2Diw!GLWkR%gOi zXTw}+tygHoSZKvJ)b&_{?gKKrKl6@>`cf@*nhIf08n1CO4d!2oKiJ5~efPjmhg&%^0j-iMk zeS@i>i7bVNk)?|tsE!||jw^$Tv8|FHtCA~;jiZ5!#E+1hvX&lAJnrQqpQ`~wGpbV!PmAM+PE34ugTuJ4&AyI z;k^^&z6#~P53jc1=fMrOxWnnf3A4D=?ZgG~#{j;;*TKWf$jjQ#(&*LJ-r(Wr=IHS7 z@$>cd`2YX^A^8LV00000EC2ui0DS;8000R80ObiBNU)&6g9sBUT*$DY!-o(fN}Ncs zqQ#3CGit`F{{D#_OSWuTe_xM=HOtm4+O$R2 zww?R$E!?4U>9VzZH)vkHXZzv>+?KCkpMMF9HGG(7V#SOb>wHW&vSgf;0b9OoGqc{! zoN0Qd3p%vS(Q`|ihKaiH>dA`TzRuZt?`+Jm1K;kPo2=^HDr?IIP7|L#ea4d`U#`44 zahe4R5Qt8_x^?T%vuoeZy}S4C;KPd-A5Xr#`Sa*+Z(Ps5z5Dm@TGCYPeJ%}_vfkQx4G(L(pKzcPl zi#9-mH9&?oL5(;=gEvBtIYWs!Ly$Q{l1WEeIz^N_Mvp>CbUjFlJ4cl~NS8fHls!qA zPfcGwOPfGUlRr$EKun!RO^iTIo=i}2LQbGVP?}UyV?$4(Lr|kiQiw!RqC`=pMpK_x zRc2IIX+~3~Yg#5*S7=97s7O|&R#|UYT4zXBt4UX=TUu&KSglJ~s##ogOIfQ^Ta8Ou zuU=hjOj@o?TCr4Il}uZ+Olzf@(wRb{|cWx-Zu!D?xCRc63dX2Diw!GLWkR%gOi zXTw}+tygHoSZKvJ)b&_{?gKKrKl6@>`cf@*nhIf08n1CO4d!2oKiJ5~efPjmhg&%^0j-iMk zeS@i>i7bVNk)?|tsE!||jw^$Tv8|FHtCA~;jiZ5!#E+1hvX&lAJnrQqpQ`~wGpbV!PmAM+PE34ugTuJ4&AyI z;k^^&z6#~P53jc1=fMrOxWnnf3A4D=?ZgG~#{j;;*TKWf$jjQ#(&*LJ-r(Wr=IHS7 z@$>cd`2YX^A^8LV00000EC2ui0Qvwm000R80ObiBNU)&6g9sBUT*$DY!-o(fN}Ncs zqQ#3CGiuz(v7^V2AVZ2ANwTELga%ToT*&%!1spjfLma%cCb62J%ptf(X2Qu{2O?+^_44i)}$9h0k z@#nmkORp;6-t*%3Hc#IURlxP<+PRDWSK7e)cjCQOKC@>J)%f>77nnbvJAF#)|7^kE z-*Y~F*RrXEA1?eAn3I41i58v#_~~chgEN(v;A{37Fas1YFddUlv)lm%#k|c7YmsW z7EoS1KgKrON(15vVQ_q^SlX8?H7IDFZqd>teVMVyQUGrSuxObJl$Sv!-2nFJXo%@@ z1YCc*R6vQBmT98^8L(Q{tFYEi)oNP}EP!gNN}5SPro7hbYp)CJYAdd(HfF2=$0n=N zt;9C#?6c5DEA6z@R%`9G*k-Hkw%oRO(zoD-EAF`DmTT_0=%%agy6m=VP(T1XCg6cR literal 0 HcmV?d00001 diff --git a/img/player/subtitleoff.gif b/img/player/subtitleoff.gif new file mode 100644 index 0000000000000000000000000000000000000000..81462769084e328940665d9d2759a27774ad90bf GIT binary patch literal 2204 zcmV;N2xIq0Nk%w1Vfp|y0QUd@0000E5lRvjFc1??5*1Mt7*!STGCYPeJ%}_vfkQx4G(L(pKzcPl zi#9-mH9&?oL5(;=gEvBtIYWs!Ly$Q{l1WEeIz^N_Mvp>CbUjFlJ4cl~NS8fHls!qA zPfcGwOPfGUlRr$EKun!RO^iTIo=i}2LQbGVP?}UyV?$4(Lr|kiQiw!RqC`=pMpK_x zRc2IIX+~3~Yg#5*S7=97s7O|&R#|UYT4zXBt4UX=TUu&KSglJ~s##ogOIfQ^Ta8Ou zuU=hjOj@o?TCr4Il}uZ+Olzf@(wRb{|cWx-Zu!D?xCRc63dX2Diw!GLWkR%gOi zXTw}+tygHoSZKvJ)b&_{?gKKrKl6@>`cf@*nhIf08n1CO4d!2oKiJ5~efPjmhg&%^0j-iMk zeS@i>i7bVNk)?|tsE!||jw^$Tv8|FHtCA~;jiZ5!#E+1hvX&lAJnrQqpQ`~wGpbV!PmAM+PE34ugTuJ4&AyI z;k^^&z6#~P53jc1=fMrOxWnnf3A4D=?ZgG~#{j;;*TKWf$jjQ#(&*LJ-r(Wr=IHS7 z@$>cd`2YX^A^8LV00000EC2ui0Qvwm000R80ObiBNU)&6g9sBUT*$DY!-o(fN}Ncs zqQ#3CGiuz(v7^V2AVZ2ANwTELga%ToT*%r97nLq3WO8KQSw)@W5z3$0G_dOhGWNu4Il_0Vv2(PHn zx`b>`O2Fxo39&&YZ@k*hOV$KSV*8AQ=M9OQ!YU1@*fX)cL2tTuURiOo^|ou% z?LXA6RKSwj$!Rfy+mdzdZu{UO-=T)4L}8-zVM74M6w3*)0o?6;uS_b2SJS%h%IM;q z!tHF8pu+W2^1}TE+wgl%$Mo}yDF=M=)H!S1U`@KRd$F9t#S(DYUyo=<#wNh%q>ml|*zmz*xy9Vw^UCY6UlFkDu)i+5SgZzcz@V_nH#5{RD>~@_ z2S7;o=X~>wcfdX;A9rl4Mjx_F#cH1LKex*p%K+SWKfUt*Y~MZi-(zLZYfq=NKyE87 z8M)(;gTB(WjhmRf=UUhOY}YpN3tRds`PiQ1#ksMK#d79>+?}4G8`w2Pypsorb=Lqrj>Bo(n~SsVxvkJy;wSh3lQ7M}s3f26{P z6(nKans*#6no&Y3GNT$h7Lx@O%pRzSLn`bMn|>^Sc{CBDSO_qq-DsjEZhVg*y!M`b zlxuHc%-Ru!b+rHR2VpUR)O#ehLz&=DEDgZNa>E2I_XPqN=clwY)Ju1$IfvjQ&OaRr#A&a%$Cfv zo-A1>JKHIFvVHC|oi!CXo literal 0 HcmV?d00001 diff --git a/img/playlist-edit-off.gif b/img/playlist-edit-off.gif new file mode 100644 index 0000000000000000000000000000000000000000..04c216db2ef62b3fc18df62a0fe6c3da2e403058 GIT binary patch literal 990 zcmV<410noJNk%w1VI%+(0QUd@0000E5lRvjFc1??5*1Mt7*!STGCYPeJ%}_vfkQx4G(L(pKzcPl zi#9-mH9&?oL5(;=gEvBtIYWs!Ly$Q{l1WEeIz^N_Mvp>CbUjFlJ4cl~NS8fHls!qA zPfcGwOPfGUlRr$EKun!RO^iTIo=i}2LQbGVP?}UyV?$4(Lr|kiQiw!RqC`=pMpK_x zRc2IIX+~3~Yg#5*S7=97s7O|&R#|UYT4zXBt4UX=TUu&KSglJ~s##ogOIfQ^Ta8Ou zuU=hjOj@o?TCr4Il}uZ+Olzf@(wRb{|cWx-Zu!D?xCRc63dX2Diw!GLWkR%gOi zXTw}+tygHoSZKvJ)b&_{?gKKrKl6@>`cf@*nhIf08n1CO4d!2oKiJ5~efPjmhg&%^0j-iMk zeS@i>i7bVNk)?|tsE!||jw^$Tv8|FHtCA~;jiZ5!#E+1hvX&lAJnrQqpQ`~wGpbV!PmAM+PE34ugTuJ4&AyI z;k^^&z6#~P53jc1=fMrOxWnnf3A4D=?ZgG~#{j;;*TKWf$jjQ#(&*LJ-r(Wr=IHS7 z@$>cd`2YX^EC2ui03-kt000QX0O1K7NU)&6g9rgAT*$DY!-o(fN}NcsqQ#3CC;r>W zaic?58)X347;mAgOc6VF{1|fNzmggH{d1Kvq>P&)6{c}@1mr)FH!}dpv~gKZha6og zb(KaTK8`Yfx{N17B}A1Zhi=3-jH5`B8J_;zSMnjpcxdAd0}#(6fUZ)rR-IY%pTo9x z0r>p-62M%VS8d`1u&iabmU1@&HjDM=+j{pDw literal 0 HcmV?d00001 diff --git a/img/playlist-edit-on.gif b/img/playlist-edit-on.gif new file mode 100644 index 0000000000000000000000000000000000000000..b6caaee26b05c7ec3b2e1f5aca9849a9be5bb189 GIT binary patch literal 3872 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 literal 0 HcmV?d00001 diff --git a/img/playliston.gif b/img/playliston.gif new file mode 100644 index 0000000000000000000000000000000000000000..3b0c92fb47bfce4ffeeaa99d80399381cc544f96 GIT binary patch literal 1957 zcmV;W2U_??Nk%w1VQBy$0QUd@0000E5lRvjFc1??5*1Mt7*!STGCYPeJ%}_vfkQx4G(L(pKzcPl zi#9-mH9&?oL5(;=gEvBtIYWs!Ly$Q{l1WEeIz^N_Mvp>CbUjFlJ4cl~NS8fHls!qA zPfcGwOPfGUlRr$EKun!RO^iTIo=i}2LQbGVP?}UyV?$4(Lr|kiQiw!RqC`=pMpK_x zRc2IIX+~3~Yg#5*S7=97s7O|&R#|UYT4zXBt4UX=TUu&KSglJ~s##ogOIfQ^Ta8Ou zuU=hjOj@o?TCr4Il}uZ+Olzf@(wRb{|cWx-Zu!D?xCRc63dX2Diw!GLWkR%gOi zXTw}+tygHoSZKvJ)b&_{?gKKrKl6@>`cf@*nhIf08n1CO4d!2oKiJ5~efPjmhg&%^0j-iMk zeS@i>i7bVNk)?|tsE!||jw^$Tv8|FHtCA~;jiZ5!#E+1hvX&lAJnrQqpQ`~wGpbV!PmAM+PE34ugTuJ4&AyI z;k^^&z6#~P53jc1=fMrOxWnnf3A4D=?ZgG~#{j;;*TKWf$jjQ#(&*LJ-r(Wr=IHS7 z@$>cd`2YX^A^8LV00000EC2ui0BHaq000R8009UbNU)&6g9sBUT*$DY!-o(fN}Ncs zqQ#3CGb$7yAOpva203aRNiw9z0|#WVK^AY{u!1snl$>etcMH zn88-JM`>gTpviQhM*#;K;Jk@~4L+d#^!3~NubV&tneO%5cTd&Qrf9zcIDp~^lnfbU zK$&BT=hk@f?&0g_udhES0tAWu$8Q;mv`jk+knu>DIj#Nf-P5IvjAcLPj7|Mo|7M$sr*KhjpV~;&Qtp{XzKlw9Rd-nheK#0k$>W?QV z`ZtgOT9CqDSIqvS2LNyCbAYlf_M0q=f37u9X#3#m8cw10W!!n4VcQaB@f7pSJorVn z7gsI@8lI_w3=9CT8N_k{@#93XYtSHQ_R|`eRQl zxV))uOBN!xBtc5*0fkbo87hE@u6TkU%K}X&Uw_FG>|)JfXAlo^|8V^-VeU%lCeV>Z zs28fS>XXi9$(gecUt9hE^r*;B8)PU7K*=H{a%vOq&(>$nOg2F%*mv04{d_o^05kd( zZfE@H1r(4@n`4fr{q1AUa)KT-xT8D3t2O}k{iDi2jsumf4DqCKM2P-Wx9>g20O}DG z#Gw}<(dpU=A%6VmILsqQpio+U{aFscbpMnwo60>BbX{_j1MvMzG&d`LqS%1y>4zSL zNE|0}A}_PdhftXdBJ2fsN{9fN0o&f-JBRG5{PwQV%1bAOv#2 zZj?pTGCYPeJ%}_vfkQx4G(L(pKzcPl zi#9-mH9&?oL5(;=gEvBtIYWs!Ly$Q{l1WEeIz^N_Mvp>CbUjFlJ4cl~NS8fHls!qA zPfcGwOPfGUlRr$EKun!RO^iTIo=i}2LQbGVP?}UyV?$4(Lr|kiQiw!RqC`=pMpK_x zRc2IIX+~3~Yg#5*S7=97s7O|&R#|UYT4zXBt4UX=TUu&KSglJ~s##ogOIfQ^Ta8Ou zuU=hjOj@o?TCr4Il}uZ+Olzf@(wRb{|cWx-Zu!D?xCRc63dX2Diw!GLWkR%gOi zXTw}+tygHoSZKvJ)b&_{?gKKrKl6@>`cf@*nhIf08n1CO4d!2oKiJ5~efPjmhg&%^0j-iMk zeS@i>i7bVNk)?|tsE!||jw^$Tv8|FHtCA~;jiZ5!#E+1hvX&lAJnrQqpQ`~wGpbV!PmAM+PE34ugTuJ4&AyI z;k^^&z6#~P53jc1=fMrOxWnnf3A4D=?ZgG~#{j;;*TKWf$jjQ#(&*LJ-r(Wr=IHS7 z@$>cd`2YX^EC2ui04xDU000R8009UbNU)&6g9sBUT*$DY!-o(fN}NcsqQ#3CGiuz( zv7^V2AVZ2ANwTELlPFWFT*9R&0wp7$s@1C*q-x#z(rJdT8Lna_qa{-*u4vPOWa_7BSg}?!kV&JfTeo=k z^ywR>wy)obPUE6@TlFiMN2-P)i>Gg2KY#xs|J$eRujR`G$%G{gSAdL3sutth=eXbF zfBsUZj=T@E>#aox7$ zx9NZSj2R_Et-JT`Pk(a%|L#3}!SUs#Y9_OTGi}0~<<8AicVG12{Pe|R{M)*4fA^e3 z1~dc~XyAd+n3oVV2sG$mgU|%U;DZiEXwZNZCU~4z@YMzvScb_kmml-6+=OJ$bG9+dnl{SKU6{l(|=#)B#srjg#dvQZjRvN?|g| z_(0`i>3zWs zH>#=BIv6WKvp#sOPuW^)P`BQSL=Vx;=JcbFi=F1Z zo2VY~GAA!>JHI3J14*wV^*>opA@(h4{~-4bp^Jjuycyf6ulFo={4$L#eiz}f7cMNM z!W-h8u+GXKFm|j=p7}wjbCBu+*dW!Ni*yJaow>-6Km;ag z@!^jDfH)8#>WGLJx`h&rctj=oh=~+JB0-?|ASqI@K~faR6%mld%q;PTPMo5_s<;qz z2>=Dlcn;ofD4o0D10OL8o9`Ayzhl|4O?wND*BJA->+}Oo9NA(QwfI9Z4zX=p1fzM# zAjl{F&^&@PNF)(CMk|idL4!PxRa*BSOYO~T_ZXHZ<(Ha!*rIrzETJ7|qz`k1(q30< z(?9spzm3#lPPJ&;Eq9d5f$UO8y)4KtFNuI!m}i(23Z_8FfJ_G|69CO@&@-WlOI?o9 zm;{NYsit|5x1eAc&sn8)V3;p|^&=jtSOzPx(KyFF%W%yS(<8b;n!MaFTK@=;Evsq& z5N#GSoBR|eF&~7@Woq-8{zPa%eYsGBUQ(gNjNe&~hm=i266w@8S!aOzA(M)MNPzUW)AUUPbO$idz2Mv{| zGBrq1eR`gyn(C)O9Vk>2f}C%Lvz#srsjnDyO5wR^rMSaK{_Z2ceGJ1CC4&^z-U+i% zX~d}rfGQba0oS+|1g=vZNKWa>SH6}Cs&xfyQtvugq}r6QGVLo?BM%~hu1 zx$R+>=~T$-wXnb4ZE$s(TfW*!wiJ1b49KU^nAvMTuH;8Na^sJ!?T4hY(@Xxc)*H^Y zGk(W{+&*ZLkyG?kN2gGbd3SV*bEWq@r-*NS=hWVTtQWmcxi5w4TM+#|h`;uwZ-43g z!2z?EBFGUy>&j2aMq-ho_Lu%Q21&@@!$&a&OH@kcA3tpAd2lFiA-=4nL!00Wb zeCJy&0F$A_4XQ7U5dh!-9~i~^#qoaQTVMdpxW*AVKvkC8$&6MPtYTHAIghuy`C+5U zq?I2(w)WYz@=iGC#K?@tcw!wp2*?9@@s>+f;~xtI#$6_enY|oiHt+cVgKI``VIH9m z-juUnsdO3srtHySSmz#|KxK1j^g??*_+H#C?J_PhW>fJPA#sN9d1)+Z{J!|c28r~U z1)P~i$2iSy2D8SjvICGZ&7JO?5kBssj}@7NG5Wv;GNfa}QR<6P(?!NI#46$N;&UIf zu*f8cIf-IV!gRyl(IncI?DHysIc^+CNt$G9W_B8TQypkqgl;T zZO9;por-Ln;1{=esSciVol6_y3AwpIgx+n2U)x7Smk7Etpo%(E%3rD6hdJoU(PYWU zG+QH_X#fW{s9UG%PMf)EJjU9GB;D*iUuMA(4uGT^UE1a%`q|YU@tqrF?httk6g>X) z*wN^C_IVl9q?4uHk@Zu)f>}mCrnME(qQ?gD!<^bu5y|g}@&mEFj{Kk?%~J^Tnen`- zKp*+Zm;UnM^MmLgM0x{_`mOc)Mtd$@vt*n$y!2w zyw4o8`0BdfXrIvz-(A0>^6_Qy2q^4827`eDA|espHxUKsdXp!90C0V!5`7FvE#b#~ z>q7qN6rXIc7UVfrv-q68W4vTiDf47$L7 zX}})W_uj|Azv=2^L5gn<8;W)IvAJsWF5x2PP5^(RhPU(26~% zkTmeV>X%Z-KoC1)YqsEPTpao%xnE}9whl8Snueg)mc@i05Dq*pn58)xI$cjfm zlabk!@VOH0IVbO#pHV4ff>NIb8Jy|qpDs}wJJO#9$`G3|C<`eY3px`GKp_p#0Noj( z7-SggiJ>$xp$$@@7s{a+w3;GH5(01lC>o+Ax}q%FqAvQPFdCyWI-@jNqc(b@IGUq6 Kx}zQu5CA)i)@c*~ literal 0 HcmV?d00001 diff --git a/img/playoff.gif b/img/playoff.gif new file mode 100644 index 0000000000000000000000000000000000000000..21a57a229d5349141418debac86062b5fe525de9 GIT binary patch literal 1578 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 literal 0 HcmV?d00001 diff --git a/img/screensaver.gif b/img/screensaver.gif new file mode 100644 index 0000000000000000000000000000000000000000..2048b3c8cfd6d21a6b629a8676978e08656bb8b8 GIT binary patch literal 7833 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 QqQHt1F>2Jf@L)gyJ8U^z?f?J) literal 0 HcmV?d00001 diff --git a/img/scrollon.gif b/img/scrollon.gif new file mode 100644 index 0000000000000000000000000000000000000000..6d7b6fc6e098565fab207dd27e56f804c944bf59 GIT binary patch literal 2127 zcmV-V2(b4@Nk%w1Vfp|y0QUd@0000E5lRvjFc1??5*1Mt7*!STGCYPeJ%}_vfkQx4G(L(pKzcPl zi#9-mH9&?oL5(;=gEvBtIYWs!Ly$Q{l1WEeIz^N_Mvp>CbUjFlJ4cl~NS8fHls!qA zPfcGwOPfGUlRr$EKun!RO^iTIo=i}2LQbGVP?}UyV?$4(Lr|kiQiw!RqC`=pMpK_x zRc2IIX+~3~Yg#5*S7=97s7O|&R#|UYT4zXBt4UX=TUu&KSglJ~s##ogOIfQ^Ta8Ou zuU=hjOj@o?TCr4Il}uZ+Olzf@(wRb{|cWx-Zu!D?xCRc63dX2Diw!GLWkR%gOi zXTw}+tygHoSZKvJ)b&_{?gKKrKl6@>`cf@*nhIf08n1CO4d!2oKiJ5~efPjmhg&%^0j-iMk zeS@i>i7bVNk)?|tsE!||jw^$Tv8|FHtCA~;jiZ5!#E+1hvX&lAJnrQqpQ`~wGpbV!PmAM+PE34ugTuJ4&AyI z;k^^&z6#~P53jc1=fMrOxWnnf3A4D=?ZgG~#{j;;*TKWf$jjQ#(&*LJ-r(Wr=IHS7 z@$>cd`2YX^A^8LV00000EC2ui0Qvwm000R80ObiBNU)&6g9sBUT*$DY!-o(fN}Ncs zqQ#3CGiuz(v7^V2AVZ2ANwTELga%ToT*duPC`5T~$V!!`lqiX&4=VZk#U$AJt~G2@t$m9&&h8Lv!2 z`~Ha*-4B;cT>VCiw%1u@9M!P@+0zUYHSB$aVBQmLdgktby?xqNjaxTqzbk(S_Y9Lz z?9ts|c6AMKTBsE_W~TIF==(Ov%kwncON&+pH}xBLy9rFrHtLu)9$SG(bd@sEw8a5mVaKJ00rPI<3+i4?|17Jg1 zOV5zT!2kvnKmZ3VYPiyVqFG76mjhfe8ht6DC&K_s7BGMe(5?4ij&JH@CYnsL*`J%+ zHAtY5E!iSkek)yZ4>qr~)Z!}ztW>}-)nQZOXe|*i8X7FMh)tr3VptuX}W1~?Zd0RaU6K;=qGjAlYhCcGg)04#C&&x@j6XF#o@b!UJHqX}!k zo^ZCe6R|eBglw?x-8NvHtQs&ujr}}BW=pR2Gb*VBxMbq5wCY3JxUT*)E~zc%mfWWW z{sU@G?Y^5*0r6J*kCUR2xGcTduB0h(xHNd=Klj|^t9HZGCtAZVG3%hS%_e9QfE0^| z?QgLFywAlN^V#Hc{m8qLpA6r0E_M1Wz!J+HbL#KC#@Wjg%{k*do(H3mxN5cmKw-{v z%5v|4nKYUC|K#A7=$+miOS*FBbOa5VNnMI2IL4-n$X9(##l4E+T|8P1RT2tWoYI)DH` z87T{?s6wpV*N2|gl35Bsjw~sFDOGfZUFl0DrnIFnX$j0>3iFpR(WL-*=}VXdQ;{hl zz$~FT!F_!RnJ*#0lF*0DG-0z!U()7Gyh+Y&=CYjL)QOkG$xWA}shaSNr#$CL&wASP zp7_kCKKIEi&wl#zp8yT0KnF_Df*SOo2%XDG7Ru0uI`p9sji^K?O3{j1^r9Hes6he( F06W>}=nMb= literal 0 HcmV?d00001 diff --git a/img/selmovieicon.gif b/img/selmovieicon.gif new file mode 100644 index 0000000000000000000000000000000000000000..af0b3d55660f1f65fbad5c2bbc85a90e41a0f964 GIT binary patch literal 1037 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 X?#Ds#<)%4NU^nU2vuodeFdzUsp^DW( literal 0 HcmV?d00001 diff --git a/img/selsubticon.gif b/img/selsubticon.gif new file mode 100644 index 0000000000000000000000000000000000000000..f0708b2a2b42a47b9a6aa4dc68a9eb3643466bc8 GIT binary patch literal 1080 zcmV-81jqYFNk%w1VI2S(0QUd@0000E5lRvjFc1??5*1Mt7*!STGCYPeJ%}_vfkQx4G(L(pKzcPl zi#9-mH9&?oL5(;=gEvBtIYWs!Ly$Q{l1WEeIz^N_Mvp>CbUjFlJ4cl~NS8fHls!qA zPfcGwOPfGUlRr$EKun!RO^iTIo=i}2LQbGVP?}UyV?$4(Lr|kiQiw!RqC`=pMpK_x zRc2IIX+~3~Yg#5*S7=97s7O|&R#|UYT4zXBt4UX=TUu&KSglJ~s##ogOIfQ^Ta8Ou zuU=hjOj@o?TCr4Il}uZ+Olzf@(wRb{|cWx-Zu!D?xCRc63dX2Diw!GLWkR%gOi zXTw}+tygHoSZKvJ)b&_{?gKKrKl6@>`cf@*nhIf08n1CO4d!2oKiJ5~efPjmhg&%^0j-iMk zeS@i>i7bVNk)?|tsE!||jw^$Tv8|FHtCA~;jiZ5!#E+1hvX&lAJnrQqpQ`~wGpbV!PmAM+PE34ugTuJ4&AyI z;k^^&z6#~P53jc1=fMrOxWnnf3A4D=?ZgG~#{j;;*TKWf$jjQ#(&*LJ-r(Wr=IHS7 z@$>cd`2YX^A^8LV00000EC2ui0384t000R8009UbNU)&6g9sBUG-#`#!-o(X>aqnu z0K0$a3Qp9;v7^V1wl)Gl_wS#+f*}_Y0G5{_R7lYo@?>@%s7u z=dT~ikP(vtjaSm8MqBpmeFQmiY0HvDk2X!%O<%uzS4+YiII-zJb_TO~^=B{aOP+1p z;)}P@mbrLf!P;xe&)~#+aofggh_|iYmj3P?42iXE$#eVufo%$wUpIthO9D14d2h+6 z%Myl+3%GP%()P?L{ENDD>vR-F>$=E!bJN5e725J;k+b8*02;1t=&P{n(&p;wu4|k$ yY^1Rf$`q(ibIYA%qc84Ba34Rw`FQFTMhdc9JaZ3~C#*X$BzyAHeaa^o5CA*N8Q`e^ literal 0 HcmV?d00001 diff --git a/img/setup1.jpg b/img/setup1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..b39876718f2e4887f7744f34a0c2730dfcb2c1f4 GIT binary patch literal 72317 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℘h{d(NaqrPid#s*s+ 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 literal 0 HcmV?d00001 diff --git a/img/setup2.jpg b/img/setup2.jpg new file mode 100644 index 0000000000000000000000000000000000000000..002cbeb5b45806c5e49372d07ee19ee49fffc8f8 GIT binary patch literal 38925 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 literal 0 HcmV?d00001 diff --git a/img/ssaver.act b/img/ssaver.act new file mode 100644 index 0000000000000000000000000000000000000000..604eceeb6343fc9e43a8990879c328d309ea73c9 GIT binary patch literal 772 zcmWlV{Yz7E6vpqfyL)!;cQfytxn+u)fyIhuL`IPWg+`QRNl}zhnVDooMG4ZI>xG2K z3Zuwa&cbMHKU6D=jMNkhwHG73$#QEiZr}f*?DpYsetLM$^Bh4C;^X{NGk1Yk6!Gv> zymHGMLEwNsNr*`x0mOg^@C*0}OaMQCG2k;W3=9DSKrhe(yarwZ&wv2n1G<1t;33cs z+y&Zzo4|G88gK<@Hi-I(^FR~O2%H8^0mp$OKs`_g90c|O)xa)byC^0^QID||*a(yv zjY*^ND^LoQNRpn)MZ}48!2+UjqK`x$B*ay!vUqT`JDiH;B*CfW~JRclI? zPZRA>)rs`)QgBV+w#h7;k>*J<>lU@q8-J>4W*@5MUnFJSFc)itg5QT z<=R|Z+v#+!Te)(V+uiN;hHu|~Ur}+u?e=zb^fxq|ef4Ujsi`Fp==<^|+S@y(X^G+C z@z&P69#2;&6w|cu_wU-fcYRNv2BOjFV6Z!x&*2PCOo;nwpxNoYXXpN4OveGcz;#{?A|G&p&Eq4NL$4 literal 0 HcmV?d00001 diff --git a/img/subticon.gif b/img/subticon.gif new file mode 100644 index 0000000000000000000000000000000000000000..a3c01420fcea3cf3541aa22dfc09805bd2874a72 GIT binary patch literal 917 zcmV;G18V$7Nk%w1VI2S(0QUd@0000E5lRvjFc1??5*1Mt7*!STGCYPeJ%}_vfkQx4G(L(pKzcPl zi#9-mH9&?oL5(;=gEvBtIYWs!Ly$Q{l1WEeIz^N_Mvp>CbUjFlJ4cl~NS8fHls!qA zPfcGwOPfGUlRr$EKun!RO^iTIo=i}2LQbGVP?}UyV?$4(Lr|kiQiw!RqC`=pMpK_x zRc2IIX+~3~Yg#5*S7=97s7O|&R#|UYT4zXBt4UX=TUu&KSglJ~s##ogOIfQ^Ta8Ou zuU=hjOj@o?TCr4Il}uZ+Olzf@(wRb{|cWx-Zu!D?xCRc63dX2Diw!GLWkR%gOi zXTw}+tygHoSZKvJ)b&_{?gKKrKl6@>`cf@*nhIf08n1CO4d!2oKiJ5~efPjmhg&%^0j-iMk zeS@i>i7bVNk)?|tsE!||jw^$Tv8|FHtCA~;jiZ5!#E+1hvX&lAJnrQqpQ`~wGpbV!PmAM+PE34ugTuJ4&AyI z;k^^&z6#~P53jc1=fMrOxWnnf3A4D=?ZgG~#{j;;*TKWf$jjQ#(&*LJ-r(Wr=IHS7 z@$>cd`2YX^A^8LV00000EC2ui0384t000Pb009UbNU)&6g9sBUG-$4&!-o(bDvU@m z;zV-`2{yc#ap1;;A3KITSh3_th9)a=>}hl<)2Aqp3jHZ{YD=e7Q6|-h)oN9*Us;Mh7!UwEwZnl0 literal 0 HcmV?d00001 diff --git a/img/subtoff.gif b/img/subtoff.gif new file mode 100644 index 0000000000000000000000000000000000000000..d9e0608623b046a18ccbf2d3e9fdc99ff65bf0dd GIT binary patch literal 1612 zcmV-S2DAA`Nk%w1VQBy$0QUd@0000E5lRvjFc1??5*1Mt7*!STGCYPeJ%}_vfkQx4G(L(pKzcPl zi#9-mH9&?oL5(;=gEvBtIYWs!Ly$Q{l1WEeIz^N_Mvp>CbUjFlJ4cl~NS8fHls!qA zPfcGwOPfGUlRr$EKun!RO^iTIo=i}2LQbGVP?}UyV?$4(Lr|kiQiw!RqC`=pMpK_x zRc2IIX+~3~Yg#5*S7=97s7O|&R#|UYT4zXBt4UX=TUu&KSglJ~s##ogOIfQ^Ta8Ou zuU=hjOj@o?TCr4Il}uZ+Olzf@(wRb{|cWx-Zu!D?xCRc63dX2Diw!GLWkR%gOi zXTw}+tygHoSZKvJ)b&_{?gKKrKl6@>`cf@*nhIf08n1CO4d!2oKiJ5~efPjmhg&%^0j-iMk zeS@i>i7bVNk)?|tsE!||jw^$Tv8|FHtCA~;jiZ5!#E+1hvX&lAJnrQqpQ`~wGpbV!PmAM+PE34ugTuJ4&AyI z;k^^&z6#~P53jc1=fMrOxWnnf3A4D=?ZgG~#{j;;*TKWf$jjQ#(&*LJ-r(Wr=IHS7 z@$>cd`2YX^A^8LV00000EC2ui0BHaq000R8009UbNU)&6g9sBUT*$DY!-o(fN}Ncs zqQ#3CF=p7vv7^V2AVX#xDbdpo0ccd6%z#pr3;``)#ymh1jjNX#-rN+BlcZ0FDh0fe zwNj|7lR{<5)T2}Y%{R1O9uO3jr=g!%6{%Csu{aG?&2TlV+k$9b z9(jZFz+4nGp}fI+Kri3EdQkB6JNT_(flgV%%y2+);{z}oJKhL6ab?K@5FXKm4V$hs z5RYKfl28CC&=*6hUak6|tj@5p$$Cf%8>xh%v1LfjSPUsoq<(t>9z3{AG{2J*2hLpZ z?t(Hp-xly1n+%7wZ)@)SydveHu3V}A(gFpFpzz^k>U2fEeEB)1!n@>YF!%EeosSI6 zz;)me{Q_ktz<<{fltzA;dB>Pd;R)iN7OJ49UU=l`Azp>@;Z$E|vOM=6LC9e9h(Ng| zSX+p&>5||hu_0p-WekpI9(m`b2h(~O%J@iz^c5sPY@c-}kaQ$+7a(j}R5v03GH@mv zX$l^|po37T;vj@J8pWP_?`6WHkORqpB5c|XXcy_Sqvr19qpVi)Lzf zC;+w1hNg#&QUs%nc)DT=ojYm&*`9l8c&RCvq+$cAnb-_e=%}^zDv+Y6E~;Qs=p~CD zQ82ZtY_jv=swjZ?dFHFG0y+6yu+O!5DyO(EL>Ux@2x9KJ=Ne)NU*rl>Zn^2Y8`!1; z{n%L=TA&CZ08TyBqYOs{rULcZXY7X)qqD7$SKZY=IoFm zFJ0jYP6CLLiA-WF4O7&A`Q{uNo?LyHVOw>@Q&M3uD*#$fowZh3R&D)O*F$*)_Kjkr zO%vG}pv{)rZV8g~BTQc@!M9U>3wD4Qk5rvCPfj~02Hsh*=J?}`M=tr~lvi%~<(Ox# K`R1Gl1OPjc?9IXe literal 0 HcmV?d00001 diff --git a/img/subton.gif b/img/subton.gif new file mode 100644 index 0000000000000000000000000000000000000000..610dc4c5fb784eb0f669efb2e6123991e17eeab2 GIT binary patch literal 1809 zcmV+s2k!VsNk%w1VQBy$0QUd@0000E5lRvjFc1??5*1Mt7*!STGCYPeJ%}_vfkQx4G(L(pKzcPl zi#9-mH9&?oL5(;=gEvBtIYWs!Ly$Q{l1WEeIz^N_Mvp>CbUjFlJ4cl~NS8fHls!qA zPfcGwOPfGUlRr$EKun!RO^iTIo=i}2LQbGVP?}UyV?$4(Lr|kiQiw!RqC`=pMpK_x zRc2IIX+~3~Yg#5*S7=97s7O|&R#|UYT4zXBt4UX=TUu&KSglJ~s##ogOIfQ^Ta8Ou zuU=hjOj@o?TCr4Il}uZ+Olzf@(wRb{|cWx-Zu!D?xCRc63dX2Diw!GLWkR%gOi zXTw}+tygHoSZKvJ)b&_{?gKKrKl6@>`cf@*nhIf08n1CO4d!2oKiJ5~efPjmhg&%^0j-iMk zeS@i>i7bVNk)?|tsE!||jw^$Tv8|FHtCA~;jiZ5!#E+1hvX&lAJnrQqpQ`~wGpbV!PmAM+PE34ugTuJ4&AyI z;k^^&z6#~P53jc1=fMrOxWnnf3A4D=?ZgG~#{j;;*TKWf$jjQ#(&*LJ-r(Wr=IHS7 z@$>cd`2YX^EC2ui0BHaq000R8009UbNU)&6g9sBUT*$DY!-o(H3YbVyK*Wm}Ginql zAVUTjAU#^#NU|g?Q<+?y%tR{X%1p9e#xzBWlqp@qatMjkiP9k*V0i9I*-&idC$S zT=C;)F5XmsMahmlOE_WLwQb)%m3w#2+^bq?@x$jA?7n!qbnz?986iJxQ11Eb7KYRu zq}cT6LAp%O9H+}j(He&Dp3|`1#tzfhxi`Tbg507A^Qia{8FOleks-*HDJaZmW5b3t ztk0af1R=~+9p5mBnf%fJN+`hGcamCds#?F^RZO6nX}MCR66f^l`h51(HUHpOumHyM zkU2>u6rMjapqGgt)dA=rfaaL;h*e$84Q$3k^-eDz>_Bu zGz^l1Ve}+}0FFoE5%*Bom1h3zGmL0q4MWP6A}&QFSn;&@BtdiubYPPJ5NM-6_>6>& zgPUoT$s=3>W|1l=?6Ayj_e{4JWmf4Ek1JvP2^Ka-kYbPk4tm$(KqA`7Vw7F}hmAm| z!Rcj2TUO^KKf~<*K*~Ik@)TB>fMG)^DFj}c=|OYsSs!k~;<~*uT(P?GVqEBhfa!zgKKod;PdB~dJlYNiTZyDVNAgK*pw+e5 z>%ssiYcW9rT-=bedu5&VWf)RO*@jwc9hbP!0{9b!Py$dLHbFbxFfG-cd*E~nb1E=I z={W{P7<}*ct%oYQEOQ}%eEDP=-+Ui&1aU0^E9|NOYz$|_fH^oZM-G0M!KD!Cp%tO& zkwWNA)0uM{Kl=3a6DfkQBzjggX*ceZnPe$Y!kHvE=j}{RnMsvY(>~A~wWFmJQsxNR z)Ke+xV`0M~|D+0BrNNbHS-$yp*o}$7mDOY*--DSdnQ)jXhq5}HJ!jjqpoJ`Gg29@&&UF<`;Xyj$5s|3yg&3&_3};Bg8rtxNILzTt0s;U#$owtC literal 0 HcmV?d00001 diff --git a/img/upfoldericon.gif b/img/upfoldericon.gif new file mode 100644 index 0000000000000000000000000000000000000000..57dbd589878f6f6aad3e77d18b59711ff0a91db3 GIT binary patch literal 902 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 literal 0 HcmV?d00001 diff --git a/img/vmodes/480pyuv.gif b/img/vmodes/480pyuv.gif new file mode 100644 index 0000000000000000000000000000000000000000..a8af128b4e80bad2ad39f8fd8152e21270ea3877 GIT binary patch literal 2038 zcmVTGCYPeJ%}_vfkQx4G(L(pKzcPl zi#9-mH9&?oL5(;=gEvBtIYWs!Ly$Q{l1WEeIz^N_Mvp>CbUjFlJ4cl~NS8fHls!qA zPfcGwOPfGUlRr$EKun!RO^iTIo=i}2LQbGVP?}UyV?$4(Lr|kiQiw!RqC`=pMpK_x zRc2IIX+~3~Yg#5*S7=97s7O|&R#|UYT4zXBt4UX=TUu&KSglJ~s##ogOIfQ^Ta8Ou zuU=hjOj@o?TCr4Il}uZ+Olzf@(wRb{|cWx-Zu!D?xCRc63dX2Diw!GLWkR%gOi zXTw}+tygHoSZKvJ)b&_{?gKKrKl6@>`cf@*nhIf08n1CO4d!2oKiJ5~efPjmhg&%^0j-iMk zeS@i>i7bVNk)?|tsE!||jw^$Tv8|FHtCA~;jiZ5!#E+1hvX&lAJnrQqpQ`~wGpbV!PmAM+PE34ugTuJ4&AyI z;k^^&z6#~P53jc1=fMrOxWnnf3A4D=?ZgG~#{j;;*TKWf$jjQ#(&*LJ-r(Wr=IHS7 z@$>cd`2YX^A^8LV00000EC2ui02l!^000R80ObiBNU)&6g9sBUT*$DY!-o(fN}Ncs zqQ#3CGiuz(v7^V2AVZ2ANwTELlPD<~Sjn=b%a<@?%A85Frp=o;bL!m5v!~CWK!XY$ zO0=laqezn~UCOkn)2C3QN}Wozs@1DlvufSSwX4^!V8e{vEjvd-P-&24_8cF z{W$;Y=Q-vx(fv>*?Z&wu=9ZiP?QjpEq zZPiCz7s#vn zXRr5t{^XstA7{s{+VjM$HfKh6Xm zXDt;l;e|1Zb=qiDwzS}J-mx_0KX0tG-;%W1(;p%FjP}fHHd$z-j{j^}WPwlqW1>A3 zJQJEegutZYOIx;-ikYdzVO~0HRtc4k_XKLc zTY-P-w_BRF$;ncwT28v3o}zXCs%TRq#^Gp7GeU^Vd~6Q5s%ZJKgqm_V+LY@*BgPri ztt*WPDN6%tH(s4CeW_}+B+e>TbtN+EC90y`GoG(2)fwzcw&5~?3E=jH6Sn#g3229g zrIKv11`N0l8U+l9uZoVj$eFuhqP9#o1!~I_swNoV?Mf9aD4IP47mO*TqJ_!hZbU*r zW5P2Xi=Rxjg?Dbgz^+ulGOe+fUXv(WX8@84KP4%HD~^GiLSZcAuK zZ2-%%xm{X3qm!-=OUHst)8?Xjzx?JmBa`WF!aVdT1xW}*(w0_-oaw}YM)-181-_K9 z?O>n+wW-)mfY-gWElo6>$`J%zlDrR|$beFLn2MB>HRN5bQ}=OQP5AdT{fQ1W_MnmU zh-Nzzo)3ji(b)~tq1PPcsETyXX-x}tLPHLDuzVfTA!pn-K${d0h)oe*0SJLM*=T}w zGlSn~^oBkB2uvR^+92(g<-g%|MhOcd4eLq>#S>!DDM(^qOr(ScC4>$pW&_b5-S&n8 zOyCWE^a%oKl1DHCa&`6LV+9`8*J73|p#Q*>R literal 0 HcmV?d00001 diff --git a/img/vmodes/576pyuv.gif b/img/vmodes/576pyuv.gif new file mode 100644 index 0000000000000000000000000000000000000000..9de3afa1ecc5c3e906f3946d1d070dc9cfcef5fd GIT binary patch literal 2167 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 bN?ZEUn9j7OH_hozd-~I$4z;Kc5)c481fU6I literal 0 HcmV?d00001 diff --git a/img/vmodes/ntscscart.gif b/img/vmodes/ntscscart.gif new file mode 100644 index 0000000000000000000000000000000000000000..9917c5e778b29c1c4d50cebcba43b08954bf0587 GIT binary patch literal 2128 zcmV-W2(R}?Nk%w1VHg250QUd@0000E5lRvjFc1??5*1Mt7*!STGCYPeJ%}_vfkQx4G(L(pKzcPl zi#9-mH9&?oL5(;=gEvBtIYWs!Ly$Q{l1WEeIz^N_Mvp>CbUjFlJ4cl~NS8fHls!qA zPfcGwOPfGUlRr$EKun!RO^iTIo=i}2LQbGVP?}UyV?$4(Lr|kiQiw!RqC`=pMpK_x zRc2IIX+~3~Yg#5*S7=97s7O|&R#|UYT4zXBt4UX=TUu&KSglJ~s##ogOIfQ^Ta8Ou zuU=hjOj@o?TCr4Il}uZ+Olzf@(wRb{|cWx-Zu!D?xCRc63dX2Diw!GLWkR%gOi zXTw}+tygHoSZKvJ)b&_{?gKKrKl6@>`cf@*nhIf08n1CO4d!2oKiJ5~efPjmhg&%^0j-iMk zeS@i>i7bVNk)?|tsE!||jw^$Tv8|FHtCA~;jiZ5!#E+1hvX&lAJnrQqpQ`~wGpbV!PmAM+PE34ugTuJ4&AyI z;k^^&z6#~P53jc1=fMrOxWnnf3A4D=?ZgG~#{j;;*TKWf$jjQ#(&*LJ-r(Wr=IHS7 z@$>cd`2YX^A^8LV00000EC2ui02l!^000R80ObiBNU)&6g9sBUT*$DY!-o(fN}Ncs zqQ#3CGiuz(v7^V2AVZ2ANwTELlPD<~Sjn=b%a<@?%A85Frp=o;bL!m5v!~CWK!XY$ zO0=laqezn~UCOkn)2C3QN}Wozs@1DlvufSSwX4^!V8ej$RJL6`BIEZN~LClzOVVE--OJ*F{=HfU?US58@GV)}} zm0u3V1z50OT%7k7CA9CKYuEj7(Ztp7wQGB+V=jB0S>`?4z+=C}1-$sbI5&U)>lb>Z za@j128@G+zCF|hOWzq(YTQyNa)Bkn1L^F|kFAejz+EzOW* zlKQx2B$8b;*PdkB_2JF_)fSSGkmWrSd+R@>u4F2zFRl8L ztFVTeQ-J@Zx}&8mN$6T`2FyUnjxpt#P62)n*;1q|6^N#!(el*mKNRYhZcOA_$K;{h zwqz}aHf~F;vp7}Y=W@?*_)>s(?xbES1{6rBOXOA{AFC|IYoC;_{b+#15&Qa*zc>aw z6T$o%rQtrfShR;htJ8BXRDAKy<6?bifx#KwAkaY*RR9`G7ALNB_89DI*$ZbpT{=qa zOQhB{*?RNUQqP??Ok`Zyy>o;3oqT5Px?l2!f(I)Kh3Z7Jc%qx zPd&TkE5DML&93yC!0NH8Ua%lekH=%{^U24 z-MD5*^2v>SFgUN+3F*mCND+GQ0~%`mnwh~kOYBx%|u3BcteabdBovOi(Uj6 zfcs(q literal 0 HcmV?d00001 diff --git a/img/vmodes/palcompyc.gif b/img/vmodes/palcompyc.gif new file mode 100644 index 0000000000000000000000000000000000000000..09ff4863df405d080468d91ee95c4d5cd9b15a3a GIT binary patch literal 2349 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 c8uE~cOr#TGCYPeJ%}_vfkQx4G(L(pKzcPl zi#9-mH9&?oL5(;=gEvBtIYWs!Ly$Q{l1WEeIz^N_Mvp>CbUjFlJ4cl~NS8fHls!qA zPfcGwOPfGUlRr$EKun!RO^iTIo=i}2LQbGVP?}UyV?$4(Lr|kiQiw!RqC`=pMpK_x zRc2IIX+~3~Yg#5*S7=97s7O|&R#|UYT4zXBt4UX=TUu&KSglJ~s##ogOIfQ^Ta8Ou zuU=hjOj@o?TCr4Il}uZ+Olzf@(wRb{|cWx-Zu!D?xCRc63dX2Diw!GLWkR%gOi zXTw}+tygHoSZKvJ)b&_{?gKKrKl6@>`cf@*nhIf08n1CO4d!2oKiJ5~efPjmhg&%^0j-iMk zeS@i>i7bVNk)?|tsE!||jw^$Tv8|FHtCA~;jiZ5!#E+1hvX&lAJnrQqpQ`~wGpbV!PmAM+PE34ugTuJ4&AyI z;k^^&z6#~P53jc1=fMrOxWnnf3A4D=?ZgG~#{j;;*TKWf$jjQ#(&*LJ-r(Wr=IHS7 z@$>cd`2YX^A^8LV00000EC2ui02l#U000R801XHnNU)&6g9sBUT*$DY!-o(fN}Ncs zqQ#3CGiuz(v7^V2AVZ2ANwTELlPFWFT*4k&C$=|a}+p1yti7Od+qIbSp7WR4eJ)qw!VJ;{QZ9HkKy+1kqylMAl~)+@45&)n9w4~m9FXcZ13xb-Mszo3+;a< ztN$@s2q8f62rbi9cN=~6^`~HF_$j1dcws4s6#}f807ZKR%7>kH3wEeqg9$x2&*{7;ef-?Cf|HS9;xM)NP5>FgkbIHl6nHtLYsl}ZOG-CY;KpNK}$vmZtumIuJgnrkdP-2H^OQrNuS>nV512Enq?znFPsbqkv}T>#x8DE9|hu7HjOW$R-=? zUJ+Gb4r<{&`zl$*(7N19UF`E1gwnz)7GnNg3sYaf5!7v2)ru=q7kCvUt-A28^lZHJ zqGV3J_~xtczWny<@4o;CEbzbt7i{ps2q&!Y!VEX;@WT*CEb+t?S8Vac7-y{U#vFI- z@y8&CEb_=Cmu&LMD5tFQ$}G2hlni9}5%bJ4*G%)xHs_4<&N}zZ^Upp94fN1L7ftlh zMkl@V4q5q84?Ryu4fWJgS55WRR%ea%)>?PX_19j94ffb#mreHBS)VeY)Ah9N_SI95l@0?Xr&M=tr~lvi%~<(Ox# q`R1H=?)m4P&q9XFq?c~`>8Pi!`s%E=?)vMn$1eNqwAX%eKma=tcooqA literal 0 HcmV?d00001 diff --git a/img/volpoint.gif b/img/volpoint.gif new file mode 100644 index 0000000000000000000000000000000000000000..48621eb19207dcef99d9daf8677396ab5722a8d0 GIT binary patch literal 838 zcmV-M1G)T1Nk%w1VF~~W0QUd@0000E5lRvjFc1??5*1Mt7*!STGCYPeJ%}_vfkQx4G(L(pKzcPl zi#9-mH9&?oL5(;=gEvBtIYWs!Ly$Q{l1WEeIz^N_Mvp>CbUjFlJ4cl~NS8fHls!qA zPfcGwOPfGUlRr$EKun!RO^iTIo=i}2LQbGVP?}UyV?$4(Lr|kiQiw!RqC`=pMpK_x zRc2IIX+~3~Yg#5*S7=97s7O|&R#|UYT4zXBt4UX=TUu&KSglJ~s##ogOIfQ^Ta8Ou zuU=hjOj@o?TCr4Il}uZ+Olzf@(wRb{|cWx-Zu!D?xCRc63dX2Diw!GLWkR%gOi zXTw}+tygHoSZKvJ)b&_{?gKKrKl6@>`cf@*nhIf08n1CO4d!2oKiJ5~efPjmhg&%^0j-iMk zeS@i>i7bVNk)?|tsE!||jw^$Tv8|FHtCA~;jiZ5!#E+1hvX&lAJnrQqpQ`~wGpbV!PmAM+PE34ugTuJ4&AyI z;k^^&z6#~P53jc1=fMrOxWnnf3A4D=?ZgG~#{j;;*TKWf$jjQ#(&*LJ-r(Wr=IHS7 z@$>cd`2YX^EC2ui015yK000Op01XJdNARA&gZKO?^k;D3!i5GCK7=T7BEyOk30hPb Q(PBdb7(WgycrYLUJ0Eag>;M1& literal 0 HcmV?d00001 diff --git a/img/wait.gif b/img/wait.gif new file mode 100644 index 0000000000000000000000000000000000000000..5437fd0c183059ba5138d026bec62c6dceb79582 GIT binary patch literal 2128 zcmV-W2(R}?Nk%w1Vfp|y0QUd@0000E5lRvjFc1??5*1Mt7*!STGCYPeJ%}_vfkQx4G(L(pKzcPl zi#9-mH9&?oL5(;=gEvBtIYWs!Ly$Q{l1WEeIz^N_Mvp>CbUjFlJ4cl~NS8fHls!qA zPfcGwOPfGUlRr$EKun!RO^iTIo=i}2LQbGVP?}UyV?$4(Lr|kiQiw!RqC`=pMpK_x zRc2IIX+~3~Yg#5*S7=97s7O|&R#|UYT4zXBt4UX=TUu&KSglJ~s##ogOIfQ^Ta8Ou zuU=hjOj@o?TCr4Il}uZ+Olzf@(wRb{|cWx-Zu!D?xCRc63dX2Diw!GLWkR%gOi zXTw}+tygHoSZKvJ)b&_{?gKKrKl6@>`cf@*nhIf08n1CO4d!2oKiJ5~efPjmhg&%^0j-iMk zeS@i>i7bVNk)?|tsE!||jw^$Tv8|FHtCA~;jiZ5!#E+1hvX&lAJnrQqpQ`~wGpbV!PmAM+PE34ugTuJ4&AyI z;k^^&z6#~P53jc1=fMrOxWnnf3A4D=?ZgG~#{j;;*TKWf$jjQ#(&*LJ-r(Wr=IHS7 z@$>cd`2YX^A^8LV00000EC2ui0Qvwm000R80ObiBNU)&6g9sBUT*$DY!-o(fN}Ncs zqQ#3CGiuz(v7^V2AVZ2ANwTELga%ToT*hbQ&u8QOTg{raJ=81ht;R)DcI(^!Xuqt$I^E0)-EhBlX&X0d-7@#hUWu!9 zoR#TTcXP>mweOg=ksC06`(D~N+*kMigPro;@|ott=Z1-@HD;LlxQ}m{KD>6A@uaT| zetf0!^)0o>+Iudgr=5DMd54y2>e)lzc{0_<-F++N#~Laz3CNv;EJ;D}2F{{b*7E z0ZN$okB>nTIi!&6gK>b?HiUt`=*nAZpr;X)ojx?AA1EO8*gJ>p#z=q+)=p?elKDnx;E0TVVFJBWp%}%4>KpS%~7OyD{2YI;;w- zo=sZzvn|A_BxnGX7 zyWMI^hOxJxXn^dG4%J*&hrYjA}zTVr|F)1;-8iOq^|N*ab&w8VZx zuNLZf_FXurGeT|v)T1of^#Tr$^7_`|%C z8D~C!4A1vw0w*1+M2%ewt{EZ^)8i5>rsnf=Mu2Qh*7(p$n{= zUP_FaIh<%tPOVF(oU|D^ZW`cOty88qRgz6+KIwPhlqO!1qRw@?^PTXFr#$CL&wASP zp7_kCKKIEj&wl#zp8yT0KnF_Df*SOo2u)}~Pr}fKI`p9sji^K?O3{j1^r9Hes74JE G5CA*vrR^^O literal 0 HcmV?d00001 diff --git a/img/yes.gif b/img/yes.gif new file mode 100644 index 0000000000000000000000000000000000000000..f5d6835657d873ba0ffb37ab30e3b67353708456 GIT binary patch literal 1070 zcmV+}1kw9PNk%w1VKM+R0QUd@0000E5lRvjFc1??5*1Mt7*!STGCYPeJ%}_vfkQx4G(L(pKzcPl zi#9-mH9&?oL5(;=gEvBtIYWs!Ly$Q{l1WEeIz^N_Mvp>CbUjFlJ4cl~NS8fHls!qA zPfcGwOPfGUlRr$EKun!RO^iTIo=i}2LQbGVP?}UyV?$4(Lr|kiQiw!RqC`=pMpK_x zRc2IIX+~3~Yg#5*S7=97s7O|&R#|UYT4zXBt4UX=TUu&KSglJ~s##ogOIfQ^Ta8Ou zuU=hjOj@o?TCr4Il}uZ+Olzf@(wRb{|cWx-Zu!D?xCRc63dX2Diw!GLWkR%gOi zXTw}+tygHoSZKvJ)b&_{?gKKrKl6@>`cf@*nhIf08n1CO4d!2oKiJ5~efPjmhg&%^0j-iMk zeS@i>i7bVNk)?|tsE!||jw^$Tv8|FHtCA~;jiZ5!#E+1hvX&lAJnrQqpQ`~wGpbV!PmAM+PE34ugTuJ4&AyI z;k^^&z6#~P53jc1=fMrOxWnnf3A4D=?ZgG~#{j;;*TKWf$jjQ#(&*LJ-r(Wr=IHS7 z@$>cd`2YX^A^8LV00000EC2ui05SkF000R8009UbNU)&6g9sBUT*$DY!-o(fN}Nbg z9>t3Wr<0)#^~0UAj?*{cjk zW^C(LDBPJNzb>WgR&Lz9G1I;UxiBx%t9;cG#+wzS;KOSR8a~P~G3CVwCwqO&Q*UO@ zSvNQ2Y_~3E#&vgEhU^*V>dPb*-~Ibqb=lLSHK!ij8YOAivm@$GjhkZV+mVfvF5dCD o>g3G@Ge7Bkddtw(!A6BXmTdO!d7m;1{{A$)_`m1V9}EZpJN*9G>Hq)$ literal 0 HcmV?d00001 diff --git a/img/zoomoff.gif b/img/zoomoff.gif new file mode 100644 index 0000000000000000000000000000000000000000..120e262775e8dd7dfbd79d69ac967f000acf8b5f GIT binary patch literal 2065 zcmV+s2=4bsNk%w1Vfp|y0QUd@0000E5lRvjFc1??5*1Mt7*!STGCYPeJ%}_vfkQx4G(L(pKzcPl zi#9-mH9&?oL5(;=gEvBtIYWs!Ly$Q{l1WEeIz^N_Mvp>CbUjFlJ4cl~NS8fHls!qA zPfcGwOPfGUlRr$EKun!RO^iTIo=i}2LQbGVP?}UyV?$4(Lr|kiQiw!RqC`=pMpK_x zRc2IIX+~3~Yg#5*S7=97s7O|&R#|UYT4zXBt4UX=TUu&KSglJ~s##ogOIfQ^Ta8Ou zuU=hjOj@o?TCr4Il}uZ+Olzf@(wRb{|cWx-Zu!D?xCRc63dX2Diw!GLWkR%gOi zXTw}+tygHoSZKvJ)b&_{?gKKrKl6@>`cf@*nhIf08n1CO4d!2oKiJ5~efPjmhg&%^0j-iMk zeS@i>i7bVNk)?|tsE!||jw^$Tv8|FHtCA~;jiZ5!#E+1hvX&lAJnrQqpQ`~wGpbV!PmAM+PE34ugTuJ4&AyI z;k^^&z6#~P53jc1=fMrOxWnnf3A4D=?ZgG~#{j;;*TKWf$jjQ#(&*LJ-r(Wr=IHS7 z@$>cd`2YX^A^8LV00000EC2ui0Qvwm000R80ObiBNU)&6g9sBUT*$DY!-o(fN}Ncs zqQ#3CGiuz(v7^V2AVZ2ANwTELga%ToT*A*uS(jabK>e(dLJQ}_g0g}#WJ{X(ZoTAK0X=^ zYTm;8DS@d3`fBMe(Ud-&5?5)Q-I#a(dujZ*t>Vo8omUPtz348Etw*;VKYc&?D!Y_U zp0d2?o8G0z-%9?iHlP6o?#CWbS6l%_gAcwj+CC7l6rO1Mydi)9ZQ zIRX$+Kmh~zA<8holm!eRgNZE}7Ymh0 zO8MiRdFn~zPyzn)N&zed>Oo5lKzE>_h$_lbia?5{St_DoGn!4%VMFLj1-v5KnLn;4 zlX0T{c~o_x-7`Q-SN>zprYu?i>MBdEzUq>!=7>33ETa8pKry26y5>wugxQh_qfJL3 zax@utps7UZ^XZ{2N&8PMFA1v;OV(oRlD3bIc7(alvidI0XDWy6lz%vE7B6-X%d)%`Lj`pm*IN|2%sxFNN zpGym~b6~|0*KFX;J1Kc?Oryce(z;G}{Sw#=r^GU)|D+FkQgFpOEfeJ3u0?=Wn5~^k{ zz!LhODw`7f{kU)Pn!;|4yG)*nc4A5)dWGI%HWN~fPbZ`4M=U18K+3SBe`b1yUY21U z0k#B!{g7ZBC>Xewl#FcuAXN5d0zc8%0~K*d1#H?k!0;XJfH`qb)l#wmYaPdQE~#O9 za40)~=?-qUD_TwNG~u2%xWs@3tQQP#B0~bM#((T1jz1G(tGuTGCYPeJ%}_vfkQx4G(L(pKzcPl zi#9-mH9&?oL5(;=gEvBtIYWs!Ly$Q{l1WEeIz^N_Mvp>CbUjFlJ4cl~NS8fHls!qA zPfcGwOPfGUlRr$EKun!RO^iTIo=i}2LQbGVP?}UyV?$4(Lr|kiQiw!RqC`=pMpK_x zRc2IIX+~3~Yg#5*S7=97s7O|&R#|UYT4zXBt4UX=TUu&KSglJ~s##ogOIfQ^Ta8Ou zuU=hjOj@o?TCr4Il}uZ+Olzf@(wRb{|cWx-Zu!D?xCRc63dX2Diw!GLWkR%gOi zXTw}+tygHoSZKvJ)b&_{?gKKrKl6@>`cf@*nhIf08n1CO4d!2oKiJ5~efPjmhg&%^0j-iMk zeS@i>i7bVNk)?|tsE!||jw^$Tv8|FHtCA~;jiZ5!#E+1hvX&lAJnrQqpQ`~wGpbV!PmAM+PE34ugTuJ4&AyI z;k^^&z6#~P53jc1=fMrOxWnnf3A4D=?ZgG~#{j;;*TKWf$jjQ#(&*LJ-r(Wr=IHS7 z@$>cd`2YX^A^8LV00000EC2ui0Qvwm000R80ObiBNU)&6g9sBUT*$DY!-o(fN}Ncs zqQ#3CGiuz(v7^V2AVZ2ANwTELga%ToT*X&gw|5X`VbWU9TM(-m8^B(AavsL>3hs*P4)T7a8&W*fZ z=9fyCqjv5R&FIo8agD~=4LRs;FNYKVXF1%Kaj4!!cX2GedGM7L+E0I5w@xZM#9De6Db;T7>9GKu6q3r_!OWlE{&l>^=@P>HcwG`n$6IOU(Xc?|#ooM&0 zB;R48y~2S29K0f3X99{wpaJC-=oA4s=14#~q3yuZ4567L009LQKtKTIga%_uK!PSD zkwzk^Bun;zw$DHPsMO?@pxLyc3E#JN%dul#nVjQRaz9-B}_2iiRYw3OsO<|xWiq2{o( zX{RoMN@ZxVfX15v#ekNop))D}F=b08g!Wvd4MvJ5rAz7a8=Wor`cEt`y{ZpO!V>F} zv3-Vi1!&8t3EH!;%0x(Lo-WGVs*x7@4=U11%Bxe)gr-ThDmnWSwg0fBu1oBm3R(e| zrgUkQ?b5_wiQ$2U=WwwUx=*;fj%!qHoM!4$XzRIzue+c%+|t7$hGu}U2GkadpEcQ1 zZ@=QkD5(dbfm@@&Nman*J`KPWnsU`yywb}ralF#Qt^T6~e}phtZA|<2<}Z2v79cQ< zL!*ookJH8!8n-N6`}5N;Nu94s91r?WDutRr@k}0b2$QgrMrt0(prLZI(nX;X8k{k$ z=~B=l7o6wEDm^UgpT6$@Q-IG`%M|ZR@9Jtm0h{h4H`_zaz??n`!&K<8E(wdxrxLzo zd9z&-Afb~6d@WP0s=8#g2}Fm?ZfIpQ9#m`n$S{-E|6oU3XsNJ7yX`3%GA+Oybt+kk%haCM+q4g8XWcX9Y()w6Fqt`Ohd4)^(*AM^20f6q4SH7(Fk2n=?Cf);$Jx~#c zRJ3M$k0OxsAT>Yw^(Sd6S%9#-138z7usPZVDdu}4sl{KD*&QW^1zu4Aco%21OaFWLIZ5DiaFHD4xzUa0lb1s zR(gd1Wa33I{$n3vtl0}UrL6SLga9(wRFJ>~G90q2hgrNyh4#otJ_g_=17Hz?Rus9I z1d;=T6hI+aQh+@wDgYHy7y%rKlL9~zL01q!0MNA(NNSQLldRAm^SBc+nevpJWF(X- zxk{N7iILe7Qz>(lN?5u?mZZ$3E_cbxUi$Kvzzn7^he^z08uOUQOr|oI$;@Ut^O-q3 ei8QB4&1zcnn%K;yHn+*mZhG^Z;50};0029}R=w{4 literal 0 HcmV?d00001 diff --git a/mmsl/adjust.mmsl b/mmsl/adjust.mmsl new file mode 100644 index 0000000..b30d079 --- /dev/null +++ b/mmsl/adjust.mmsl @@ -0,0 +1,341 @@ +///////////////////////////////////////////// +// set current settings: +on load_adjustments > 0 + screen.brightness = settings.brightness + screen.contrast = settings.contrast + screen.saturation = settings.saturation +on load_adjustments == 1 + mute = 1 + player.volume = settings.volume + mute = 0 + +on player.volume == 0 + on load_adjustments == 1 + do_mute = 1 + +///////////////////////////////////////////// +// balloons (open/close/NA/...): + +on show_static_balloon != "" + balloon_timer = -1 + balloon_start_hidden = 0 + restore_timer_balloon = "" + show_balloon = show_static_balloon + +on show_timer_balloon != "" + balloon_timer = 3000 + balloon_start_hidden = 0 + restore_timer_balloon = "" + show_balloon = show_timer_balloon + +on show_fast_balloon != "" + balloon_timer = 1500 + balloon_start_hidden = 0 + restore_timer_balloon = "" + show_balloon = show_fast_balloon + +on show_fast_balloon_and_restore != "" + balloon_timer = 600 + balloon_start_hidden = 0 + restore_timer_balloon = restore_balloon + show_balloon = show_fast_balloon_and_restore + +on show_delayed_balloon != "" + balloon_timer = 300 + balloon_timer2 = 3000 + balloon_start_hidden = 1 + restore_timer_balloon = "" + show_balloon = show_delayed_balloon + balloon.visible = 0 + +on show_balloon == "" + delete .group == "balloon" + +on allow_balloons == 1 + on show_balloon != "" + delete .group == "balloon" + add image balloon + balloon.group = "balloon" + balloon.src = "img/" + show_balloon + ".gif" + balloon.x = screen.right - balloon.width + balloon.y = screen.top + balloon.timer = balloon_timer + +on restore_timer_balloon == "" + on balloon_start_hidden == 0 + on balloon.timer == 0 + delete .group == "balloon" + + on balloon_start_hidden == 1 + on balloon.timer == 0 + balloon.visible = 1 + balloon_start_hidden = 0 + balloon.timer = balloon_timer2 + +on restore_timer_balloon != "" + on balloon.timer == 0 + delete .group == "balloon" + show_static_balloon = restore_timer_balloon + +//////////////////////////// +// balloon text: + +on show_balloon_text != "" + add text balloon_text + balloon_text.group = "balloon" + balloon_text.valign = "center" + balloon_text.font = font2 + balloon_text.x = screen.right - balloon.width + 64 + balloon_text.y = screen.top + 28 + balloon_text.color = colors.yellow + balloon_text.backcolor = -1 + balloon_text.value = show_balloon_text + +/////////////////////////////////////////////// +// video modes: + +on pad.key == "vmode" + screen.switch = "next" + +on screen.tvstandard == "ntsc" && screen.tvout == "composite" + show_timer_balloon = "vmodes/ntsccompyc" +on screen.tvstandard == "ntsc" && screen.tvout == "ypbpr" + show_timer_balloon = "vmodes/ntsccompyuv" +on screen.tvstandard == "ntsc" && screen.tvout == "rgb" + show_timer_balloon = "vmodes/ntscscart" +on screen.tvstandard == "pal" && screen.tvout == "composite" + show_timer_balloon = "vmodes/palcompyc" +on screen.tvstandard == "pal" && screen.tvout == "ypbpr" + show_timer_balloon = "vmodes/palcompyuv" +on screen.tvstandard == "pal" && screen.tvout == "rgb" + show_timer_balloon = "vmodes/palscart" +on screen.tvstandard == "480p" && screen.tvout == "ypbpr" + show_timer_balloon = "vmodes/480pyuv" +on screen.tvstandard == "576p" && screen.tvout == "ypbpr" + show_timer_balloon = "vmodes/576pyuv" +on screen.tvstandard == "720p" && screen.tvout == "ypbpr" + show_timer_balloon = "vmodes/720pyuv" +on screen.tvstandard == "1080i" && screen.tvout == "ypbpr" + show_timer_balloon = "vmodes/1080iyuv" + +on screen.tvstandard != "" + load_adjustments = 2 + +/////////////////////////////////////////////////// +// Brightness/Contrast/Saturation: +on setup == 0 && adjustment == 0 && (drive.mediatype != "dvd" || dvd != 0) + on do_setup == 1 + do_setup = 0 + adjustment = 1 + delete .group == "vol" + delete .group == "volback" + add image adjback + adjback.group = "volback" + adjback.src = "img/adjustment.gif" + adjback.halign="center" + adjback.valign="bottom" + adjback.x = (screen.right + screen.left)/2 + adjback.y = screen.bottom + add rect adjcur + adjcur.group = "volback" + adjcur.x = adjback.x - 100 + adjcur.width = 230 + adjcur.height = 25 + adjcur.round = 5 + adjcur.color = colors.yellow + adjcur.backcolor = -1 + update_adj = 0 + cur_adj = cur_adj // update + +on adjustment == 0 + cur_adj = 0 + screen.update = 1 + +on adjustment == 1 + on adjback.timer == 0 + delete .group == "volback" + delete .group == "vol" + adjustment = 0 + settings.brightness = screen.brightness + settings.contrast = screen.contrast + settings.saturation = screen.saturation + + on cur_adj < 3 + on do_setup == 1 + do_setup = 0 + adjback.timer = 3000 + pad.key = "down" + + on cur_adj == 3 + on do_setup == 1 + adjback.timer = 0 + + on cur_adj >= 0 + adjcur.y = adjback.y - 130 + cur_adj * 30 + + on update_adj == 0 + screen.update = 0 + delete .group == "vol" + + on update_adj == 0 + adjback.timer = 3000 + adj_value = screen.brightness + adjclr = colors.darkblue + (cur_adj == 0) * 250 /* white */ + on update_adj == 1 + adj_value = screen.contrast + adjclr = colors.darkblue + (cur_adj == 1) * 250 /* white */ + on update_adj == 2 + adj_value = screen.saturation + adjclr = colors.darkblue + (cur_adj == 2) * 250 /* white */ + on update_adj == 3 + adj_value = (player.audio_offset + 5000) / 10 + adjclr = colors.darkblue + (cur_adj == 3) * 250 /* white */ + + + on update_adj >= 0 && update_adj < 4 + add rect adj + adj.group = "vol" + adj.x = adjback.x - 95 + adj_value * 212 / 1000 + adj.y = adjback.y - 125 + update_adj * 30 + adj.width = 7 + adj.height = 15 + adj.round = 2 + adj.color = -1 + adj.backcolor = adjclr + update_adj = update_adj + 1 + + on update_adj == 3 + screen.update = 1 + + on cur_adj < 0 + cur_adj = 3 + on cur_adj == 0 + on adj_plus != 0 + screen.brightness = screen.brightness + adj_plus + on cur_adj == 1 + on adj_plus != 0 + screen.contrast = screen.contrast + adj_plus + on cur_adj == 2 + on adj_plus != 0 + screen.saturation = screen.saturation + adj_plus + on cur_adj == 3 + on adj_plus != 0 + player.audio_offset = player.audio_offset + adj_plus * 5 + + + on pad.key == "left" || pad.key == "volume_down" + pad.key = "" + adj_plus = -20 + update_adj = 0 + + on pad.key == "right" || pad.key == "volume_up" + pad.key = "" + adj_plus = 20 + update_adj = 0 + + on pad.key == "up" || pad.key == "prev" + pad.key = "" + cur_adj = (cur_adj - 1) % 4 + update_adj = 0 + + on pad.key == "down" || pad.key == "next" + pad.key = "" + cur_adj = (cur_adj + 1) % 4 + update_adj = 0 + + on pad.key == "cancel" || pad.key == "return" + pad.key = "" + screen.brightness = 500 + screen.contrast = 500 + screen.saturation = 500 + player.audio_offset = 0 + update_adj = 0 + + on pad.key == "enter" + pad.key = "" + adjback.timer = 0 + +/////////////////////////////////////////////////// +// volume: + +on pad.key == "volume_up" + release_mute = 1 + player.volume = player.volume + 10 + +on pad.key == "volume_down" + release_mute = 1 + player.volume = player.volume - 10 + +on pad.key == "mute" + do_mute = 1 +on mute == 0 + on do_mute == 1 + do_mute = 0 + mute = 1 + saved_volume = player.volume + player.volume = 0 + add image muteballoon + muteballoon.group = "mute" + muteballoon.src = "img/mute.gif" + muteballoon.x = screen.right - muteballoon.width + muteballoon.y = screen.top + +on mute == 1 + on do_mute == 1 + do_mute = 0 + release_mute = 1 + + on release_mute == 1 + delete .group == "mute" + player.volume = saved_volume + mute = 0 + +on mute == 0 + on player.volume >= 0 + delete .group == "vol" + delete .group == "volback" + add image volback + volback.group = "volback" + volback.halign="center" + volback.valign="bottom" + volback.x = (screen.right + screen.left)/2 + volback.y = screen.bottom + volback.src = "img/volbkgrnd.gif" + vol_x = 0 + screen.update = 0 + vol_cnt = 0 + on vol_cnt < player.volume + add image vol + vol.group = "vol" + vol.x = volback.x - 90 + vol_x + vol.y = volback.y - 40 + vol.src = "img/volpoint.gif" + vol_x = vol_x + 19 + vol_cnt = vol_cnt + 10 + on vol_cnt >= player.volume + screen.update = 1 + volback.timer = 1000 + +on volback.timer == 0 + delete .group == "vol" + delete .group == "volback" + settings.volume = player.volume + +on player.volume == 0 + on volback.timer == 0 + do_mute = 1 + +/////////////////////////////////////////////////// +// Frame rate autodetect: + +on player.frame_rate == 25000 || player.frame_rate == 50000 + on autodetect_pal_ntsc == 1 + kernel.print = "Autodetect: PAL (" + player.frame_rate + " fps)" + screen.tvstandard = "pal" + autodetect_pal_ntsc = 0 +//on player.frame_rate == 23976 || (player.frame_rate >= 29000 && player.frame_rate <= 30000) || player.frame_rate == 60000 +on autodetect_pal_ntsc == 1 + kernel.print = "Autodetect: NTSC (" + player.frame_rate + " fps)" + screen.tvstandard = "ntsc" + autodetect_pal_ntsc = 0 + diff --git a/mmsl/doc/examples.txt b/mmsl/doc/examples.txt new file mode 100644 index 0000000..b2b5492 --- /dev/null +++ b/mmsl/doc/examples.txt @@ -0,0 +1,159 @@ +*** CONDITIONAL EXPRESSIONS *** + +How to execute a conditional expression only once, in the right place of code? + +// 1. First, the "silly" method: +i = a; +// here we want to check the new value of i: +on i == 4 + kernel.print = "number four!"; +// this will trigger printing "number four" once again if b=4! +i = b; + +// 2. Now, if we want to check only once (right after the 'i' set to 'a') then: +i = a; +// we'll check some unique variable with the same value instead of i: +checking_i = i; +on checking_i == 4 + kernel.print = "number four!"; +// now the block above will not be triggered anymore until checking_i is changed! +i = b; + + +*** LOOPS *** + +Simple loop - call some code N times: + +// 1. Set loop counter and number of repeats: +counter=0; N=10 + +// 2. The loop is a normal 'on' block: +on counter int(val)+4 + * "123" + 4 => 127 + * - otherwise, if one of the operands is string constant, the other is converted to string for concatenation; + * Examples: + * var = "a" + * var + "b" => "ab" + * - otherwise, if one of the operands is integer variable, the other is converted to integer; + * Examples: + * var1 = "2" + * var2 = 3 + * var1 + var2 => 5 + * - otherwise, both operands are considered as strings, and string concatenation used. + * 4. String-to-integer conversion: + * - "10 Monkeys" = 10 + * - "Opera 5" = 0 + * - " 1" = 1 + * - "-55.3" = -55 + * - "+12 15" = 12 + * - "no digits" = 0 + * 5. For other comparison operators (except ==), operands are considered integers + +================================================ +- MMSL Operators + * Arithmetic and Conditional expression operators: + * ::= Operator:: + * ::== Operator:: + * ::&& Operator:: + * ::|| Operator:: + * ::!= Operator:: + * ::> Operator:: + * ::< Operator:: + * ::>= Operator:: + * ::<= Operator:: + * ::+ Operator:: + * ::- Operator:: + * ::* Operator:: + * ::/ Operator:: + * ::% Operator:: + * + * Also special types of operators used for events and object addition and deletion statements: + * ::ON Operator:: + * ::ADD Operator:: + * ::DELETE Operator:: + +================================================ +- = Operator + * Syntax: + * [variable] = [variable or constant] + * Description: + * Simple assignment operator. + * Also the same as '==' in conditions. + * Example: + * a = b + * kernel.print = "Hello" +================================================ +- == Operator + * Syntax: + * [variable or constant] == [variable or constant] + * Description: + * Equality comparison operator. + * Used in conditional expressions. + * See: ::Type conversion::. + * Example: + * a == 5 + * mytext.value == "Hello" +================================================ +- && Operator + * Syntax: + * [expression] && [expression] + * Description: + * Logical-AND operator. + * Used in conditional expressions. + * Example: + * (a == 5) && (b != 3) +================================================ +- || Operator + * Syntax: + * [expression] || [expression] + * Description: + * Logical-OR operator. + * Used in conditional expressions. + * Example: + * (a > 5) || (a < -5) +================================================ +- != Operator + * Syntax: + * [variable or constant] != [variable or constant] + * Description: + * Non-Equality comparison operator. + * Used in conditional expressions. + * See: ::Type conversion::. + * Example: + * a != 5 + * mytext.value != "Hello" +================================================ +- > Operator + * Syntax: + * [integer variable or constant] > [integer variable or constant] + * Description: + * Relational 'greater than' operator. + * Used in conditional expressions. + * Example: + * a > 5 + * var1 > var2 +================================================ +- < Operator + * Syntax: + * [integer variable or constant] < [integer variable or constant] + * Description: + * Relational 'less than' operator. + * Used in conditional expressions. + * Example: + * a < 5 + * var1 < var2 +================================================ +- >= Operator + * Syntax: + * [integer variable or constant] >= [integer variable or constant] + * Description: + * Relational 'greater than or equal' operator. + * Used in conditional expressions. + * Example: + * a >= 5 + * var1 >= var2 +================================================ +- <= Operator + * Syntax: + * [integer variable or constant] <= [integer variable or constant] + * Description: + * Relational 'less than or equal' operator. + * Used in conditional expressions. + * Example: + * a <= 5 + * var1 <= var2 +================================================ +- + Operator + * Syntax: + * [variable or constant] + [variable or constant] + * Description: + * Addition/Concatenation operator. + * Used in arithmetic and conditional expressions. + * See: ::Type conversion::. + * Example: + * a + 5 + * mytext.value + "Hello" +================================================ +- - Operator + * Syntax: + * [integer variable or constant] - [integer variable or constant] + * Description: + * Subtraction operator. + * The subtraction operator (-) subtracts the second operand from the first. + * Both operands are considered integers. + * Used in arithmetic and conditional expressions. + * Example: + * a - 5 + * var1 - var2 +================================================ +- * Operator + * Syntax: + * [integer variable or constant] * [integer variable or constant] + * Description: + * Multiplication operator. + * The multiplication operator (*) causes its two operands to be multiplied. + * Both operands are considered integers. + * Used in arithmetic and conditional expressions. + * Example: + * a * 5 + * var1 * var2 +================================================ +- / Operator + * Syntax: + * [integer variable or constant] / [non-zero integer variable or constant] + * Description: + * Division operator. + * The division operator ( / ) causes the first operand to be divided by the second. + * Both operands are considered integers. + * If second operand is zero, the result is zero (and run-time warning is issued). + * Used in arithmetic and conditional expressions. + * Example: + * a / 5 + * var1 / var2 +================================================ +- % Operator + * Syntax: + * [integer variable or constant] % [non-zero integer variable or constant] + * Description: + * Modulus operator. + * The result of the modulus operator (%) is the remainder when the first operand is divided by the second. + * Both operands are considered integers. + * If second operand is zero, the result is zero (and run-time warning is issued). + * Used in arithmetic and conditional expressions. + * Example: + * a % 5 + * var1 % var2 +================================================ +- ON Operator + * Syntax: + * on [condition] + * [statements...] + * ... + * Description: + * Conditional Event operator. + * Triggers event when one of condition variables is set (to any value) inside MMSL script or by running program. + * When event is triggered, its statements are called - but only if condition of this event and its parent events' conditions are true. + * Object variables are not set implicitly when an object is created (and thus they won't trigger an event). + * Normally, the depending events are triggered immediately after conditional variable is set. + * Call recursion is not allowed directly. When one of triggered event's statements activates the same event, this next call is deferred untill the current event call runs to the end. + * Example: + * i = 1 + * on (i <= 5) + * add text mytxt; mytxt.x = 10; mytxt.y = i * 20; mytxt.value = "String "+i + * i = i + 1 +================================================ +- ADD Operator + * Syntax: + * add [object_type] [object_name] + * Description: + * Add Object operator. + * Creates a new object of given type and adds it to the pool. + * This operator is also some sort of a variable declaration. Thus every object name should be used only once. + * The statement itself can be called many times (when event is repeated), assigning a new object to the same variable each time. Old objects become nameless and cannot be accessed by variables anymore. However, the deletion operator works with all created objects. + * Built-in object types: + * - text + * * Creates a text object + * - image + * * Creates an image object + * - rect + * * Creates a rectangle object + * Example: + * add text hello + * hello.x = 100 + * hello.y = 200 + * hello.value = "Hello, world!" + * Example: + * add rect myrect + * myrect.x = 5; myrect.y = 7; myrect.width = 50; myrect.height = 10; +================================================ +- DELETE Operator + * Syntax: + * delete [condition] + * Description: + * Delete Objects operator. + * Object's variables must be used in comparison conditions (including object's name, type etc). + * Object variables are used as ".variablename", i.e. without object name or prefix. + * Warning! The 'delete' command needs to run through the object list, so it takes some time. + * Warning! On deletion, the object's variables are not triggered. There's no direct way to 'capture' the object deletion in events. + * Warning! If multiple objects match the condition, the deletion order is undetermined. + * Example: + * delete .group=="myrectangle" + * * An object of any type named 'myrectangle' will be deleted. + * Example: + * delete (.type=="rect") + * * All rectangles created will be deleted. + * Example: + * add text t1 + * t1.group = "g" + * add text t2 + * t2.group = "g" + * // ... + * delete (.group == "g") + * * One can use groups for simple deletion of all objects of the same kind + * Example: + * delrect.x1 = 10; delrect.y1 = 10; delrect.x2 = 100; delrect.y2 = 100; + * delete (.x <= delrect.x2 && .y <= delrect.y2 && \ + * .x+.width >= delrect.x1 && .y+.height >= delrect.y1) + * * Deletes all objects intersecting 'delrect' rectangle + * Example: + * delete 1 + * * Deletes all created objects! + * * Be careful using conditionals without object variables: such statement may delete all objects if it's true. +================================================ +- INCLUDE Command + * Syntax: + * include [string constant MMSL filename] + * Description: + * Inserts the code from another MMSL file. + * Include command is pre-processed, so only string constant can be specified as file name, no variables allowed. + * Examples: + * include "file.mmsl" + +================================================ + diff --git a/mmsl/doc/mmsl-misc.txt b/mmsl/doc/mmsl-misc.txt new file mode 100644 index 0000000..5d10be1 --- /dev/null +++ b/mmsl/doc/mmsl-misc.txt @@ -0,0 +1,51 @@ +=================================================================== + +- object + * Special built-in object - always equal to last created/accessed/modified object. + * Cannot be used in 'on' condition + - created + All objects have this member variable. + Set once - after the object's creation + - modified + All objects have this member variable. + Set to modified variable's name + - accessed + All objects have this member variable. + Set to accessed variable's name; Accessing means reading or modification + +- array + - count + - sort="normal", "inverse", "random" + - slice + - insert + - pos + ALIAS: position + * zero-based + - value + * contains a single array element, choosen by pos or jump + - jump + = "first" + = "last" + = "next" + = "prev" + +- string + * Can be used for text strings manipulation. + - value [r/w] + * string value - contains the string itself + - length [r/w] + * Read it to get string length. + * Set it to trim the string. + - from + * Set substring of current string starting from given character position. + * Can be used for character access too. + - find + * Cuts string to found substring + +- timer + - delta + * Time in milliseconds left before triggering timer (updating object) + * Will be set to zero when time elapses + + +=================================================================== diff --git a/mmsl/doc/mmsl-objects1.txt b/mmsl/doc/mmsl-objects1.txt new file mode 100644 index 0000000..96632d4 --- /dev/null +++ b/mmsl/doc/mmsl-objects1.txt @@ -0,0 +1,849 @@ + +- kernel + * Base firmware settings and control. + - power [w/o] + * Can be set by script to switch the player, but normally is set automatically via 'POWER' button. + = 0 + * Set it to turn the player off + = 1 (default) + * Set by default when the player started and initialized or when it is turned on + * When the player is turned off (suspended), mmsl events are not triggered. + = 2 + * halt firmware + - frequency [r/w] + * Get or set CPU frequency (for experts only!) + - chip [r/o] + * Get player chip revision string. + - free_memory [r/o] + * Get free kernel memory, bytes. + - flash_memory [r/o] + * Get Flash memory size, in bytes. + - print [w/o] + * Output a string to debug console (Press 'P/N' to show or hide console on the screen) + - firmware_version [r/o] + * Current version of firmware kernel + - mmsl_version [r/o] + * Current version of MMSL interpreter + - mmsl_errors [r/w] + * Filter console messages + = "none" (default, no error logging) + = "critical" (only critical errors reported) + = "general" (only general & critical errors reported) + = "all" (all errors & warnings) + - run [w/o] + * Run external binary file (Ex: kernel.run="file.bin") + * Maximum 20 arguments allowed (space-separated). + - random [r/o] + * Get a random number (0..32767) + +======================================================================================= + +- screen + * On-screen display (OSD) object with different settings. + * Use separate graphical objects for user interface. + - switch [r/w] + * Turns the screen on or off + = 0 + * The screen is turned off automatically when kenrel.power=0 + = 1 (default) + = "next" + * switch next video mode + - update [r/w] + = 0 + * Turns screen auto-update off + = 1 + * Turns screen auto-update on + = "now" + * Update the screen now + - tvstandard [r/w] + * TV standard. + = "pal" (default) + = "ntsc" + - tvout [r/w] + * TV output. + = "composite" (default) + * Composite/S-Video + = "ypbpr" + * Component/YPbPr + = "rgb" + * Composite/RGB via SCART + - left [r/o] + * TV-safe visible area rectangle left x coordinate, in pixels. + - top [r/o] + * TV-safe visible area rectangle top y coordinate, in pixels. + - right [r/o] + * TV-safe visible area rectangle right x coordinate, in pixels. + - bottom [r/o] + * TV-safe visible area rectangle bottom y coordinate, in pixels. + - palette [r/w] + * External 256-color palette file name (.ACT) + * Also resets all alpha values to defaults (255). + - palidx [w/o] + * Used for setting alpha or color value. See 'palalpha' and 'palcolor'. + = 0-255 + - palalpha [r/w] + * Set new alpha value to the current palette. See 'palidx'. + = 0-255 (255 = opaque, default for all indices) + - palcolor [r/w] + * Set new color value to the current palette. See 'palidx'. + = "#000000"-"#ffffff" or 24-bit integer + - font [r/w] + * Default font used for new text objects. Set it to the font file name (external .FNT file). + - color [r/w] + * Color index for font & drawing operations (default value for new objects) + = 0-255 + - backcolor [r/w] + * Background color index for font & drawing operations (default value for new objects) + = 0-255 + = -1 (default) + * For transparent background ('trcolor' value is used) + - trcolor [r/w] + * Transparent color index + = 0 (default) + - halign [r/w] + * Default horizontal text (and other objects') align, used for created objects + = "left" (default) + = "center" + = "right" + - valign [r/w] + * Default hertical text (and other objects') align, used for created objects + = "top" (default) + = "center" + = "bottom" + - back [r/w] + * JPEG file name for background image + = "" (default) + * For black background + - back_left [r/w] + * Left coordinate of JPEG output rect, in background pixels (0..719). + * Used for picture previews. + = 0 (default) + - back_top [r/w] + * Top coordinate of JPEG output rect, in background pixels (0..479). + * Used for picture previews. + = 0 (default) + - back_right [r/w] + * Right coordinate of JPEG output rect, in background pixels (0..719). + * Used for picture previews. + * For external images (not from flash memory), auto aspect-ration correction is used. + = 719 (default) + - back_bottom [r/w] + * Bottom coordinate of JPEG output rect, in background pixels (0..479). + * Used for picture previews. + = 479 (default) + - preload [w/o] + * Pre-load GIF image in memory. Set it to .gif file path. + = "" (default) + * Set it to empty string to unload all images from memory. + - hzoom [r/w] + * Used to scale background image or video horizontally, in percents + = "in" + = "out" + = 100 (default) + = 10-1000 + * Set zoom value in percents + - vzoom [r/w] + * Used to scale background image or video vertically, in percents + = "in" + = "out" + = 100 (default) + = 10-1000 + * Set zoom value in percents + - hscroll [r/w] + * Horizontal offset for background image or video (<0 for left, >0 for right). + = 0 (default) + - vscroll [r/w] + * Vertical offset for background image or video (<0 for left, >0 for right). + = 0 (default) + - rotate [r/w] + * Used for background image, in degrees + = 0 (default) + = 0,90,180,270 + = "auto" + * Set this to auto-detect image orientation (using Exif data). + - brightness [r/w] + * Set TV brightness + = 0..1000 + = 500 (default) + - contrast [r/w] + * Set TV contrast + = 0..1000 + = 500 (default) + - saturation [r/w] + * Set TV saturation + = 0..1000 + = 500 (default) + - fullscreen [r/w] + * Set OSD fullscreen mode + = 0 (default) + * OSD has 30/31 height by default. + = 1 + * Full height, 480 pixels. + +======================================================================================= + +- pad + * Front panel and IR-remove control object. Needed to process user input. + - key [r/o] + * Key code pressed by user. Keys for IR-remote and front panel are combined. + = "power" + = "eject" + = "one" + = "two" + = "three" + = "four" + = "five" + = "six" + = "seven" + = "eight" + = "nine" + = "zero" + = "cancel" + = "search" + = "enter" + = "osd" + = "subtitle" + = "setup" + = "return" + = "title" + = "pn" + = "menu" + = "ab" + = "repeat" + = "up" + = "down" + = "left" + = "right" + = "volume_down" + = "volume_up" + = "pause" + = "rewind" + = "forward" + = "prev" + = "next" + = "play" + = "stop" + = "slow" + = "audio" + = "vmode" + = "mute" + = "zoom" + = "program" + = "pbc" + = "angle" + - display [w/o] + * To display the string to the front panel. + - set [w/o] + * Set special symbol of the front panel display. + = "play" + * 'Play' symbol. + = "pause" + * 'Pause' symbol. + = "mp3" + * 'MP3' symbol. + = "dvd" + * 'DVD' symbol. + = "s" + * 'SVCD' symbol part. + = "v" + * 'VCD' symbol part. + = "cd" + * 'CD' symbol. + = "dolby" + * Dolby digital sound. + = "dts" + * DTS sound. + = "play_all" + * Play All. + = "play_repeat" + * Play Repeat. + = "pbc" + * VCD Playback Control. + = "camera" + * DVD camera/angle. + = "colon1" + * First ':'. + = "colon2" + * Second ':' + - clear [w/o] + * Clear entire front panel display or one special symbol. + * See also ::pad.set:: for possible special symbol values. + = "all" + * Clear entire display + +======================================================================================= + +- drive + * DVD player's drive object. Helps to detect media types and to control the drive tray. + - mediatype [r/w] + * Inserted disc type. + * You may change media type to force it for some strange discs. + = "none" (default) + = "dvd" + = "iso" + = "audio" + = "mixed" + = "hdd" + - tray [r/w] + * Drive tray status. + = "open" + = "close" + = "toggle" + * Set this to toggle tray status + +======================================================================================= + +- explorer + * File/folder/playlist browser. Helps to create file or item lists. + - charset [r/w] + * Filesystem mount charset (see built-in charsets below) + = "iso8859-1" (default) + = "iso8859-2" + = "cp1251" + = "koi8-r" + - folder [r/w] + * Current source folder for the file or item list. + * If subfolder is set (explorer.folder = explorer.path), current position and list are saved. + * To restore the previous list and position, call: explorer.folder = ".." + = "/" + * root folder for mounted files (CD-ISO mode or HDD) or Audio-CD tracks. + = "cdrom/" + * For mounted files on CD/DVD-ROM drive. Equivalent to "/". + = "hdd/" + * For mounted files on hard disc drive. Equivalent to "/". + = "dvd/" + * for DVD titles and chapters + = "playlist/",... + * Virtual folder for playlist used as explorer.target to gather items. + - target [r/w] + * Current target folder (for item copying). Can be used for play-lists. + = "" (default) + - filter [r/w] + * Allows tracks, files or folders filtering and grouping. + = "up,folder,track,dvd,file" (default) + * Special 'up' folder goes first, then all folders, then CDDA tracks, then special DVD item, then all files. + = "track" + * CD-Audio tracks only + = "file" + * Files only + = "folder" + * Folders only + = "dvd" + * DVD items only + = "" + * No filtering, all items ungrouped. + - mask1 [r/w] + * A filemask to list only some file/item types, separated by commas (ex. "*.avi,*.mpg,*.dat"). + * Several filemasks can be combined. + = "*.*" (default) + - mask2 [r/w] + * A filemask to list only some file/item types, separated by commas (ex. "*.avi,*.mpg,*.dat") + * Several filemasks can be combined. + = "*.*" (default) + - mask3 [r/w] + * A filemask to list only some file/item types, separated by commas (ex. "*.avi,*.mpg,*.dat") + * Several filemasks can be combined. + = "*.*" (default) + - mask4 [r/w] + * A filemask to list only some file/item types, separated by commas (ex. "*.avi,*.mpg,*.dat") + * Several filemasks can be combined. + = "*.*" (default) + - mask5 [r/w] + * A filemask to list only some file/item types, separated by commas (ex. "*.avi,*.mpg,*.dat") + * Several filemasks can be combined. + = "*.*" (default) + - path [r/o] + * Full path to the current file (like "/cdrom/dir/movie_file.avi") or CD-Audio track (like "/cdrom/1.cda"). + * To play, it should be passed to the 'player' object [code: player.source = explorer.path] + - filename [r/o] + * The filename (without extension!) of the current item (like "movie_file") + - extension [r/o] + * Current file/item extension (like ".avi") + * Can be used to detect current file's type. + - drive_letter [r/o] + * Current drive letter (like "C") for hard disc drives. + - type [r/o] + * The type of the current item + = "item" + * A file or media stream. + = "folder" + * Folder + = "up" + * A Special item for up-folder jumps - "..". + = "dvd" + * Special DVD item (it means that the current folder contains a DVD movie). + - maskindex [r/o] + * The index of the mask which included the current item. + = 1-5 + - filesize [r/o] + * Set if current item is a file, a size, in bytes. + * If real filesize greater than 2147483647 bytes (2 Gb limit), + * then the filesize is given as negative value, in kilobytes. + = 0 (default) + * All folders have zero filesize. + - filetime [r/o] + * Set to 'last modified' date & time, if current item is a file + * Folowing string format is used: DD.MM.YYYY HH:MM + = "" (default) + * If filetime is not set or unknown/zero. + - count [r/o] + * Number of items + - sort [r/w] + * Items sorting + = "none" + = "normal" + * Alphabetical sort + = "inverse" + * Inverse alphabetical sort + = "random" + * Shuffle + - position [r/w] + * Current item position (zero-based index). For empty lists, the position is always -1. + = -1 (default) + - command [w/o] + * One can use commands to manipulate with the list and it's items. + = "update" + * Update the list from the current item source folder. + * Call it every time the folder, charset, filter or mask changes. + = "first" + * Jump to the first item. + = "last" + * Jump to the last item. + = "next" + * Move to the next item. Set to explorer.count if moved before the last item. + = "prev" + * Move to the previous item. Set to -1 if moved before the first item. + = "randomize" + * Initialize random sequence and jump to the first random item. + = "nextrandom" + * Move to the next random item (see explorer.command="randomize"). + * Every item is passed through one time. Current item index set to -1 after the last one. + = "prevrandom" + * Move to the previous random item (see explorer.command="randomize"). + * Every item is passed through one time. Current item index set to -1 after the first one reached. + = "remove" + * Remove current item from the list. The next item becomes current. + = "removeall" + * Remove all items from the list. + = "copy" + * Copy current item from source folder to target folder. + * No item duplicates allowed. + * To check item existance in the target folder, use ::explorer.copied::. + * If the item copied, explorer.copied set to 1. + = "copyall" + * Copy all items from source folder to target folder. + * See explorer.command = "copy". + = "targetremove" + * Remove current item from the target folder. + * Equivalent to explorer.command="remove" if the current folder is the same as target. + * If the item removed, explorer.copied set to 0. + = "targetremoveall" + * Remove all items from the target folder. + - find [w/o] + * Find given item path and set current item if found in this folder. + * If not found, the item position is set to -1. + = "" (default) + - copied [r/o] + * Set if the current item was already copied to the current target folder. + = 0 (default) + = 1 + +======================================================================================= + +- player + * Object used to play or show media files on ISO, AudioCD or DVD discs. + * Uses internal code (for DVD and JPEG) or calls external binary players. + * Controlled by commands. Has special functionality for DVD discs. + - source [r/w] + * Media file source - set it to 'explorer.filepath' value for media file play/show + * or to the special folders for disc play (See 'explorer.folder' for details). + * File/disc is not read at this time. Use other commands to start playing or get file/disc info. + - playing [r/w] + * When player starts or stops playing, the player.playing is set to 1 or 0. + * Can be used to detect the player's state. + = 0 (default) + * Playing is stopped. + * Setting player.playing = 0 is equivalent to player.command = "stop" + = 1 + * The player is playing now. + * Setting player.playing = 1 is equivalent to player.command = "play" + - saved [r/o] + * Set for DVD disc if it was already played (a limited number of discs is stored: 5). + = 0 (default) + * The DVD disc is inserted for the first time. + = 1 + * The DVD disc was already played (the main part, not menu!). + - command [w/o] + = "info" + * Get media file info. Fills player variables - 'player.name', 'player.artist' and others. + * This may take some time - file reading is needed. + = "play" + * Starts playing. Call it also after title/chapter is changed. + = "continue" + * Continue playing from saved position. + = "stop" + * Stops playing. + = "pause" + * Pause play + = "step" + * Advance 1 frame. + = "slow" + * Turn on slow forward (1/2). + = "forward" + * Fastforward (and speed up/down for DVD). + = "rewind" + * Rewind (and speed up/down for DVD). + = "prev" + * Skip to previous DVD chapter; or stop playing media file. + = "next" + * Skip to the next DVD chapter; or stop playing media file. + = "press" + * Press 'enter' button for DVD menu. + = "left" + * Move DVD menu highlight left. + = "right" + * Move DVD menu highlight right. + = "up" + * Move DVD menu highlight up. + = "down" + * Move DVD menu highlight down. + = "menu" + * Call default DVD menu ('escape' menu). + = "rootmenu" + * Call root DVD menu. + = "return" + * Return to previous DVD menu. + = "cancel" + * Undo user-set time/title/chapter before play continues. + = "angle" + * Switch next DVD camera angle. + = "audio" + * Switch next DVD audio stream. 'language_audio' is updated. + = "subtitle" + * Switch next DVD subtitles language or turn them off. 'language_subtitle' is updated. + - select [w/o] + * Set selection range markers for the DVD movie or AudioCD. + = "" + = "begin" + = "end" + - repeat [r/w] + * Set repeat mode + = "none" (default) + * Play one time, no repeat. + = "selection" + * Repeat selected fragment. (See 'selection') + = "track" + * Repeat current track (for AudioCDs). + = "all" + * Repeat all disc. + = "random" + * Random play. + - speed [r/w] + * Current playing speed. + = 0 (default) + * Paused or stopped. + = 1 + * Normal play + = 8 + * Forward x8 + = 16 + * Forward x16 + = 32 + * Forward x32 + = 48 + * Forward x48 + = -8 + * Backward x8 + = -16 + * Backward x16 + = -32 + * Backward x32 + = -48 + * Backward x48 + = "1/2" + * Slow forward x1/2 + = "1/4" + * Slow forward x1/4 + = "1/8" + * Slow forward x1/8 + - menu [r/w] + * DVD menu control + = 0 (default) + * We're not in menu (Or set to 1 to resume playback) + = 1 + * We're in menu or not playing (Or set to 1 for menu jump) + = "escape" + * Jump to the root menu or resume playback if in menu + = "title" + * Jump to the title menu + = "root" + * Jump to the root menu + = "subtitle" + * Jump to subtitles selection menu + = "audio" + * Jump to audio selection menu + = "angle" + * Jump to angle/camera selection menu + = "chapters" + * Jump to DVD chapters selection menu + - language_menu [r/w] + * Default language for DVD menu. + * You can set two-letter codes ("en", "fr" etc.) or full strings ("English", "French"). + * Two-letter codes will be automatically converted into the full strings. + = "English" (default) + - language_audio [r/w] + * DVD set default audio language or change current audio stream if playing. + * You can set two-letter codes ("en", "fr" etc.) or full strings ("English", "French"). + * Two-letter codes will be automatically converted into the full strings. + = "English" (default) + - language_subtitle [r/w] + * DVD default subtitle language or change current if playing. + * You can set two-letter codes ("en", "fr" etc.) or full strings ("English", "French"). + * Two-letter codes will be automatically converted into the full strings. + = "English" (default) + = "" + * Set to hide subtitles. + - subtitle_charset [r/w] + * Used in subtitle files processing for video player. Set this before starting play! + = "" (default) + * No processing required. The subtitle file charset corresponds to font charset. + = "koi8-r" + * Translate KOI8-R to default WIN/ISO charset. + - subtitle_wrap [r/w] + * Number of letters in the text string before soft line break. Should be set to fit entire subtitle on the screen. + * Used in subtitle files processing for video player. Set this before starting play! + = 35 (default) + - subtitle [r/o] + * Returns subtitle string that should be currently shown on the screen. + = "" (default) + - angle [r/w] + * DVD camera angle. + = 1 (default) + - volume [r/w] + * Sound volume (0-100). + = 50 (default) + - balance [r/w] + * Volume stereo balance. -100 = left channel only, 100 = right channel only + = 0 (default) + * Centered + = -100..100 + - audio_offset [r/w] + * Number of msecs added to audio for audio/video sync. correction + = 0 (default) + = -5000..5000 + - time [r/w] + * Current playing time, in seconds. + * Also one can set this variable to seek the time position. + = 0 (default) + - title [r/w] + * Current DVD title number (1..99). Set player.command="play" to apply the title/chapter changes. + = 0 (default) + - chapter [r/w] + * Current DVD chapter (part) number (1..999). Set player.command="play" to apply the title/chapter changes. + = 0 (default) + - name [r/o] + * Name of the movie/song or DVD disc. Use player.command="info" before. + = "" (default) + - artist [r/o] + * Move/song artist info, if present. Use player.command="info" before. + = "" (default) + - audio_info [r/o] + * Info about audio data type & quality. Use player.command="info" before. + = "" (default) + - video_info [r/o] + * Info about video data type & quality. Use player.command="info" before. + = "" (default) + - audio_stream [r/o] + * Current audio stream number (1..8) or 0 for no audio. + = 0 (default) + - subtitle_stream [r/o] + * Current subtitle stream number (1..32) or 0 for hidden subtitles. + = 0 (default) + - length [r/o] + * Total length of the movie/song, in seconds. Use player.command="info" before. + = 0 (default) + - num_titles [r/o] + * Total number of titles in DVD. (0..99) + = 0 (default) + - num_chapters [r/o] + * Total number of chapters in current DVD title. (0..999) + = 0 (default) + - width [r/o] + * Video frame width, in pixels. Set for DVD or video file when started playing. + = 0 (default) + - height [r/o] + * Video frame height, in pixels. Set for DVD or video file when started playing. + = 0 (default) + - frame_rate [r/o] + * Video frame rate, in frames per millisecond (for example, 25000 means 25 fps). + = 0 (default) + - color_space [r/o] + * Color space used. Use player.command="info" before. + = "" (default) + = "ycbcr" + = "grayscale" + - debug [r/w] + * Set to 1 for player's debug info output (for internal players only). + = 0 (default) + = 1 + - error [r/o] + * Last error string code. Only non-empty errors trigger the variable. + * Every successful player's action silently clears the error variable. + = "" (default) + * Everything's OK. + = "invalid" + * The command or player operation is currently not allowed. + = "badaudio" + * Unsupported audio format. + = "badvideo" + * Unsupported video codec. + = "wait" + * Player requires some time. It's not actually an error. + = "corrupted" + * Read errors etc. Player-dependant. + +======================================================================================= + +- settings + * Firmware settings stored in EEPROM are controlled by this object. + - command [w/o] + = "defaults" + * Restore default settings + - audioout [r/w] + * Store audio output type. + = "analog" (default) + = "digital" + - tvtype [r/w] + * Used by video player. + = "letterbox" (default) + * 4:3 letterbox + = "panscan" + * 4:3 panscan + = "wide" + * 16:9 + = "vcenter" + * 16:9 panscan + - tvstandard [r/w] + * See ::screen.tvstandard::. + = "pal" (default) + = "ntsc" + = "480p" + = "576p" + = "720p" + = "1080i" + - tvout [r/w] + * See ::screen.tvout::. + = "composite" (default) + * Composite/S-Video + = "ypbpr" + * Component/YPbPr + = "rgb" + * Composite/RGB via SCART + - dvi [r/w] + * DVI Configuration index. (reserved for future use). + - hq_jpeg [r/w] + * High-Quality JPEG viewer mode. + = 0 + * Default + = 1 + - hdd_speed [r/w] + * Change SATA/PATA HDD speed + = "fastest" (default) + = "limited" + * Used for some SATA adapters + = "slow" + * Used for some SATA adapters or PATA HDD drives + - dvd_parental [r/w] + * DVD Parental control level + = 0..8 + - dvd_mv [r/w] + * DVD macrovision check flag + = 0 (default) + = 1 + - dvd_lang_menu [r/w] + * Default DVD menu language code + = "??" + * Two character language code ("en", "fr", ...) + - dvd_lang_audio [r/w] + * Default DVD audio language code + = "??" + * Two character language code ("en", "fr", ...) + - dvd_lang_spu [r/w] + * Default DVD subtitle language code + = "??" + * Two character language code ("en", "fr", ...) + - volume [r/w] + * Store audio volume + = 0..100 + = 50 (default) + - balance [r/w] + * Volume stereo balance. -100 = left channel only, 100 = right channel only + = -100..100 + = 0 (default) + * Centered + - brightness [r/w] + * Store/Load TV brightness (depends on TV mode) + = 0..1000 + = 500 (default) + - contrast [r/w] + * Store/Load TV contrast (depends on TV mode) + = 0..1000 + = 500 (default) + - saturation [r/w] + * Store/Load TV saturation (depends on TV mode) + = 0..1000 + = 500 (default) + - user1 [r/w] + * User-defined integer + - user2 [r/w] + * User-defined integer + - user3 [r/w] + * User-defined integer + - user4 [r/w] + * User-defined integer + - user5 [r/w] + * User-defined integer + - user6 [r/w] + * User-defined integer + - user7 [r/w] + * User-defined integer + - user8 [r/w] + * User-defined integer + - user9 [r/w] + * User-defined integer + - user10 [r/w] + * User-defined integer + - user11 [r/w] + * User-defined integer + - user12 [r/w] + * User-defined integer + - user13 [r/w] + * User-defined integer + - user14 [r/w] + * User-defined integer + - user15 [r/w] + * User-defined integer + - user16 [r/w] + * User-defined integer + +======================================================================================= + +- flash + * Object used for accessing the FLASH memory. + * Flashing begins automatically when both file and address is set. + * Setting file or address during the flashing (when progress < 100) will interrupt/restart the procedure. + * CAUTION! If address is zero, boot loader is overwriten, which can damage your player. + - file [r/w] + * File name of firmware binary. + - address [r/w] + * Starting address to flash. + - progress [r/o] + * Progress indicator, in per cents. + = 0 (default) + = 0..100 + = -1 + * If there was a read or flash error, the progress is set to -1. + = -2 + * Verification failed. + +======================================================================================= diff --git a/mmsl/doc/mmsl-objects2.txt b/mmsl/doc/mmsl-objects2.txt new file mode 100644 index 0000000..b90a4d3 --- /dev/null +++ b/mmsl/doc/mmsl-objects2.txt @@ -0,0 +1,161 @@ + +- image + * Puts a new image (animated picture) object on the screen. + - type [r/o] + * Object's type (set automatically when created). + = "image" + - group [r/w] + * Object's group name. Can be used for grouped objects deletion. + - src [r/w] + * Source image file in .GIF format (normal or animated). + - x [r/w] + * X coordinate, in pixels (see 'halign') + - y [r/w] + * Y coordinate, in pixels (see 'valign') + - visible [r/w] + * If the object is visible on the screen. + = 0 + * Default for empty objects ('new' without arguments) + = 1 + * Default for set-up objects ('new' with arguments) + - width [r/w] + * Image width, in pixels + - height [r/w] + * Image height, in pixels + - hflip [r/w] + * Horizontal image flip. A little more slow than normal image. + = 0 (default) + = 1 + - vflip [r/w] + * Vertical image flip + = 0 (default) + = 1 + - halign [r/w] + * Horizontal image align + = screen.halign (default) + - valign [r/w] + * Vertical image align + = screen.valign (default) + - timer [r/w] + * Sets built-in count-back timer, in msecs. Should be used in 'on' conditionals. + = 0 (default) + +======================================================================================= + +- text + * Puts a new text object on the screen. + - type [r/o] + * Object's type (set automatically when created). + = "text" + - group [r/w] + * Object's group name. Can be used for grouped objects deletion. + - x [r/w] + * X coordinate, in pixels (see 'halign') + - y [r/w] + * Y coordinate, in pixels (see 'valign') + - visible [r/w] + * If the object is visible on the screen. + * Non-visible text objects can be used for string manipulation. + = 0 + * Default for empty objects ('new' without arguments) + = 1 + * Default for set-up objects ('new' with arguments) + - width [r/w] + * Text width, in pixels. /*Set it to format multi-line text.*/ + = 0 + * Set automatically to text extents + - height [r/w] + * Text height, in pixels + - halign [r/w] + * Horizontal text align. + = screen.halign (default) + - valign [r/w] + * Vertical text align. + = screen.valign (default) + - color [r/w] + * Text color. + = screen.color (default) + - backcolor [r/w] + * Text background color. + = screen.backcolor (default) + - value [r/w] + * Text string. + - count [r/w] + * Number of characters in the text string. + * One can change this value to cut the string from the right. + * Text width and height changes if string is cut. + - delete [w/o] + * Delete first N characters from the string. Text width and height changes. + - font [r/w] + * Font name used for this text object + - textalign [r/w] + * Horizontal text align (rows are affected). + = "left" (default) + = "center" + = "right" + - style [r/w] + * Text style (see below). Setting text style changes text width and height. + = "" (default) + * No style, normal text. + = "underline" + * Underlined text. Underline displayed using text color. + = "outline" + * Contrast outline, used for subtitles. + * For outlined text width and height is increased by 1 pixel. + * Background color is used for outline, and transparent is for text background. + - timer [r/w] + * Sets built-in count-back timer, in msecs. Should be used in 'on' conditionals. + = 0 (default) + +======================================================================================= + +- rect + * Puts a new rectangle object on the screen. + - type [r/o] + * Object's type (set automatically when created). + = "rect" + - group [r/w] + * Object's group name. Can be used for grouped objects deletion. + - x [r/w] + * X Coordinate of rectangle offset, in pixels. + * See 'halign'. + - y [r/w] + * Y Coordinate of rectangle offset, in pixels. + * See 'valign'. + - visible [r/w] + * If the object is visible on the screen. + = 0 + * Default for empty objects ('new' without arguments) + = 1 + * Default for set-up objects ('new' with arguments) + - width [r/w] + * Rectangle width, in pixels. + - height [r/w] + * Rectangle height, in pixels. + - linewidth [r/w] + * Border line width, in pixels. + = 1 (default) + = 0 + * For no border. + - color [r/w] + * Is a rectangle border color, palette index + = screen.color (default) + - backcolor [r/w] + * Is a background color, palette index + * Set it to negative value to draw transparent rectangle + = screen.backcolor (default) + - halign [r/w] + * Is a rectangle horizontal align, relative to the given (x,y) center + = screen.halign (default) + - valign [r/w] + * Is a rectangle vertical align, relative to the given (x,y) center + = screen.valign (default) + - round [r/w] + * Draw a rounded rectangle. + * 'round' is a round arc radius, in pixels. + = 0 (default) + - timer [r/w] + * Sets built-in count-back timer, in msecs. Should be used in 'on' conditionals. + = 0 (default) + +======================================================================================= diff --git a/mmsl/doc/mmsl-tricks.txt b/mmsl/doc/mmsl-tricks.txt new file mode 100644 index 0000000..b3df9f6 --- /dev/null +++ b/mmsl/doc/mmsl-tricks.txt @@ -0,0 +1,27 @@ + +1) How to toggle variable states on event: + +/////// This is WRONG way because of cross-triggering: + +on settings.tvstandard == "pal" + on pad.key == "vmode" + settings.tvstandard = "ntsc" + +on settings.tvstandard == "ntsc" + on pad.key == "vmode" + settings.tvstandard = "pal" + +/////// This is RIGHT way: + +on pad.key == "vmode" + toggle_tvstandard = 1 + +on settings.tvstandard == "ntsc" + on toggle_tvstandard == 1 + toggle_tvstandard = 0 + settings.tvstandard = "pal" + +on settings.tvstandard == "pal" + on toggle_tvstandard == 1 + toggle_tvstandard = 0 + settings.tvstandard = "ntsc" diff --git a/mmsl/dvd.mmsl b/mmsl/dvd.mmsl new file mode 100644 index 0000000..9d3971f --- /dev/null +++ b/mmsl/dvd.mmsl @@ -0,0 +1,254 @@ + +screen.back = "" +cancel_setup = 1 +cancel_popup = 1 +pad.clear = "all" +pad.set = "dvd" + +// play dvd by default +set_dvd_source = 1 +on player_source_set == 0 + on set_dvd_source == 1 + player.source = "dvd/" + set_dvd_source = 0 + +player.language_menu = settings.dvd_lang_menu +player.language_audio = settings.dvd_lang_audio +player.language_subtitle = settings.dvd_lang_spu + +screen.preload = font1 +screen.preload = font2 +screen.preload = "img/player/popup.gif" +screen.preload = "img/player/invalid.gif" +screen.preload = "img/player/subtitle.gif" +screen.preload = "img/player/subtitleoff.gif" +screen.preload = "img/player/audio.gif" +screen.preload = "img/player/angle.gif" + +search_title = "DVD Search" +filesize_string = "" +filedate_string = "" + +//player.debug = 1 + +search = 0 + +player.command = "play" + +continue_cnt = 6 +continue_msg = "Playback will continue at saved position\n in " + +test_saved = 1 + +allow_zoom = 1 +allow_osd = 1 + +// play? +on test_saved == 0 + on menu == 0 + on pad.key == "play" + show_fast_balloon = "player/play" + player.command = "play" + on menu != 0 + on player.speed != 1 + on pad.key == "play" + player.command = "play" + on player.speed == 1 + on pad.key == "play" + show_balloon = "" + player.command = "press" + +// stop? +on pad.key == "stop" + dvd = 0 + show_fast_balloon = "player/stop" + auto_close_popup = 0 + show_popup_text = "The current disc position was saved!\nPress [Play] to resume playing.\nPress [Enter] to open file browser.\nPress [Setup] to open setup." + +//////////////////////// +// react on errors: +on player.speed != 0 + on player.error == "invalid" + show_fast_balloon = "player/invalid" + +/////////////////////// +// process keys: + +on pad.key == "forward" + player.command = "forward" + +on pad.key == "rewind" + player.command = "rewind" + +on pad.key == "prev" + show_fast_balloon = "player/prev" + player.command = "prev" + +on pad.key == "next" + show_fast_balloon = "player/next" + player.command = "next" + +on pad.key == "slow" + player.command = "slow" // auto-process slow fwd/rev + +// pause pressed +on player.menu == 0 + allow_zoom = 1 + allow_osd = 1 + on pad.key == "pause" + do_pause = 1 +on player.menu == 1 + cancel.zoom = 1 + allow_zoom = 0 + allow_osd = 0 + on pad.key == "pause" + show_fast_balloon = "player/invalid" + +// -> pause +on player.speed != 0 + on do_pause == 1 + show_static_balloon = "player/pause" + do_pause = 0 + player.command = "pause" + +// already is paused mode -> step +on player.speed == 0 + on do_pause == 1 + restore_balloon = "player/pause" + show_fast_balloon_and_restore = "player/stepfwd" + do_pause = 0 + player.command = "step" + +on search == 0 && zoom_mode == 0 && test_saved == 0 + on pad.key == "return" + show_fast_balloon = "player/return" + player.command = "return" + + on pad.key == "enter" + show_balloon = "" + player.command = "press" + + on pad.key == "left" + player.command = "left" + on pad.key == "right" + player.command = "right" + on pad.key == "up" + player.command = "up" + on pad.key == "down" + player.command = "down" + +on search == 0 + on pad.key == "menu" + zoom_mode = 0 + player.command = "menu" + on pad.key == "title" + zoom_mode = 0 + player.command = "rootmenu" + on pad.key == "angle" + player.command = "angle" + on pad.key == "audio" + player.command = "audio" + on pad.key == "subtitle" + player.command = "subtitle" + +///////////////////////// +on player.saved == 1 + on test_saved == 1 + kernel.print = "Disc position was saved. Continue playback?" + player.command = "pause" + popup_timer = 1000 + auto_close_popup = 0 + show_popup_text = "" + continue_msg + continue_cnt + " seconds" + add image cancel + cancel.group = "popup" + cancel.halign = "center" + cancel.valign = "center" + cancel.src = "img/player/cancel.gif" + cancel.x = popup.x + cancel.y = popup.y + 55 + + on continue_cnt <= 1 + on popuptext.timer == 0 + dvd_continue_play = 1 + + on continue_cnt > 1 + on popuptext.timer == 0 + continue_cnt = continue_cnt - 1 + popuptext.value = "" + continue_msg + continue_cnt + " seconds" + popuptext.timer = 1000 + + on pad.key == "enter" + continue_cnt = 0 + dvd_continue_play = 1 + + on pad.key == "cancel" + cancel_popup = 1 + kernel.print = "Cancel!" + player.command = "play" + test_saved = 0 + + on dvd_continue_play == 1 + cancel_popup = 1 + kernel.print = "Continue!" + player.command = "continue" + test_saved = 0 +on player.saved == 0 + on test_saved == 1 + test_saved = 0 + +///////////////////////// +// display speed: + +on show_balloon != "player/play" && search != 1 + on player.speed == 0 + show_static_balloon = "player/pause" + on player.speed == 1 + show_balloon = "" +on player.speed == 8 + show_static_balloon = "player/fwd8x" +on player.speed == 16 + show_static_balloon = "player/fwd16x" +on player.speed == 32 + show_static_balloon = "player/fwd32x" +on player.speed == 48 + show_static_balloon = "player/fwd48x" +on player.speed == -8 + show_static_balloon = "player/rev8x" +on player.speed == -16 + show_static_balloon = "player/rev16x" +on player.speed == -32 + show_static_balloon = "player/rev32x" +on player.speed == -48 + show_static_balloon = "player/rev48x" +on player.speed == "1/2" + show_static_balloon = "player/slowfwd2x" +on player.speed == "1/4" + show_static_balloon = "player/slowfwd4x" +on player.speed == "1/8" + show_static_balloon = "player/slowfwd8x" + + +///////////////////////////// +// display subtitle/audio languages: + +on player.menu == 0 + on player.subtitle_stream == 0 + show_fast_balloon = "player/subtitleoff" + on player.subtitle_stream > 0 + show_fast_balloon = "player/subtitle" + show_balloon_text = player.language_subtitle + " ("+ player.subtitle_stream + ")" + on player.audio_stream > 0 + show_fast_balloon = "player/audio" + show_balloon_text = player.language_audio + " ("+ player.audio_stream + ")" + + on player.angle > 0 + show_fast_balloon = "player/angle" + show_balloon_text = "Camera " + player.angle + +///////////////////////////// +// PAL/NTSC autodetect: + +on settings.user2 == 1 + on player.frame_rate > 0 + autodetect_pal_ntsc = 1 + diff --git a/mmsl/flash.mmsl b/mmsl/flash.mmsl new file mode 100644 index 0000000..867bb53 --- /dev/null +++ b/mmsl/flash.mmsl @@ -0,0 +1,59 @@ +on flash_firmware != "" + pad.display = "FLASH" + screen.update = "now" + + ft0 = "Upgrading from " + flash_firmware + + add rect fbk + fbk.width = 500 + fbk.height = 150 + fbk.x = (screen.right + screen.left) / 2 - fbk.width / 2 + fbk.y = (screen.top + screen.bottom) / 2 + fbk.valign = "center" + fbk.round = 5 + fbk.color = colors.lightblueback + fbk.backcolor = colors.lightblueback + + add text fte + fte.x = fbk.x + 10 + fte.y = fbk.y - 40 + fte.valign = "center" + fte.font = font1 + fte.color = colors.white + fte.backcolor = fbk.backcolor + fte.value = ft0 + "\nPlease wait..." + + add rect r + r.x = fbk.x + 10 + r.y = fbk.y + r.valign = "center" + r.width = 0 + r.height = 22 + r.color = colors.white + r.backcolor = colors.white + + flash.file = flash_firmware + ft0 = ft0 + " @ 0x" + (flash.address/65536) + ((flash.address%65536)/4096) + "000..." + + on flash.progress == -1 + fte.value = ft0 + "\nERROR!" + pad.display = "Error" + drive.tray = "open" + screen.update = "now" + on flash.progress == -2 + fte.value = ft0 + "\nVerification ERROR! Try again!" + pad.display = "Error" + drive.tray = "open" + screen.update = "now" + + on flash.progress >= 0 + r.width = flash.progress * 480 / 100 + fte.value = ft0 + "\n" + flash.progress + " %" + pad.display = flash.progress + + on flash.progress == 100 + fte.value = ft0 + "\nDONE! PLEASE TURN OFF YOUR PLAYER NOW!" + pad.display = "donE" + drive.tray = "open" + screen.update = "now" + kernel.power = 2 diff --git a/mmsl/iso-info.mmsl b/mmsl/iso-info.mmsl new file mode 100644 index 0000000..65e3d41 --- /dev/null +++ b/mmsl/iso-info.mmsl @@ -0,0 +1,206 @@ +////////////////////////////// +// files info: + +on iso_info == 1 + info_x = 178 + info_y = 125 + + need_info = 0 + + delete .group == "fileinfo" + + add text textsize + textsize.group = "fileinfo" + textsize.visible = 0 + + add text textdate + textdate.group = "fileinfo" + textdate.visible = 0 + + add text photoload + photoload.group = "fileinfo" + photoload.visible = 0 + photoload.value = "Loading..." + photoload.halign = "center" + photoload.valign = "center" + photoload.x = 501 + photoload.y = 174 + +on del_info == 1 + need_info = 0 + textsize.visible = 0 + textdate.visible = 0 + photoload.visible = 0 + delete .group == "infotitle" + delete .group == "infoartist" + delete .group == "infodims" + delete .group == "infoclrs" + delete .group == "infolength" + put_dims = 0 + put_clrs = 0 + put_title = 0 + put_artist = 0 + put_length = 0 + +on in_menu == 0 + on cur_pos != -1 + del_info = 1 +on in_menu == 0 && osd == 0 && explorer.type == "file" + on cur_pos != -1 + info_iy = info_y + 2*23 + put_dims = 0 + put_clrs = 0 + put_title = 0 + put_artist = 0 + put_length = 0 + need_info = 1 + + // output user-friendly file size: + textsize.visible = 1 + textsize.x = info_x + textsize.y = info_y + textsize.timer = 500 // 0.5 sec + + filesize_value = explorer.filesize // see osd.mmsl + textsize.value = "Size: " + filesize_string + + // file modification date: + textdate.x = info_x + textdate.y = textsize.y + textsize.height + textdate.value = "Date: " + explorer.filetime + filedate_string = explorer.filetime + textdate.visible = 1 + textdate.timer = 0 + info_iy = info_iy + 23 + + show_preview = "" + info_path = explorer.path + +on in_menu == 0 && osd == 0 && explorer.type == "track" + on cur_pos != -1 + // first, get some info from CD + info_iy = info_y + 2*23 + need_info = 1 + need_length = 1 + player.source = explorer.path + we_asked_info_for = player.source + player.command = "info" + + info_iy = info_y + 1*23 + put_title = 1 + delete .group == "infotitle" + add text texttitle + texttitle.group = "infotitle" + texttitle.x = info_x + texttitle.y = info_iy + info_iy = info_iy + 23 + texttitle.value = "Track/Total: " + (explorer.filename/10) + (explorer.filename%10) + " / " + (player.num_titles/10) + (player.num_titles%10) + +on need_info == 1 && playing == 0 && show_popup_text == "" + // use a little delay to speed-up cursor move + on textsize.timer == 0 + call_info = 1 + +on explorer.maskindex == 3 // jpeg + on call_info == 1 + show_preview = info_path + textdate.timer = 600 // start timer for preview +on show_preview != "" + on del_info == 1 + show_preview = "" + do_show_preview = "/img/defpreview.jpg" + folder.width = 0 + on textdate.timer == 0 + photoload.visible = 1 + screen.update = "now" + folder.width = 220 + do_show_preview = show_preview + photoload.visible = 0 + + // there was an error + on screen.back == "" + kernel.print = "Preview Error!" + do_show_preview = "/img/defpreview.jpg" + folder.width = 0 + +on do_show_preview != "" + saved_back_right = screen.back_right + saved_back_bottom = screen.back_bottom + + screen.back_left = 460 + screen.back_top = 105 + screen.back_right = 669 + screen.back_bottom = 244 + + screen.back = do_show_preview + + screen.back_left = 0 + screen.back_top = 0 + screen.back_right = saved_back_right + screen.back_bottom = saved_back_bottom + + +on call_info == 1 + call_info = 0 + player.source = info_path + // query player for extended info (we'll receive it when available) + we_asked_info_for = player.source + player.command = "info" + + +on need_info == 1 && osd == 0/* && playing == 0 */ + on put_title == 0 + on player.name != "" + put_title = 1 + delete .group == "infotitle" + add text texttitle + texttitle.group = "infotitle" + texttitle.x = info_x + texttitle.y = info_iy + info_iy = info_iy + 23 + texttitle.value = "Title: " + player.name + + on put_artist == 0 + on player.artist != "" + put_artist = 1 + delete .group == "infoartist" + add text textartist + textartist.group = "infoartist" + textartist.x = info_x + textartist.y = info_iy + info_iy = info_iy + 23 + textartist.value = "Artist: " + player.artist + + on put_dims == 0 + on player.width != 0 && player.height != 0 + put_dims = 1 + delete .group == "infodims" + add text textdims + textdims.group = "infodims" + textdims.x = info_x + textdims.y = info_iy + info_iy = info_iy + 23 + textdims.value = "Resolution: " + player.width + " X " + player.height + + on put_clrs == 0 + on player.color_space != "" + put_clrs = 1 + delete .group == "infoclrs" + add text textclrs + textclrs.group = "infoclrs" + textclrs.x = info_x + textclrs.y = info_iy + info_iy = info_iy + 23 + textclrs.value = "Color Space: " + player.color_space + + on put_length == 0 && need_length == 1 + on player.length > 0 + put_length = 1 + need_length = 0 + delete .group == "infolength" + add text textlength + textlength.group = "infolength" + textlength.x = info_x + textlength.y = info_iy + info_iy = info_iy + 23 + textlength.value = "Length: " + (player.length/36000) + ((player.length/3600)%10) + ":" + (player.length%3600/600) + ((player.length%3600/60)%10) + ":" + ((player.length%60)/10) + ((player.length%60)%10) diff --git a/mmsl/iso-menu.mmsl b/mmsl/iso-menu.mmsl new file mode 100644 index 0000000..165e9da --- /dev/null +++ b/mmsl/iso-menu.mmsl @@ -0,0 +1,383 @@ +////////////////////////////////// +// left menu: + +menu_x = screen.left +menu_y = 100 +menu_item_height = 40 + +add image mplay + mplay.x = menu_x; + mplay.y = menu_y + menu_item_height * 0; + mplay.src = "img/playoff.gif" +add image mplaymode + mplaymode.x = menu_x + 5; + mplaymode.y = menu_y + mplay.height + 5; + set_playmode = 1 + +add image mplaylist + mplaylist.x = menu_x - 5 + mplaylist.y = menu_y + menu_item_height * 2; + mplaylist.src = "img/playlistoff.gif" +add image mplaylistedit + mplaylistedit.x = menu_x - 5 + mplaylistedit.y = mplaylist.y + mplaylist.height; + set_playlistmode = 1 + +add image mplaylistmodes + mplaylistmodes.x = mplaylistedit.x + mplaylistedit.width + mplaylistmodes.y = mplaylist.y + mplaylist.height; + mplaylistmodes.src = "img/playlist-modes.gif" + +mrect_x = screen.left - 5 +mrect_y = menu_y + menu_item_height * 3 + +add image maudio + maudio.x = mrect_x; + maudio.y = mrect_y + menu_item_height * 1; +add image mvideo + mvideo.x = mrect_x; + mvideo.y = mrect_y + menu_item_height * 2; +add image mphoto + mphoto.x = mrect_x; + mphoto.y = mrect_y + menu_item_height * 3; +add image msubt + msubt.x = mrect_x; + msubt.y = mrect_y + menu_item_height * 4; + + +add rect mrect + mrect.x = menu_x - 1 + mrect.y = mrect_y - 2 + mrect.width = 105 + mrect.height = menu_item_height - 2 + mrect.backcolor = -1 + mrect.color = colors.yellow + mrect.round = 5 + mrect.linewidth = 2 + mrect.visible = 0 + +set_mflags = 1 + +on flag_music == 0 + on set_mflags == 1 + maudio.src = "img/audoff.gif" +on flag_music == 1 + on set_mflags == 1 + maudio.src = "img/audon.gif" +on flag_movie == 0 + on set_mflags == 1 + mvideo.src = "img/vidoff.gif" +on flag_movie == 1 + on set_mflags == 1 + mvideo.src = "img/vidon.gif" +on flag_pics == 0 + on set_mflags == 1 + mphoto.src = "img/photoff.gif" +on flag_pics == 1 + on set_mflags == 1 + mphoto.src = "img/photon.gif" +on flag_subt == 0 + on set_mflags == 1 + msubt.src = "img/subtoff.gif" +on flag_subt == 1 + on set_mflags == 1 + msubt.src = "img/subton.gif" +on flag_playlist == 0 + on set_mflags == 1 + mplaylist.src = "img/playlistoff.gif" +on flag_playlist == 1 + on set_mflags == 1 + mplaylist.src = "img/playliston.gif" + +// set "playlist edit" mode - not in playlist! +on playlist_edit == 1 && flag_playlist == 1 + on set_playlistmode == 1 + flag_playlist = 0 + set_mflags = 1 + do_playlist = 1 + do_playlist = 2 +on playlist_edit == 0 + on set_playlistmode == 1 + mplaylistedit.src = "img/playlist-edit-off.gif" + set_playlistmode = 0 +on playlist_edit == 1 + on set_playlistmode == 1 + mplaylistedit.src = "img/playlist-edit-on.gif" + set_playlistmode = 0 + +on play_mode == "all" + on set_playmode == 1 + set_playmode = 0 + mplaymode.src = "img/play-all.gif" +on play_mode == "random" + on set_playmode == 1 + set_playmode = 0 + mplaymode.src = "img/play-random.gif" + +old_menu_pos = -1 + + +/////////////////////////////////////////// +// menu cursor: + +on pad.key == "right" + do_right = 1 + +on in_menu = 1 + on explorer.count > 0 + on menu_cursor_hide == 1 + mrect.visible = 0 + on menu_pos == 0 + mrect.x = mplay.x - 2 + mrect.y = mplay.y - 2 + mrect.width = 105 + mrect.height = 26 + mrect.visible = 1 + on menu_pos == 1 && menu_posx == 0 + mrect.y = mplaymode.y + mrect.x = mplaymode.x - 2 + mrect.width = 28 + mrect.height = 17 + mrect.visible = 1 + on menu_pos == 1 && menu_posx == 1 + mrect.y = mplaymode.y + mrect.x = mplaymode.x + 38 + mrect.width = 57 + mrect.height = 17 + mrect.visible = 1 + on menu_pos == 2 + mrect.y = mplaylist.y + mrect.x = mplaylist.x - 2 + mrect.width = 110 + mrect.height = menu_item_height - 10 + mrect.visible = 1 + on menu_pos == 3 && menu_posx == 0 + mrect.y = mplaylistedit.y + mrect.x = mplaylistedit.x - 2 + mrect.width = mplaylistedit.width + 4 + mrect.height = 20 + mrect.visible = 1 + on menu_pos == 3 && menu_posx == 1 + mrect.y = mplaylistmodes.y + mrect.x = mplaylistmodes.x + 4 + mrect.width = 23 + mrect.height = 20 + mrect.visible = 1 + on menu_pos == 3 && menu_posx == 2 + mrect.y = mplaylistmodes.y + mrect.x = mplaylistmodes.x + 30 + mrect.width = 43 + mrect.height = 20 + mrect.visible = 1 + on menu_pos >= 4 + mrect.y = mrect_y + (menu_pos - 3) * menu_item_height - 2 + mrect.x = menu_x - 1 + mrect.width = 105 + mrect.height = menu_item_height - 2 + mrect.visible = 1 + +///////////////////// +// menu cursor control: +on in_menu = 1 + on menu_pos > 0 + on pad.key == "up" + menu_pos = menu_pos - 1 + menu_posx = 0 + + on (menu_pos == 1 && menu_posx == 0) || (menu_pos == 3 && menu_posx < 2) + on do_right == 1 + do_right = 0 + menu_posx = menu_posx + 1 + + on (menu_pos == 1 && menu_posx == 1) || (menu_pos == 3 && menu_posx > 0) + on pad.key == "left" + menu_posx = menu_posx - 1 + + on menu_pos < 7 + on pad.key == "down" + menu_pos = menu_pos + 1 + menu_posx = 0 + + // pageup/pagedown + on pad.key == "next" + do_next = 1 + on pad.key == "prev" + do_prev = 1 + on menu_pos == 0 || menu_pos == 1 + on do_next == 1 + do_next = 0 + menu_pos = 2 + menu_posx = 0 + on menu_pos == 2 || menu_pos == 3 + on do_next == 1 + do_next = 0 + menu_pos = 4 + menu_posx = 0 + on menu_pos <= 3 + on do_prev == 1 + do_prev = 0 + menu_pos = 0 + menu_posx = 0 + on menu_pos == 4 + on do_prev == 1 + do_prev = 0 + menu_pos = 2 + menu_posx = 0 + on menu_pos >= 4 && menu_pos < 7 + on do_next == 1 + do_next = 0 + menu_pos = menu_pos + 1 + menu_posx = 0 + on menu_pos > 4 && menu_pos <= 6 + on do_prev == 1 + do_prev = 0 + menu_pos = menu_pos - 1 + menu_posx = 0 + +/////////////////////////////////////////// +// menu/list switch: +on in_menu == 0 + on pad.key == "left" + in_menu = 1 + menu_pos = menu_pos + cursor_rect.backcolor = colors.grey + +on in_menu == 1 && explorer.count > 0 && (menu_pos == 0 || menu_pos == 2 || menu_pos >=4 || (menu_pos == 1 && menu_posx == 1) || (menu_pos == 3 && menu_posx == 2)) + on do_right == 1 + do_rignt = 0 + menu_cursor_hide = 1 + in_menu = 0 + cursor_rect.backcolor = colors.yellowback + +/////////////////////////// +// menu actions: +on in_menu == 1 + on pad.key == "enter" + do_enter = 1 + // play all/random (see iso-play.mmsl) + on explorer.count > 0 + on menu_pos == 0 + on do_enter == 1 + menu_cursor_hide = 1 + in_menu = 0 + cursor_rect.backcolor = colors.yellowback + do_start_play_all = 1 + + // playmode all + on menu_pos == 1 && menu_posx == 0 + on do_enter == 1 + do_enter = 0 + play_mode = "all" + set_playmode = 1 + // playmode random + on menu_pos == 1 && menu_posx == 1 + on do_enter == 1 + do_enter = 0 + play_mode = "random" + set_playmode = 1 + // playlist edit + on menu_pos == 3 && menu_posx == 0 + on do_enter == 1 + do_enter = 0 + playlist_edit = 1 - playlist_edit + set_playlistmode = 1 + menu_cursor_hide = 1 + in_menu = 0 + cursor_rect.backcolor = colors.yellowback + // playlist add all + on menu_pos == 3 && menu_posx == 1 + on do_enter == 1 + do_enter = 0 + explorer.command = "copyall" + menu_cursor_hide = 1 + in_menu = 0 + update_list = 1 + // playlist clear + on menu_pos == 3 && menu_posx == 2 + on do_enter == 1 + do_enter = 0 + explorer.command = "targetremoveall" + jump_to_list = 1 + update_list = 1 + on explorer.count > 0 + on jump_to_list == 1 + menu_cursor_hide = 1 + in_menu = 0 + +/////////////////////////////////// +// bottom left menu actions: +on in_menu == 1 + // playlist + on menu_pos == 2 + on pad.key == "enter" + flag_playlist = 1 - flag_playlist + set_mflags = 1 + do_playlist = 1 + do_playlist = 2 + on flag_playlist == 1 + on do_playlist == 1 + do_playlist = 0 + saved_folder = explorer.folder + explorer.folder = "playlist" + header.src = "img/playlisttop.gif" + playlist_edit = 0 + set_playlistmode = 1 + on flag_playlist == 0 + on do_playlist == 1 + do_playlist = 0 + explorer.folder = saved_folder + header.src = "img/mediatop.gif" + on do_playlist == 2 + show_timer_balloon = "wait" + screen.update = "now" + + // we cannot continue playing if folder is changed + do_play_all = 0 + + explorer.command = "update" + need_menu_cursor_hide = 1 + folder_changed = 1 + on explorer.count > 0 + need_menu_cursor_hide = 1 + menu_cursor_hide = 1 + in_menu = 0 + + // music flag + on menu_pos == 4 + on pad.key == "enter" + flag_music = 1 - flag_music + set_mflags_and_update = 1 + + // movie flag + on menu_pos == 5 + on pad.key == "enter" + flag_movie = 1 - flag_movie + set_mflags_and_update = 1 + // pics flag + on menu_pos == 6 + on pad.key == "enter" + flag_pics = 1 - flag_pics + set_mflags_and_update = 1 + // subt flag + on menu_pos == 7 + on pad.key == "enter" + flag_subt = 1 - flag_subt + set_mflags_and_update = 1 + +on set_mflags_and_update == 1 + set_mflags = 1 + show_timer_balloon = "wait" + screen.update = "now" + + // we cannot continue playing if folder is changed + do_play_all = 0 + + explorer.command = "update" + folder_changed = 1 + set_mflags_and_update = 2 + +on explorer.count > 0 + on set_mflags_and_update == 2 + set_mflags_and_update = 0 + menu_cursor_hide = 1 + in_menu = 0 diff --git a/mmsl/iso-photo.mmsl b/mmsl/iso-photo.mmsl new file mode 100644 index 0000000..ed0de01 --- /dev/null +++ b/mmsl/iso-photo.mmsl @@ -0,0 +1,84 @@ + +//////////////////////// +// photo viewer + +on do_show_photo = 1 + do_play = 0 + del_info = 1 + update_list_loop = 0 + pad.display = "JPEG" + delete .group != "mute" && .group != "scr" + screen.update = "now" + save_menu = 1 + in_menu = 2 + play_cur_pos = play_pos + player.source = play_path // for info only + playing = 1 + we_need_info = 1 + allow_zoom = 1 + allow_osd = 1 + screen.back = play_path + restore_osd_for_continue = 1 + start_slideshow = 1 + + on settings.user6 > 0 && do_play_all == 1 + on start_slideshow == 1 + add rect slideshow + slideshow.group = "sldshow" + slideshow.visible = 0 + slideshow.timer = settings.user6 * 1000 + + on slideshow.timer == 0 + delete .group == "sldshow" + pad.key = "next" + + on save_menu == 1 + on in_menu != 2 + old_in_menu = in_menu + + on zoom_mode == 0 + on do_enter == 1 + stop_viewer = 1 + + on pad.key == "stop" || pad.key == "cancel" || pad.key == "return" || stop_viewer == 1 + delete .group == "sldshow" + do_enter = 0 + stop_viewer = 0 + show_fast_balloon = "player/stop" + pad.display = "ISO" + kernel.print = "Restore ISO interface..." + do_show_photo = 0 + cancel_osd = 1 + cancel_zoom = 1 + old_play_osd = osd + allow_zoom = 0 + allow_osd = 0 + do_play_all = 0 + playing = 0 + + in_menu = old_in_menu + redraw_iso = 1 + screen.update = "now" + + // there was an error + on screen.back == "" + kernel.print = "Viewer Error!" + stop_viewer = 1 + show_badphoto_popup = 1 + + on pad.key == "angle" + screen.rotate = screen.rotate+90 + slideshow.timer = settings.user6 * 1000 + + on pad.key == "pbc" + screen.rotate = screen.rotate-90 + slideshow.timer = settings.user6 * 1000 + + on pad.key == "pause" + slideshow.timer = -1 + show_fast_balloon = "player/pause" + + on slideshow.timer < 0 + on pad.key == "play" + slideshow.timer = settings.user6 * 1000 + show_fast_balloon = "player/play" diff --git a/mmsl/iso-play.mmsl b/mmsl/iso-play.mmsl new file mode 100644 index 0000000..9a91546 --- /dev/null +++ b/mmsl/iso-play.mmsl @@ -0,0 +1,397 @@ + +//////////////////////// +// file player control + +on explorer.count > 0 && playing == 0 + on pad.key == "play" + do_start_play_all = 1 + +// play all or random +on play_mode == "all" + on do_start_play_all == 1 + play_first_cmd = "" // "first" + play_next_cmd = "next" + play_prev_cmd = "prev" + play_cmd = play_next_cmd + do_play_all = 1 +on play_mode == "random" + on do_start_play_all == 1 + play_first_cmd = "randomize" + play_next_cmd = "nextrandom" + play_prev_cmd = "prevrandom" + play_cmd = play_next_cmd + do_play_all = 1 + +on do_play_all == 1 + kernel.print = "PLAY " + play_mode + mplay.src = "img/playon.gif" + cur_play_pos = 0 + do_init_play_all = 1 + +on do_play_all == 0 + play_cur_pos = -1 + mplay.src = "img/playoff.gif" + +on do_init_play_all == 1 + saved_pos = explorer.position + explorer.command = play_first_cmd // "first" or "randomize" + cur_play_pos = explorer.position + play_pos = cur_play_pos + play_path = explorer.path + play_mediatype = explorer.maskindex + play_type = explorer.type + explorer.position = saved_pos + do_init_play_all = 2 + +on cur_play_pos < 0 || cur_play_pos >= explorer.count + on do_init_play_all == 2 + do_init_play_all = 0 + do_play_all = 0 +on play_type != "file" && play_type != "track" && play_type != "folder" + on do_init_play_all == 2 + do_init_play_all = 0 + do_continue_play = 1 +on do_init_play_all == 2 + do_init_play_all = 0 + was_continue_play = 0 + do_play = 1 +/////////////////////////////////////////////////// + + +// first, stop previous play +on player.playing == 1 + on do_play == 1 + kernel.print = "STOP PLAYING BEFORE PLAY" + stop_before_play = 1 + player.command = "stop" + +// stop viewer if we don't view pics anymore +on do_show_photo == 1 && play_mediatype != 3 /* photo */ + on do_play == 1 + kernel.print = "STOP VIEWER BEFORE PLAY" + stop_viewer = 1 + +// determine file type +on play_type == "folder" && play_path == "/cdrom/VIDEO_TS" + on do_play == 1 + play_path = "dvd/" + play_type = "dvd" + +on play_type == "track" || play_type == "folder" + on do_play == 1 + do_play = 0 + do_play_audio = 1 + +on play_type == "file" + on explorer.filename == "romfs" && explorer.extension == ".bin" + on do_play == 1 + do_play = 0 + flash_firmware = play_path + + +on play_type == "dvd" + on do_play == 1 + do_play = 0 + update_list_loop = 0 + del_info = 1 + delete .group != "mute" && .group != "scr" + screen.update = "now" + play_cur_pos = play_pos + player.source = play_path + player_source_set = 1 + // start as normal DVD + pad.key = "" + drive.mediatype = "dvd" + +on play_mediatype == 2 /////// audio + on do_play == 1 + do_play_audio = 1 +on play_mediatype == 3 /////// photo + on do_play == 1 + do_show_photo = 1 +on play_mediatype == 4 /////// subtitles + on explorer.copied == 0 + on do_play == 1 + do_play = 0 + explorer.command = "copy" // just add them to playlist + update_list = 1 + on explorer.copied == 1 + on do_play == 1 + do_play = 0 + explorer.command = "targetremove" + update_list = 1 + +/////////////////////////////////////////////////////////////////////// + +on do_play_audio == 0 && do_show_photo == 0 && play_mediatype != 4 && play_type != "folder" + on update_list_loop == 1 + on do_play == 1 + explorer.position = cur_play_pos + on do_play == 1 + update_list_loop = 0 + del_info = 1 + delete .group != "mute" && .group != "scr" + screen.back = "" + screen.update = "now" + screen.update = 1 + allow_zoom = 1 + do_play_video = 1 + +on player.playing == 0 + // disable ISO interface controls when playing video + on do_play_video == 1 + old_in_menu = in_menu + in_menu = 2 + screen.preload = "" + // this is for multiple audio tracks + screen.preload = "img/player/audio.gif" + screen.preload = "img/player/subtitle.gif" + screen.preload = "img/player/subtitleoff.gif" + + add text subtitle + subtitle.halign="center" + subtitle.valign="bottom" + subtitle.x = (screen.right + screen.left) / 2 + subtitle.y = screen.bottom + subtitle.font = font2 + subtitle.color = colors.white + subtitle.backcolor = 1 + subtitle.textalign = "center" + subtitle.style = "outline" + subtitle.value = "" + + on settings.user4 == 0 + on set_sub_charset == 1 + kernel.print = "subt_charset=" + subt_charset + player.subtitle_charset = subt_charset + on settings.user4 == 1 + on set_sub_charset == 1 + player.subtitle_charset = "koi8-r" + + set_sub_charset = 1 + player.subtitle_wrap = 35 + + start_play = 1 + on do_play_audio == 1 + start_play = 1 + + // PLAY! + on start_play == 1 + start_play = 0 + do_play = 0 + kernel.print = "PLAY " + play_path + play_cur_pos = play_pos + player.source = play_path + show_timer_balloon = "player/play" + playing = 1 + we_need_info = 1 + stop_before_play = 0 + player.command = "play" + allow_osd = 1 + restore_osd_for_continue = 1 + +// we want file info for OSD during play +we_asked_info_for != player.source + on we_need_info == 1 + we_asked_info_for = player.source + player.command = "info" + we_need_info = 0 + +on was_continue_play == 1 + on restore_osd_for_continue == 1 + restore_osd_for_continue = 0 + //kernel.print = "player.playing="+player.playing + do_osd = old_play_osd + +on playing == 1 + /////// if playing stopped: + on player.playing == 0 + play_cur_pos = -1 + cancel_search = 1 + tmp_play_osd = osd + cancel_osd = 1 + show_fast_balloon = "player/stop" + pad.clear = "all" + pad.display = "ISO" + // if video stopped - restore GUI + on do_play_video == 1 + on player.playing == 0 + kernel.print = "Restore ISO interface..." + in_menu = old_in_menu + redraw_iso = 1 + screen.update = "now" + on player.playing == 0 + do_play_audio = 0 + do_play_video = 0 + do_show_photo = 0 + cancel_zoom = 1 + allow_zoom = 0 + allow_osd = 0 + playing = 0 + playing_stopped = 1 + stop_before_play = 0 + + on do_continue_play == 0 + on player.source != play_path + play_path = player.source + kernel.print = "PLAY NEXT/PREV: " + play_path + +// in "play all/random" mode, we continue with the next file +on do_play_all == 1 && stop_before_play == 0 + on playing_stopped == 1 + was_continue_play = 0 + old_play_osd = tmp_play_osd // before we canceled + play_cmd = play_next_cmd + do_continue_play = 1 + +// play next/prev file... +on do_play_video == 1 || do_show_photo == 1 || osd == 1 + on play_type != "folder" + on pad.key == "next" + old_play_osd = osd + cancel_osd = 1 + play_cmd = play_next_cmd + do_continue_play = 1 + on pad.key == "prev" + old_play_osd = osd + cancel_osd = 1 + play_cmd = play_prev_cmd + do_continue_play = 1 + on play_type == "folder" + on pad.key == "next" + player.command = "next" + on pad.key == "prev" + player.command = "prev" + +//////// continue playing the next file +on do_continue_play == 1 + saved_pos = explorer.position + explorer.position = cur_play_pos + explorer.command = play_cmd // next or prev (normal/random) + cur_play_pos = explorer.position + + kernel.print = "Continue Play: " + explorer.filename + explorer.extension + play_pos = cur_play_pos + play_path = explorer.path + play_mediatype = explorer.maskindex + play_type = explorer.type + explorer.position = saved_pos + do_continue_play = 2 + +on cur_play_pos < 0 || cur_play_pos >= explorer.count + on do_continue_play == 2 + do_continue_play = 0 + do_play_all = 0 +on play_type != "file" && play_type != "track" && play_type != "folder" + on do_continue_play == 2 + do_continue_play = 1 + +on do_continue_play == 2 + do_continue_play = 0 + screen.update = "now" + was_continue_play = 1 + do_play = 1 + + +/////// player controls: +on playing == 1 + on pad.key == "pause" + do_pause = 1 + on player.speed == 1 + on do_pause == 1 + do_pause = 0 + show_static_balloon = "player/pause" + player.command = "pause" + + on pad.key == "forward" + player.command = "forward" + on pad.key == "rewind" + player.command = "rewind" + + on do_play_audio == 1 + on pad.key == "forward" + show_fast_balloon = "player/fwd" + on pad.key == "rewind" + show_fast_balloon = "player/rev" + + on do_play_video == 1 + on pad.key == "audio" + player.command = "audio" + on pad.key == "subtitle" + player.command = "subtitle" + on pad.key == "angle" + show_fast_balloon = "player/invalid" + + on pad.key == "stop" + kernel.print = "STOP PLAYING" + do_play_all = 0 + stop_before_play = 0 + player.command = "stop" + +// display playing status balloons +on playing == 1 + on do_play_video == 1 + on show_balloon != "player/invalid" + on player.speed == 1 + show_fast_balloon = "player/play" + on player.speed == 4 + show_static_balloon = "player/fwd" + on player.speed == -4 + show_static_balloon = "player/rev" + on player.speed > 4 + show_static_balloon = "player/fwdfast" + on player.speed < -4 + show_static_balloon = "player/revfast" + + // PAL/NTSC autodetect + on settings.user2 == 1 + on player.frame_rate > 0 + autodetect_pal_ntsc = 1 + + on player.subtitle != "" + subtitle.value = player.subtitle + on player.subtitle == "" + subtitle.value = player.subtitle + + on player.subtitle_stream == 0 + show_fast_balloon = "player/subtitleoff" + on player.subtitle_stream > 0 + show_fast_balloon = "player/subtitle" + show_balloon_text = player.language_subtitle + " ("+ player.subtitle_stream + ")" + + + // resume play + on player.speed != 1 && do_show_photo == 0 + on pad.key == "play" + resume_play = 1 + on player.speed == 0 && do_show_photo == 0 + on do_pause == 1 + do_pause = 0 + resume_play = 1 + on resume_play == 1 + cancel_search = 1 + show_fast_balloon = "player/play" + player.command = "play" + + on player.audio_stream > 0 + show_fast_balloon = "player/audio" + show_balloon_text = "Audio "+ player.audio_stream + +//////////////////////// +// react on errors: +on player.error == "invalid" + show_static_balloon = "player/invalid" +on player.error == "corrupted" + show_fast_balloon = "player/corrupted" +on player.error == "wait" + show_fast_balloon = "wait" +on player.error == "badaudio" + show_fast_balloon = "mute" +on player.error == "badvideo" + show_badvideo_popup = 1 +on player.error == "qpel" + qpel_gmc = "QPel"; + show_qpel_gmc_popup = 1 +on player.error == "gmc" + qpel_gmc = "GMC"; + show_qpel_gmc_popup = 1 diff --git a/mmsl/iso.mmsl b/mmsl/iso.mmsl new file mode 100644 index 0000000..ad30999 --- /dev/null +++ b/mmsl/iso.mmsl @@ -0,0 +1,111 @@ +lib_back = "img/lib.jpg" +screen.back = lib_back + +show_preview = "" +pad.clear = "all" +delete .group != "mute" && .group != "scr" +pad.display = "LoAd" +adjustment = 0 +screensaver = 0 +sleep_timer = 0 + +on drive.mediatype == "iso" || drive.mediatype == "mixed" + on explorer.folder == "/cdrom" + on display_pad == 1 + saved_display_pad = "ISO" + on explorer.folder == "/hdd" + on display_pad == 1 + saved_display_pad = "Hdd" + on explorer.drive_letter != "" + on display_pad == 1 + saved_display_pad = explorer.drive_letter + "_ Hdd" +on drive.mediatype == "audio" + on display_pad == 1 + show_timer_balloon = "cd" +on drive.mediatype == "audio" || drive.mediatype == "mixed" + on display_pad == 1 + pad.set = "cd" +on display_pad == 1 + pad.display = saved_display_pad + +search_title = "Time Search" +filesize_string = "" +filedate_string = "" + +iso_info = 1 + +do_init = 1 +on redraw_iso == 0 + on do_init == 1 + iso_init = 1 + +display_pad = 1 + +on drive.mediatype == "iso" || drive.mediatype == "mixed" || drive.mediatype == "audio" + on iso_init == 1 + explorer.folder = "/" +on iso_init == 1 + kernel.print = "Browser Init (" + iso_charset + ")..." + explorer.charset = iso_charset + explorer.target = "/playlist" + + explorer.filter = "up,track,folder,dvd,file" + explorer.sort = "normal" + + music_mask = "*.mp3,*.wav,*.ogg,*.mp2,*.mp1,*.mpa,*.ac3," + movie_mask = "*.avi,*.mpeg,*.mpg,*.m1v,*.m2v,*.dat,*.vob,*.divx,*.mp4,*.3gp," + pics_mask = "*.jpg,*.jpeg," + subt_mask = "*.sub,*.srt,*.ssa,*.txt" + + flag_music = 1 + flag_movie = 1 + flag_pics = 1 + flag_subt = 0 + + flag_playlist = 0 + playlist_edit = 0 + play_mode = "all" + + do_play_audio = 0 + do_play_video = 0 + do_show_photo = 0 + + playing = 0 + do_play_all = 0 + play_cur_pos = -1 + list_offset = 0 + + show_timer_balloon = "wait" + screen.update = "now" + + explorer.command = "update" + +//////////////////////////// +// handle file types: +on flag_movie >= 0 || flag_music >= 0 || flag_pics >= 0 || flag_subt >= 0 + explorer.mask1 = "" + explorer.mask2 = "" + explorer.mask3 = "" + explorer.mask4 = "" + explorer.mask5 = "romfs.bin" + flag_change_mask = 1 +on flag_movie == 1 + on flag_change_mask = 1 + explorer.mask1 = movie_mask +on flag_music == 1 + on flag_change_mask = 1 + explorer.mask2 = music_mask +on flag_pics == 1 + on flag_change_mask = 1 + explorer.mask3 = pics_mask +on flag_subt == 1 + on flag_change_mask = 1 + explorer.mask4 = subt_mask + + +include "list.mmsl" +include "iso-menu.mmsl" +include "iso-info.mmsl" +include "iso-play.mmsl" +include "iso-photo.mmsl" + diff --git a/mmsl/list.mmsl b/mmsl/list.mmsl new file mode 100644 index 0000000..8d56e65 --- /dev/null +++ b/mmsl/list.mmsl @@ -0,0 +1,437 @@ + +////////////////////////////////// +// header pic: + +add image header + header.x = screen.left + header.y = 20 + header.src = "img/mediatop.gif" + +////////////////////////////////// +// upper text: +upper_x = 150+30 +upper_y = 100 + +add text folder + folder.x = upper_x + folder.y = upper_y + folder.value = explorer.folder + + on explorer.folder != "" + folder.value = explorer.folder + +////////////////////////////////// +// list: + +list_x = 148 +list_y = 260 +list_count = 7 +list_item_height = 0 + +add rect play_rect + play_rect.group = "cursor_rect" + play_rect.round = 8 + play_rect.color = -1 + play_rect.color = colors.white + play_rect.backcolor = colors.grey + play_rect.visible = 0 + +add rect cursor_rect + cursor_rect.group = "cursor_rect" + cursor_rect.round=8 + cursor_rect.color = -1 + cursor_rect.backcolor = colors.yellowback + cursor_rect.visible = 0 + +add rect scroll_back + scroll_back.width = 10 + scroll_back.x = screen.right - 15 + scroll_back.y = list_y + scroll_back.height = screen.bottom - list_y - 10 + scroll_back.color = -1 + scroll_back.backcolor = colors.grey + +add image scroll_up + scroll_up.x = screen.right - 15 + scroll_up.y = list_y - 10 + scroll_up.src = "img/scroll.gif" +add image scroll_down + scroll_down.x = screen.right - 15 + scroll_down.y = screen.bottom - 10 + scroll_down.src = "img/scroll.gif" + scroll_down.vflip = 1 + +add rect scroll + scroll.width = scroll_back.width + scroll.height = scroll.width + scroll.x = screen.right - 15 + scroll.color = -1 + scroll.backcolor = colors.white + scroll.round = 5 + scroll.visible = 0 + +list_init = 1 +on iso_init == 1 + on list_init == 1 + list_init = 0 + iso_init = 0 + in_menu = explorer.count == 0 + menu_pos = 0 + explorer.position = list_offset + +folder_changed = 1 + +////////////////////////// +// start folder browsing: + +on folder_changed == 1 + list_offset = 0 + + on explorer.position - list_offset >= list_count + list_offset = explorer.position - list_count + 1 + + folder_changed = 0 + update_list = 1 + +//////////////////////////////////////// +// if user pressed 'ENTER': +on pad.key == "enter" + do_enter = 1 + +////////////////////////// +// update/draw list: + +on update_list == 1 + update_list_loop = 0 + delete .group == "items" + del_info = 1 + osd = 0 + +on explorer.count > 0 + on update_list == 1 + list_iy = list_y + list_pos = list_offset + saved_pos = explorer.position + explorer.position = list_offset + screen.update = 0 + + scroll_back.visible = 1 + scroll_up.visible = 1 + scroll_down.visible = 1 + + update_list_loop = 1 + +on explorer.count == 0 + on update_list == 1 + + scroll_back.visible = 0 + scroll_up.visible = 0 + scroll_down.visible = 0 + scroll.visible = 0 + cursor_rect.visible = 0 + show_balloon = "" + + screen.update = 1 + add text item + item.group = "items" + item.backcolor = -1 + item.x = list_x + 40; item.y = list_y; + item.value = "** No items **" + + +////////////////////////// +// draw 1 list item: + +on explorer.count > 0 + on update_list_loop == 1 + + // draw item icon: + icon_type = explorer.type + + on icon_type == "folder" + draw_icon = "foldericon" + on icon_type == "up" + draw_icon = "upfoldericon" + on explorer.maskindex == 1 // movie + on icon_type == "file" + draw_icon = "movieicon" + on explorer.maskindex == 2 // music + on icon_type == "file" + draw_icon = "musicicon" + on explorer.maskindex == 3 // pics + on icon_type == "file" + draw_icon = "picsicon" + on explorer.maskindex == 4 // subtitles + on icon_type == "file" + draw_icon = "subticon" + on explorer.maskindex == 5 // firmware + on icon_type == "file" + draw_icon = "fwicon" + on icon_type == "track" + draw_icon = "musicicon" + on icon_type == "dvd" + draw_icon = "dvdicon" + + on explorer.copied == 1 + on draw_icon != "" + draw_icon_src = "sel"+draw_icon + on explorer.copied == 0 + on draw_icon != "" + draw_icon_src = draw_icon + on draw_icon_src != "" + add image itemimg + itemimg.group = "items" + itemimg.x = list_x; itemimg.y = list_iy; + itemimg.src = "img/"+draw_icon_src+".gif" + + // print item text + add text item + item.group = "items" + item.backcolor = -1 + item.x = list_x + 40; item.y = list_iy; + set_item_type = explorer.type + on set_item_type == "up" + item.value = "[Up Folder]" + on set_item_type == "folder" + item.value = "[" + explorer.filename+explorer.extension + "]" + on set_item_type == "file" + item.value = explorer.filename+explorer.extension + on set_item_type == "track" + item.value = "Track " + explorer.filename + on set_item_type == "dvd" + item.value = "DVD Movie" + + list_item_height = item.height + list_iy = list_iy + list_item_height + 2 + explorer.command = "next" + list_pos = list_pos+1 + + on (list_pos < explorer.count && list_pos < list_offset + list_count) + update_list_loop = 1 + + on (list_pos >= explorer.count || list_pos >= list_offset + list_count) + explorer.position = saved_pos + update_list_loop = 2 + screen.update = 1 + + // the end of update + on update_list_loop == 2 + clear_balloon = 1 + on show_balloon != "player/stop" + on clear_balloon == 1 + show_balloon = "" + + cancel_popup = 1 + check_popups = 1 + on show_badvideo_popup == 1 + on check_popups == 1 + popup_timer = 4500 + auto_close_popup = 1 + show_popup_text = "Video codec not supported" + show_badvideo_popup = 0 + + on show_qpel_gmc_popup == 1 + on check_popups == 1 + popup_timer = 4500 + auto_close_popup = 1 + show_popup_text = "MPEG-4 Video with " + qpel_gmc + " not supported" + show_qpel_gmc_popup = 0 + + on show_badphoto_popup == 1 + on check_popups == 1 + popup_timer = 4500 + auto_close_popup = 1 + show_popup_text = "Cannot show JPEG file\n(too large or not enough memory)" + show_badphoto_popup = 0 + + update_list_loop = 0 + cur_pos = explorer.position + play_cur_pos = play_cur_pos + +///// move cursor + on cur_pos != -1 + cursor_rect.visible = 1 + cursor_rect.backcolor = colors.yellowback + cursor_rect.x = list_x + 33 + cursor_rect.y = list_y+(cur_pos-list_offset)*(list_item_height+2) + cursor_rect.width = screen.right-list_x - 40 + cursor_rect.height = list_item_height + // move scroller: + scroll.y = list_y + (cur_pos * scroll_back.height) / explorer.count + scroll_height = scroll_back.height / explorer.count + on scroll_height > 5 + scroll.height = scroll_height + on scroll_height <= 5 + scroll.height = 5 + scroll.visible = explorer.count > 1 + +////// move play cursor + on play_cur_pos > -2 + play_rect.visible = 0 + on in_menu != 2 && play_cur_pos >= list_offset && play_cur_pos < list_offset+list_count + on play_cur_pos != -1 + play_rect.visible = 1 + play_rect.x = list_x + 32 + play_rect.y = list_y+(play_cur_pos-list_offset)*(list_item_height+2)-1 + play_rect.width = screen.right - list_x - 40 + play_rect.height = list_item_height+2 + +/////////////////////////////////////////// +// cursor on the list: +on update_list_loop == 0 + on in_menu == 0 + on cur_pos < explorer.count - 1 + // cursor is at the bottom - scroll to the next page + on cur_pos == list_offset + list_count - 1 + on pad.key == "down" + explorer.command="next" + list_offset = explorer.position - list_count + 1 + cur_pos = explorer.position + update_list = 1 + + on (cur_pos - list_offset < list_offset + list_count - 1) + on pad.key == "down" + explorer.command = "next" + cur_pos = explorer.position + + // cursor is at the top - scroll to the prev. page + on cur_pos > 0 && cur_pos == list_offset + on pad.key == "up" + explorer.command = "prev" + list_offset = explorer.position + cur_pos = explorer.position + update_list = 1 + + on cur_pos > 0 + on pad.key == "up" + explorer.command = "prev" + cur_pos = explorer.position + + on pad.key == "next" + scroll_next = 1 + on pad.key == "prev" + scroll_prev = 1 + + on explorer.type == "folder" + on do_enter == 1 + do_enter = 0 + // we cannot continue playing if folder is changed + do_play_all = 0 + + explorer.folder = explorer.path + show_timer_balloon = "wait" + screen.update = "now" + explorer.command = "update" + display_pad = 1 + folder_changed = 1 + + on explorer.type == "up" + on do_enter == 1 + // we cannot continue playing if folder is changed + do_play_all = 0 + + do_enter = 0 + explorer.folder = ".." + show_timer_balloon = "wait" + screen.update = "now" + explorer.command = "update" + display_pad = 1 + folder_changed = 1 + + // play current file + on (explorer.type == "file" || explorer.type == "track" || explorer.type == "dvd") && playlist_edit == 0 + on do_enter == 1 + do_play_all = 0 + do_continue_play = 0 + stop_before_play = 0 + player.command = "stop" + play_pos = explorer.position + cur_play_pos = play_pos + play_next_cmd = "next" + play_prev_cmd = "prev" + play_path = explorer.path + play_mediatype = explorer.maskindex + play_type = explorer.type + do_enter = 0 + do_play_audio = 0 + do_play_video = 0 + do_show_photo = 0 + was_continue_play = 0 + do_play = 1 + + // copy to playlist + on (explorer.type == "file" || explorer.type == "track") && playlist_edit == 1 && explorer.copied == 0 + on do_enter == 1 + do_enter = 0 + explorer.command = "copy" + update_list = 1 + on (explorer.type == "file" || explorer.type == "track") && playlist_edit == 1 && explorer.copied == 1 + on do_enter == 1 + do_enter = 0 + explorer.command = "targetremove" + update_list = 1 + + /////////////////////////////////// + // jump to the first: + on cur_pos > 0 + on pad.key == "return" + explorer.position = 0 + list_offset = 0 + cur_pos = explorer.position + update_list = 1 + on pad.key == "title" + explorer.folder = "/" + explorer.command = "update" + display_pad = 1 + list_offset = 0 + cur_pos = explorer.position + update_list = 1 + + ///////////////////////////////////// + // page down: + on cur_pos <= explorer.count - list_count*2 + on scroll_next == 1 + explorer.position = explorer.position + list_count + list_offset = list_offset + list_count + cur_pos = explorer.position + update_list = 1 + + on cur_pos > explorer.count - list_count*2 && cur_pos < explorer.count - list_count + on scroll_next == 1 + explorer.position = explorer.position + list_count + list_offset = explorer.count - list_count + cur_pos = explorer.position + update_list = 1 + + on cur_pos >= explorer.count - list_count && cur_pos < explorer.count - 1 + on scroll_next == 1 + explorer.position = explorer.count - 1 + list_offset = explorer.count - list_count + on list_offset < 0 + list_offset = 0 + cur_pos = explorer.position + update_list = 1 + + ///////////////////////////////////// + // page up: + on cur_pos >= list_count*2 + on scroll_prev == 1 + explorer.position = explorer.position - list_count + list_offset = list_offset - list_count + cur_pos = explorer.position + update_list = 1 + + on cur_pos < list_count*2 && cur_pos >= list_count + on scroll_prev == 1 + explorer.position = explorer.position - list_count + list_offset = 0 + cur_pos = explorer.position + update_list = 1 + + on cur_pos < list_count && cur_pos > 0 + on scroll_prev == 1 + explorer.position = 0 + list_offset = 0 + cur_pos = explorer.position + update_list = 1 + diff --git a/mmsl/osd.mmsl b/mmsl/osd.mmsl new file mode 100644 index 0000000..92aa02e --- /dev/null +++ b/mmsl/osd.mmsl @@ -0,0 +1,379 @@ +////////////////////////// +/// OSD playing info: + +osd = 0 + +on allow_osd == 1 + on pad.key == "osd" + do_osd = 1 + +on osd == 0 + delete .group == "osd" + + on player.playing == 1 || setup == 1 || do_show_photo == 1 + on do_osd == 1 + real_do_osd = 1 + + on real_do_osd == 1 + kernel.print = "SHOW OSD INFO!" + do_osd = 0 + real_do_osd = 0 + cancel_osd = 0 + osd_old_in_menu = in_menu + in_menu = 2 + osd = 1 + delete .group == "osd" + add rect osdback + osdback.group = "osd" + osdback.halign = "center" + osdback.valign = "center" + osdback.x = (screen.right + screen.left) / 2 + osdback.y = (screen.bottom + screen.top) / 2 + osdback.width = 500 + osdback.height = 320 + osdback.round = 10 + osdback.color = -1 + osdback.backcolor = colors.blueback + + add rect osdframe + osdframe.group = "osd" + osdframe.halign = "center" + osdframe.valign = "center" + osdframe.x = osdback.x + osdframe.y = osdback.y + osdframe.width = osdback.width - 20 + osdframe.height = osdback.height - 20 + osdframe.round = 8 + osdframe.color = colors.yellow + osdframe.backcolor = -1 + + osd_x = osdback.x - osdback.width/2 + 30 + osd_y = osdback.y - osdback.height/2 + 20 + + add text osdhdr + osdhdr.group = "osd" + osdhdr.halign = "center" + osdhdr.x = osdback.x + osdhdr.y = osd_y + osdhdr.backcolor = osdback.backcolor + osdhdr.style = "underline" + osd_draw_header = 1 + + osd_ix = osd_x + osd_offx = 130 + osd_h = 24 + osd_iy = osd_y + osd_h * 2 + + osd_draw_main = 1 + +on drive.mediatype == "dvd" && player.playing == 1 + on osd_draw_header == 1 + osdhdr.value = "DVD Information" + on osd_draw_main == 1 + titletxt = "Title/Total" + print_title = 1 + print_chapter = 1 + print_time = 1 + print_length = 1 + print_audio = 1 + print_video = 1 + print_resolution = 1 + print_fps = 1 + +on drive.mediatype == "iso" || drive.mediatype == "mixed" + on osd_draw_header == 1 + osdhdr.value = "File Information" + on do_show_photo == 1 + on osd_draw_main == 1 + print_filename = 1 + print_filesize = 1 + print_filedate = 1 + print_resolution = 1 + print_clrs = 1 + on do_play_audio == 1 + on osd_draw_main == 1 + print_filename = 1 + print_filesize = 1 + print_time = 1 + print_length = 1 + print_audio = 1 + print_name = 1 + print_artist = 1 + + on do_play_video == 1 + on osd_draw_main == 1 + print_filename = 1 + print_filesize = 1 + print_time = 1 + print_length = 1 + print_audio = 1 + print_video = 1 + print_resolution = 1 + print_fps = 1 + +on play_type == "track" + on osd_draw_header == 1 + osdhdr.value = "Audio CD Information" + on osd_draw_main == 1 + titletxt = "Title/Total" + print_title = 1 + print_time = 1 + print_length = 1 + +on osd == 1 + on do_osd == 1 + do_osd = 0 + cancel_osd = 1 + + on pad.key == "cancel" + do_osd = 0 + cancel_osd = 1 + + on cancel_osd == 1 + in_menu = osd_old_in_menu + osd = 0 + + on print_osd != "" + add text osdtxt + osdtxt.group = "osd" + osdtxt.x = osd_ix + osdtxt.y = osd_iy + osdtxt.backcolor = osdback.backcolor + osdtxt.value = print_osd + + ////////////////////////// + // file name + on play_path != "" + on print_filename == 1 + print_osd = "File Name" + osd_ix = osd_x + osd_offx + print_osd = ":" + osd_ix = osd_x + osd_iy = osd_iy + osd_h + add text osdfname + osdfname.group = "osd" + osdfname.x = osd_ix + 10 + osdfname.y = osd_iy + osdfname.color = colors.yellow + osdfname.backcolor = osdback.backcolor + osd_iy = osd_iy + osd_h + 5 + + on play_path != "" || print_filename == 1 + osdfname.value = play_path + osdfname.delete = 6 + osdfname.width = 420 + + ////////////////////////// + // file size + on filesize_string != "" + on print_filesize == 1 + print_osd = "File Size" + add text osdfsize + osdfsize.group = "osd" + osdfsize.x = osd_x + osd_offx + osdfsize.y = osd_iy + osdfsize.backcolor = osdback.backcolor + osd_iy = osd_iy + osd_h + + on filesize_string != "" || print_filesize == 1 + osdfsize.value = ": " + filesize_string + + //////////////////////////////// + // file date + on filedate_string != "" + on print_filedate == 1 + print_osd = "File Date" + add text osdfdate + osdfdate.group = "osd" + osdfdate.x = osd_x + osd_offx + osdfdate.y = osd_iy + osdfdate.backcolor = osdback.backcolor + osd_iy = osd_iy + osd_h + + on filedate_string != "" || print_filedate == 1 + osdfdate.value = ": " + filedate_string + + ////////////////////////// + // file time + on player.time >= 0 + on print_time == 1 + print_osd = "Play Time" + add text osdftime + osdftime.group = "osd" + osdftime.x = osd_x + osd_offx + osdftime.y = osd_iy + osdftime.backcolor = osdback.backcolor + osd_iy = osd_iy + osd_h + + on player.time > 0 || print_time == 1 + osdftime.value = ": " + (player.time/36000) + ((player.time/3600)%10) + ":" + (player.time%3600/600) + ((player.time%3600/60)%10) + ":" + ((player.time%60)/10) + ((player.time%60)%10) + + ////////////////////////// + // file length + on player.length > 0 + on print_length == 1 + print_osd = "Length" + add text osdflength + osdflength.group = "osd" + osdflength.x = osd_x + osd_offx + osdflength.y = osd_iy + osdflength.backcolor = osdback.backcolor + osd_iy = osd_iy + osd_h + + on player.length > 0 || print_length == 1 + osdflength.value = ": " + (player.length/36000) + ((player.length/3600)%10) + ":" + (player.length%3600/600) + ((player.length%3600/60)%10) + ":" + ((player.length%60)/10) + ((player.length%60)%10) + + ////////////////////////// + // audio info + on player.audio_info != "" + on print_audio == 1 + print_osd = "Audio Info" + add text osdaudio + osdaudio.group = "osd" + osdaudio.x = osd_x + osd_offx + osdaudio.y = osd_iy + osdaudio.backcolor = osdback.backcolor + osd_iy = osd_iy + osd_h + + on player.audio_info != "" || print_audio == 1 + osdaudio.value = ": " + player.audio_info + + ////////////////////////// + // video info + on player.video_info != "" + on print_video == 1 + print_osd = "Video Info" + add text osdvideo + osdvideo.group = "osd" + osdvideo.x = osd_x + osd_offx + osdvideo.y = osd_iy + osdvideo.backcolor = osdback.backcolor + osd_iy = osd_iy + osd_h + + on player.video_info != "" || print_video == 1 + osdvideo.value = ": " + player.video_info + + ////////////////////////// + // name + on player.name != "" + on print_name == 1 + print_osd = "Title" + add text osdname + osdname.group = "osd" + osdname.x = osd_x + osd_offx + osdname.y = osd_iy + osdname.backcolor = osdback.backcolor + osd_iy = osd_iy + osd_h + + on player.name != "" || print_name == 1 + osdname.value = ": " + player.name + osdname.width = 315 + + ////////////////////////// + // artist + on player.artist != "" + on print_artist == 1 + print_osd = "Artist" + add text osdartist + osdartist.group = "osd" + osdartist.x = osd_x + osd_offx + osdartist.y = osd_iy + osdartist.backcolor = osdback.backcolor + osd_iy = osd_iy + osd_h + + on player.artist != "" || print_artist == 1 + osdartist.value = ": " + player.artist + osdartist.width = 315 + + ////////////////////////// + // DVD title + on player.title > 0 && player.num_titles > 0 + on print_title == 1 + print_osd = titletxt + add text osdtitle + osdtitle.group = "osd" + osdtitle.x = osd_x + osd_offx + osdtitle.y = osd_iy + osdtitle.backcolor = osdback.backcolor + osd_iy = osd_iy + osd_h + + on player.title > 0 && player.num_titles > 0 || print_title == 1 + osdtitle.value = ": " + (player.title/10) + (player.title%10) + " / " + (player.num_titles/10) + (player.num_titles%10) + + ////////////////////////// + // DVD chapter + on player.chapter > 0 && player.num_chapters > 0 + on print_chapter == 1 + print_osd = "Chapter/Total" + add text osdchapter + osdchapter.group = "osd" + osdchapter.x = osd_x + osd_offx + osdchapter.y = osd_iy + osdchapter.backcolor = osdback.backcolor + osd_iy = osd_iy + osd_h + + on player.chapter > 0 && player.num_chapters > 0 || print_chapter == 1 + osdchapter.value = ": " + (player.chapter/10) + (player.chapter%10) + " / " + (player.num_chapters/10) + (player.num_chapters%10) + + ////////////////////////// + // resolution + on player.width > 0 && player.height > 0 + on print_resolution == 1 + print_osd = "Resolution" + add text osdresolution + osdresolution.group = "osd" + osdresolution.x = osd_x + osd_offx + osdresolution.y = osd_iy + osdresolution.backcolor = osdback.backcolor + osd_iy = osd_iy + osd_h + + on player.width > 0 && player.height > 0 || print_resolution == 1 + osdresolution.value = ": " + player.width + " X " + player.height + + // frame rate + on player.frame_rate > 0 + on print_fps == 1 + print_osd = "Frame Rate" + add text osdfps + osdfps.group = "osd" + osdfps.x = osd_x + osd_offx + osdfps.y = osd_iy + osdfps.backcolor = osdback.backcolor + osd_iy = osd_iy + osd_h + + on player.frame_rate > 0 || print_fps == 1 + osdfps.value = ": " + (player.frame_rate / 1000) + "," + (player.frame_rate % 1000)/100 + (player.frame_rate % 100)/10 + player.frame_rate % 10 + + ////////////////////////// + // color space + on player.color_space != "" + on print_clrs == 1 + print_osd = "Color Space" + add text osdclrs + osdclrs.group = "osd" + osdclrs.x = osd_x + osd_offx + osdclrs.y = osd_iy + osdclrs.backcolor = osdback.backcolor + osd_iy = osd_iy + osd_h + + on player.color_space != "" || print_clrs == 1 + osdclrs.value = ": " + player.color_space + +//////////////////////////////////////////// +// File size formatting: + +on filesize_value < 1000 && filesize_value >= 0 + filesize_string = filesize_value + " bytes" +on filesize_value < 1000000 && filesize_value >= 1000 + filesize_string = (filesize_value/1000) + ","+ ((filesize_value%1000)/100)+((filesize_value%100)/10)+(filesize_value%10)+" bytes" +on filesize_value < 1024000 && filesize_value >= 1000000 + filesize_string = (filesize_value/1024) + " Kbytes" +on filesize_value < 1024000000 && filesize_value >= 1024000 + filesize_string = (filesize_value/1024000) + ","+ (((filesize_value/1024)%1000)/100)+(((filesize_value/1024)%100)/10)+((filesize_value/1024)%10)+" Kbytes" +on filesize_value < 1048576000 && filesize_value >= 1024000000 + filesize_string = (filesize_value/1048576) + " Mbytes" +on filesize_value >= 1048576000 + filesize_string = (filesize_value/1048576000) + ","+ (((filesize_value/1048576)%1000)/100)+(((filesize_value/1048576)%100)/10)+((filesize_value/1048576)%10)+" Mbytes" +// if filesize if negative, it means the huge file, the size is in kilos. +on filesize_value < 0 + filesize_string = (-filesize_value/1048576) + "," + (((-filesize_value/1024)%1000)/100) + " Gbytes" diff --git a/mmsl/popup.mmsl b/mmsl/popup.mmsl new file mode 100644 index 0000000..1e8c3ab --- /dev/null +++ b/mmsl/popup.mmsl @@ -0,0 +1,43 @@ +auto_close_popup = 1 +cancel_popup = 0 + +on show_popup_text != "" + add image popup + popup.group = "popup" + popup.halign = "center" + popup.valign = "center" + popup.src = "img/player/popup.gif" + popup.x = (screen.right + screen.left) / 2 + popup.y = (screen.bottom + screen.top) / 2 - 20 + screen.backcolor = -1 + add text header + header.group = "popup" + header.font = font2 + header.valign = "center" + header.x = popup.x - 185 + header.y = popup.y - 83 + header.value = "Message" + stext_x = popup.x - 170 + stext_y1 = popup.y - 42 + add text popuptext + popuptext.group = "popup" + popuptext.x = stext_x + popuptext.y = stext_y1 + popuptext.font = msgfont + popuptext.value = show_popup_text + popuptext.width = 335 + popuptext.height = 120 + popuptext.timer = popup_timer + + on pad.key == "cancel" + cancel_popup = 1 + +on auto_close_popup == 1 + on popuptext.timer == 0 + cancel_popup = 1 + +on cancel_popup == 1 + delete .group == "popup" + show_popup_text = "" + auto_close_popup = 1 + diff --git a/mmsl/search.mmsl b/mmsl/search.mmsl new file mode 100644 index 0000000..2ea43f4 --- /dev/null +++ b/mmsl/search.mmsl @@ -0,0 +1,367 @@ +//////////////////////////// +// search: +on player.menu == 0 && (player.speed == 0 || player.speed == 1) && player.playing == 1 + on pad.key == "search" + do_search = 1 + +on player.menu == 1 + on pad.key == "search" + show_fast_balloon = "player/invalid" + +on search == 0 + on cancel_search == 1 + cancel_search = 0 + on do_search == 1 + cancel_search = 0 + do_search = 0 + old_allow_osd = allow_osd + allow_osd = 0 + old_allow_zoom = allow_zoom + allow_zoom = 0 + search_old_in_menu = in_menu + in_menu = 2 + search = 1 + show_balloon = "" + player.command = "pause" + add image popup + popup.group = "search" + popup.halign = "center" + popup.valign = "center" + popup.src = "img/player/popup.gif" + popup.x = (screen.right + screen.left) / 2 + popup.y = (screen.bottom + screen.top) / 2 - 20 + screen.backcolor = -1 + add text header + header.group = "search" + header.valign = "center" + header.font = font2 + header.x = popup.x - 185 + header.y = popup.y - 83 + header.value = search_title + stext_x = popup.x - 50 + stext_y1 = popup.y - 42 + stext_y2 = stext_y1 + 20 + + stext_y3 = stext_y1 + stext_y4 = stext_y2 + + stext_yr = stext_y1 + 60 + add rect srect + srect.group = "search" + srect.backcolor = colors.white + srect.linewidth = 0 + + screen.halign = "right" + draw_titles = 1 + add text t; t.group = "search"; t.x = stext_x; t.y = stext_y3; t.value = "Time :" + add text tt; tt.group = "search"; tt.x = stext_x; tt.y = stext_y4; tt.value = "Total Time :" + + screen.halign = "left" + stext_x = stext_x + 20 + + rlength_width = 310 + rlength_height = 10 + + add rect rlength + rlength.group = "search" + rlength.width = rlength_width + 4 + rlength.x = popup.x - 180 + rlength.y = stext_yr + rlength.height = rlength_height + 4 + rlength.linewidth = 1 + rlength.color = colors.white + rlength.backcolor = -1 + + add rect rtime + rtime.group = "search" + rtime.width = rlength_width + rtime.x = rlength.x + 2 + rtime.y = stext_yr + 2 + rtime.height = rlength_height + rtime.linewidth = 0 + rtime.backcolor = colors.yellow + + draw_titles = 2 + + add text time; time.group = "search"; time.color = colors.yellow; time.x = stext_x; time.y = stext_y3; + old_cur_secs = player.time + cur_secs = player.time + + add text fulltime; fulltime.group = "search"; fulltime.x = stext_x; fulltime.y = stext_y4; + fulltime.value = "" + (player.length/36000) + ((player.length/3600)%10) + ":" + (player.length%3600/600) + ((player.length%3600/60)%10) + ":" + ((player.length%60)/10) + ((player.length%60)%10) + + screen.halign = "left" + + sy = 2 + sx = 0 + + draw_titles = 3 + + on set_digit >= 0 + kernel.print = "Jump to " + (set_digit * 10) + "%%!" + player.command = "pause" + player.time = player.length * set_digit / 10 + player.command = "play" + +on drive.mediatype == "dvd" + on draw_titles == 1 + draw_titles = 0 + add text t; t.group = "search"; t.x = stext_x; t.y = stext_y1; t.value = "Title/Total :" + add text ct; ct.group = "search"; ct.x = stext_x; ct.y = stext_y2; ct_value = "Chapter/Total :"; ct.value = ct_value; + stext_y3 = stext_y1 + 50 + stext_y4 = stext_y1 + 70 + stext_yr = stext_y1 + 110 + +on drive.mediatype == "dvd" + on draw_titles == 2 + draw_titles = 0 + add text title; title.group = "search"; title.color = colors.yellow; title.x = stext_x; title.y = stext_y1; + real_title = player.title + cur_title = player.title + + add text t; t.group = "search"; t.x = stext_x + title.width; t.y = stext_y1; t.value = " / " + (player.num_titles/10) + (player.num_titles%10) + + add text chapter; chapter.group = "search"; chapter.color = colors.yellow; chapter.x = stext_x; chapter.y = stext_y2; + cur_chapter = player.chapter + + add text numch; numch.group = "search"; numch.x = stext_x + chapter.width; numch.y = stext_y2; + num_chapters = player.num_chapters + +on drive.mediatype == "dvd" + on draw_titles == 3 + draw_titles = 0 + sy = 1 + sx = 1 - (cur_chapter > 9) + +on search == 1 + on do_search == 1 || pad.key == "cancel" + cancel_search = 1 + on player.playing == 1 + on cancel_search == 1 + player.command = "cancel" // cancel all changes + player.command = "play" + on cancel_search == 1 + delete .group == "search" + in_menu = search_old_in_menu + allow_osd = old_allow_osd + allow_zoom = old_allow_zoom + cancel_search = 0 + search = 0 + + on pad.key == "enter" + delete .group == "search" + player.command = "play" + in_menu = search_old_in_menu + allow_osd = old_allow_osd + allow_zoom = old_allow_zoom + search = 0 + + // update time + on player.length > 0 + on cur_secs > player.length + cur_secs = old_cur_secs + on cur_secs >= 0 + old_cur_secs = cur_secs + s_h1 = (cur_secs/36000) + s_h2 = ((cur_secs/3600)%10) + s_m1 = (cur_secs%3600/600) + s_m2 = ((cur_secs%3600/60)%10) + s_s1 = ((cur_secs%60)/10) + s_s2 = ((cur_secs%60)%10) + time.value = "" + s_h1 + s_h2 + ":" + s_m1 + s_m2 + ":" + s_s1 + s_s2 + rtime.width = cur_secs * rlength_width / player.length + on cur_secs != player.time + on cur_secs >= 0 + player.time = cur_secs + + on update_cur_secs == 1 + cur_secs = (s_h1 * 10 + s_h2) * 3600 + (s_m1 * 10 + s_m2) * 60 + (s_s1 * 10 + s_s2) + + on player.time >= 0 && player.time <= player.length + cur_secs = player.time + on player.length > 0 + on player.time >= 0 && player.time > player.length + cur_secs = player.length + + on cur_title >= 0 + s_t1 = (cur_title/10) + s_t2 = (cur_title%10) + title.value = "" + s_t1 + s_t2 + + on cur_title != player.title + on cur_title >= 0 + player.title = cur_title + + on cur_title != real_title + fulltime.visible = 0 + time.visible = 0 + tt.visible = 0 + + on cur_title == real_title + fulltime.visible = 1 + time.visible = 1 + tt.visible = 1 + + on update_cur_title == 1 + cur_title = s_t1 * 10 + s_t2 + + on cur_chapter >= 0 + s_c1 = (cur_chapter/10) + s_c2 = (cur_chapter%10) + chapter.value = "" + s_c1 + s_c2 + on cur_chapter != player.chapter + on cur_chapter >= 0 + player.chapter = cur_chapter + + on update_cur_chapter == 1 + cur_chapter = s_c1 * 10 + s_c2 + + on player.chapter >= 0 + cur_chapter = player.chapter + + on num_chapters >= 0 + numch.value = " / " + (num_chapters/10) + (num_chapters%10) + + on player.num_chapters >= 0 + num_chapters = player.num_chapters + + on pad.key == "up" + move_up = 1 + on pad.key == "down" + move_down = 1 + + // draw cursor + char_w = 10 + on sy == 0 + on sx >= 0 + srect.x = title.x + sx * char_w + srect.width = char_w+1 + srect.y = title.y+1 + srect.height = title.height-2 + on pad.key == "right" || pad.key == "left" + sx = 1 - sx + on move_down == 1 + move_down = 0 + sy = 1 + sx = 1 + on sx == 0 + on set_digit >= 0 + s_t1 = set_digit + set_digit = -1 + update_cur_title = 1 + sx = sx + 1 + on sx == 1 + on set_digit >= 0 + s_t2 = set_digit + update_cur_title = 1 + on sy == 1 + on sx >= 0 + srect.x = chapter.x + sx * char_w + srect.width = char_w+1 + srect.y = chapter.y+1 + srect.height = chapter.height-2 + on pad.key == "right" || pad.key == "left" + sx = 1 - sx + on move_up == 1 + move_up = 0 + sy = 0 + sx = 1 + on move_down == 1 + move_down = 1 + sy = 2 + sx = 0 + on sx == 0 + on set_digit >= 0 + s_c1 = set_digit + set_digit = -1 + update_cur_chapter = 1 + sx = sx + 1 + on sx == 1 + on set_digit >= 0 + s_c2 = set_digit + update_cur_chapter = 1 + on sy == 2 + on sx < 5 + on pad.key == "right" + sx = sx + 1 + on sx > 0 + on pad.key == "left" + sx = sx - 1 + on drive.mediatype == "dvd" // move to chapters/titles only for DVD + on move_up == 1 + move_up = 0 + sy = 1 + sx = 1 + on sx == 0 || sx == 1 + srect.x = time.x + sx * char_w + srect.width = char_w+1 + srect.y = time.y+1 + srect.height = time.height-2 + on sx == 2 || sx == 3 + srect.x = time.x + 7 + sx * char_w + srect.width = char_w+1 + srect.y = time.y+1 + srect.height = time.height-2 + on sx == 4 || sx == 5 + srect.x = time.x + 14 + sx * char_w + srect.width = char_w+1 + srect.y = time.y+1 + srect.height = time.height-2 + on sx == 0 + on set_digit >= 0 + s_h1 = set_digit + set_digit = -1 + update_cur_secs = 1 + sx = sx + 1 + on sx == 1 + on set_digit >= 0 + s_h2 = set_digit + set_digit = -1 + update_cur_secs = 1 + sx = sx + 1 + on sx == 2 + on set_digit >= 0 && set_digit <= 5 + s_m1 = set_digit + set_digit = -1 + update_cur_secs = 1 + sx = sx + 1 + on sx == 3 + on set_digit >= 0 + s_m2 = set_digit + set_digit = -1 + update_cur_secs = 1 + sx = sx + 1 + on sx == 4 + on set_digit >= 0 && set_digit <= 5 + s_s1 = set_digit + set_digit = -1 + update_cur_secs = 1 + sx = sx + 1 + on sx == 5 + on set_digit >= 0 + s_s2 = set_digit + set_digit = -1 + update_cur_secs = 1 + +on player.playing == 1 + on pad.key == "zero" + set_digit = 0 + on pad.key == "one" + set_digit = 1 + on pad.key == "two" + set_digit = 2 + on pad.key == "three" + set_digit = 3 + on pad.key == "four" + set_digit = 4 + on pad.key == "five" + set_digit = 5 + on pad.key == "six" + set_digit = 6 + on pad.key == "seven" + set_digit = 7 + on pad.key == "eight" + set_digit = 8 + on pad.key == "nine" + set_digit = 9 + diff --git a/mmsl/setup.mmsl b/mmsl/setup.mmsl new file mode 100644 index 0000000..3705f15 --- /dev/null +++ b/mmsl/setup.mmsl @@ -0,0 +1,688 @@ +/* Setup structure: + PAGE 1: + y=0 Analog(1) Digital(2) + y=1 Letterbox(3) Panscan(4) Wide(5) + y=2 C/S-Video(6) YPbPr(7) RGB(8) + y=3 PAL(9) NTSC(10) + y=4 OSD DVD-Menu DVD-Audio DVD-Sub + y=5 En(11) Ru(12) De(13) Fr(14) Es(15) Pl(16) + + settings.user1 - OSD language + settings.user2 - Auto detect PAL/NTSC + settings.user3 - FS charset + settings.user4 - subtitle charset + settings.user5 - screensaver time, in seconds + settings.user6 - photo slide show time, in seconds + settings.user7 - sleep timer, in minutes +*/ + +kernel.print = "Entering SETUP..." + +setup_page = 1 + +on setup_page == 1 + delete .group != "mute" && .group != "scr" + screen.back = "img/setup1.jpg" + adjustment = 0 + + add image ssel1; ssel1.group = "setup"; ssel1.src = "img/yes.gif" + add image ssel2; ssel2.group = "setup"; ssel2.src = "img/yes.gif" + add image ssel3; ssel3.group = "setup"; ssel3.src = "img/yes.gif" + add image ssel4; ssel4.group = "setup"; ssel4.src = "img/yes.gif" + add image ssel5; ssel5.group = "setup"; ssel5.src = "img/yes.gif" + ssel4.visible = 0 + ssel5.visible = 0 + + add rect langrect + langrect.group = "setup" + langrect.width = 109 + langrect.height = 21 + langrect.backcolor = colors.yellowback + langrect.color = -1 + langrect.round = 4 + langrect.visible = 0 + + load_settings = 1 + +on setup_page == 2 + delete .group != "mute" && .group != "scr" + screen.update = "now" + screen.back = "img/setup2.jpg" + + on display_setup_line != "" + add text s2text + s2text.group = "setup" + s2text.x = 70 + s2text.y = setup_line_i + s2text.color = colors.yellow + s2text.value = display_setup_line + setup_line_i = setup_line_i + s2text.height + + setup_line_i = 115 + display_setup_line = "Auto PAL/NTSC detect for video: " + + add text s2text1 + s2text1.group = "setup" + s2text1.x = s2text.x + s2text.width + s2text1.y = s2text.y + s2text1.color = colors.white + + display_setup_line = "Filesystem charset: " + + add text s2text2 + s2text2.group = "setup" + s2text2.x = s2text.x + s2text.width + s2text2.y = s2text.y + s2text2.color = colors.white + + display_setup_line = "Subtitle file charset: " + + add text s2text3 + s2text3.group = "setup" + s2text3.x = s2text.x + s2text.width + s2text3.y = s2text.y + s2text3.color = colors.white + + display_setup_line = "HD Component Out: " + + add text s2text4 + s2text4.group = "setup" + s2text4.x = s2text.x + s2text.width + s2text4.y = s2text.y + s2text4.color = colors.white + + display_setup_line = "HQ Photo Viewer: " + + add text s2text5 + s2text5.group = "setup" + s2text5.x = s2text.x + s2text.width + s2text5.y = s2text.y + s2text5.color = colors.white + + display_setup_line = "Photo Slide Show: " + + add text s2text55 + s2text55.group = "setup" + s2text55.x = s2text.x + s2text.width + s2text55.y = s2text.y + s2text55.color = colors.white + + display_setup_line = "SATA/PATA HDD Speed (needs restart): " + + add text s2text6 + s2text6.group = "setup" + s2text6.x = s2text.x + s2text.width + s2text6.y = s2text.y + s2text6.color = colors.white + + display_setup_line = "Screen Saver: " + + add text s2text7 + s2text7.group = "setup" + s2text7.x = s2text.x + s2text.width + s2text7.y = s2text.y + s2text7.color = colors.white + + display_setup_line = "Sleep Timer: " + + add text s2text8 + s2text8.group = "setup" + s2text8.x = s2text.x + s2text.width + s2text8.y = s2text.y + s2text8.color = colors.white + + load_settings = 2 + +on setup_page != 0 + sel_id = -1 + set_sel_id = -1 + + add rect srect + srect.group = "setup" + srect.width = 80 + srect.height = 25 + srect.backcolor = -1 + srect.color = colors.yellow + srect.round = 5 + srect.linewidth = 2 + +on setup_page == 1 + setup_y = 0 + setup_x = 0 + +on setup_page == 2 + setup_y = 0 + sel_id = 20 + +on cancel_setup == 1 + cancel_setup = 0 + setup = 0 + delete .group != "mute" && .group != "scr" + //screen.back = "" + +/////// load settings +on settings.audioout == "analog" + on load_settings == 1 + sel_id = 1 +on settings.audioout == "digital" + on load_settings == 1 + sel_id = 2 +on settings.tvtype == "letterbox" + on load_settings == 1 + sel_id = 3 +on settings.tvtype == "panscan" + on load_settings == 1 + sel_id = 4 +on settings.tvtype == "wide" + on load_settings == 1 + sel_id = 5 +on settings.tvout == "composite" + on load_settings == 1 + sel_id = 6 +on settings.tvout == "ypbpr" + on load_settings == 1 + sel_id = 7 +on settings.tvout == "rgb" + on load_settings == 1 + sel_id = 8 +on settings.tvstandard == "pal" + on load_settings == 1 + sel_id = 9 +on settings.tvstandard == "ntsc" + on load_settings == 1 + sel_id = 10 + +num_charsets = 2 + +on settings.tvstandard != "pal" && settings.tvstandard != "ntsc" + on load_settings == 2 + settings.user2 = 0 + s2text1.color = colors.lightgrey + +on settings.user2 == 0 + on load_settings == 2 + s2text1.value = "No" +on settings.user2 == 1 + on load_settings == 2 + s2text1.value = "Yes" +on settings.user3 == 0 + on load_settings == 2 + s2text2.value = "Win/ISO" +on settings.user3 == 1 + on load_settings == 2 + s2text2.value = "KOI8-R" +on settings.user4 == 0 + on load_settings == 2 + s2text3.value = "Win/ISO" +on settings.user4 == 1 + on load_settings == 2 + s2text3.value = "KOI8-R" + +on settings.tvstandard == "pal" || settings.tvstandard == "ntsc" + on load_settings == 2 + s2text4.value = "Off" +on settings.tvstandard == "480p" + on load_settings == 2 + s2text4.value = "480P" +on settings.tvstandard == "576p" + on load_settings == 2 + s2text4.value = "576P" +on settings.tvstandard == "720p" + on load_settings == 2 + s2text4.value = "720P" +on settings.tvstandard == "1080i" + on load_settings == 2 + s2text4.value = "1080i" + +on settings.hq_jpeg == 0 + on load_settings == 2 + s2text5.value = "No" +on settings.hq_jpeg == 1 + on load_settings == 2 + s2text5.value = "Yes" + +on settings.hdd_speed == "fastest" + on load_settings == 2 + s2text6.value = "Fastest" +on settings.hdd_speed == "limited" + on load_settings == 2 + s2text6.value = "Limited" +on settings.hdd_speed == "slow" + on load_settings == 2 + s2text6.value = "Slow" + +on settings.user5 < 60 + on load_settings == 2 + set_sel_id = -1 + s2text7.value = settings.user5 + " Secs" +on settings.user5 == 60 + on load_settings == 2 + set_sel_id = -1 + s2text7.value = "1 Min" +on settings.user5 > 60 + on load_settings == 2 + set_sel_id = -1 + s2text7.value = (settings.user5 / 60) + " Mins" +on settings.user5 == 0 + on load_settings == 2 + s2text7.value = "Off" + +on settings.user6 > 0 + on load_settings == 2 + set_sel_id = -1 + s2text55.value = settings.user6 + " Seconds" +on settings.user6 == 0 + on load_settings == 2 + s2text55.value = "Off" + +on settings.user7 > 0 + on load_settings == 2 + set_sel_id = -1 + s2text8.value = settings.user7 + " Mins" +on settings.user7 == 0 + on load_settings == 2 + s2text8.value = "Off" +////// move cursor +on osd == 0 + on pad.key == "right" + smove_x = 1 + on pad.key == "left" + smove_x = -1 + on pad.key == "up" + smove_y = -1 + on pad.key == "down" + smove_y = 1 + on pad.key == "enter" + set_sel = 1 + set_sel_id = sel_id + + on pad.key == "return" + settings.command = "defaults" + screen.tvout = settings.tvout + screen.tvstandard = settings.tvstandard + setup_page = setup_page + +//////////////////////////// +// OSD Info: +on pad.key == "osd" + do_setup_osd = 1 + +on osd == 0 + on do_setup_osd == 1 + do_setup_osd = 0 + do_osd = 1 + +on osd == 1 + on do_setup_osd == 1 + do_setup_osd = 0 + cancel_osd = 1 + on pad.key == "enter" + cancel_osd = 1 + + on osd_draw_header == 1 + osdback.backcolor = colors.lightblueback + osdhdr.backcolor = osdback.backcolor + osdhdr.value = "Player Information" + + on osd_draw_main == 1 + osd_ix = osdback.x + screen.halign = "center" + screen.font = msgfont + setup_print_osd = "*** SigmaPlayer Firmware (Technosonic version) ***" + setup_print_osd = "Copyright (c) SigmaPlayer Team, 2010." + screen.halign = "left" + osd_ix = osd_x + osd_iy = osd_iy + osd_h + setup_print_osd = "Firmware version" + setup_print_osd = "MMSL version" + setup_print_osd = "Chip version" + setup_print_osd = "Core frequency" + setup_print_osd = "Available memory" + setup_print_osd = "FLASH memory" + + osd_iy = osd_y + osd_h * 5 + osd_ix = osd_ix + 190 + setup_print_osd = ": " + kernel.firmware_version + setup_print_osd = ": " + kernel.mmsl_version + setup_print_osd = ": " + kernel.chip + setup_print_osd = ": " + kernel.frequency + " MHz" + filesize_value = kernel.free_memory + setup_print_osd = ": " + filesize_string + filesize_value = kernel.flash_memory + setup_print_osd = ": " + filesize_string + + screen.font = font1 + + on setup_print_osd != "" + print_osd = setup_print_osd + osd_iy = osd_iy + osd_h + +///////// pages: +on setup_page == 1 + on pad.key == "next" + cancel_osd = 1 + setup_page = setup_page + 1 +on setup_page == 2 + on pad.key == "prev" + cancel_osd = 1 + setup_page = setup_page - 1 + +on setup_page == 1 + on (setup_y == 0 || setup_y == 3) && setup_x + smove_x >= 0 && setup_x + smove_x <= 1 + on smove_x != 0 + setup_x = setup_x + smove_x + on (setup_y == 1 || setup_y == 2) && setup_x + smove_x >= 0 && setup_x + smove_x <= 2 + on smove_x != 0 + setup_x = setup_x + smove_x + on setup_y == 4 && setup_x + smove_x >= 0 && setup_x + smove_x <= 3 + on smove_x != 0 + setup_x = setup_x + smove_x + on setup_y == 5 && setup_x + smove_x >= 0 && setup_x + smove_x <= 5 + on smove_x != 0 + setup_x = setup_x + smove_x + // save lang.type selection + on setup_y == 4 + on smove_y == 1 + slang_setup_x = setup_x + setup_y = 5 + setup_x = 0 + smove_y = 0 + on setup_y == 5 + on smove_y == -1 + setup_y = 4 + setup_x = slang_setup_x + smove_y = 0 + on setup_y + smove_y >= 0 && setup_y + smove_y <= 5 + on smove_y != 0 + setup_y = setup_y + smove_y + setup_x = 0 + + // set selection + on setup_y == 0 || setup_y == 1 + on set_sel == 1 + sel_id = 1 + setup_y * 2 + setup_x + on setup_y == 2 || setup_y == 3 + on set_sel == 1 + sel_id = 6 + (setup_y - 2) * 3 + setup_x + on setup_y == 5 + on set_sel == 1 + sel_id = 11 + setup_x + + // set cursor + on setup_y == 0 + on setup_x >= 0 + srect.width = 59 + srect.height = 25 + srect.x = 109 + 203 * setup_x + srect.y = 115 + on setup_y >= 1 && setup_y <= 2 + on setup_x >= 0 + srect.width = 77 - (setup_x * 11) + srect.height = 25 + srect.x = 67 + 160 * setup_x + srect.y = 199 + 62 * (setup_y - 1) + on setup_y == 3 + on setup_x >= 0 + srect.width = 50 + srect.height = 25 + srect.x = 163 + 145 * setup_x + srect.y = 312 + on setup_y == 4 + on setup_x >= 0 + srect.width = 113 + srect.height = 25 + srect.x = 59 + 120 * setup_x + srect.y = 370 + langrect.x = srect.x + 2 + langrect.y = srect.y + 2 + langrect.visible = 1 + slang = setup_x + on setup_y != 4 && setup_y != 5 + langrect.visible = 0 + ssel5.visible = 0 + on setup_y == 5 + on setup_x >= 0 + srect.width = 50 + srect.height = 36 + srect.x = 121 + 62 * setup_x + srect.y = 406 + langrect.visible = 1 + +on setup_page == 2 + // set cursor + on setup_y >= 0 + srect.width = 430 + srect.height = 25 + srect.x = 65 + srect.y = 115 + s2text.height * setup_y + + on setup_y + smove_y >= 0 && setup_y + smove_y <= 8 + on smove_y != 0 + setup_y = setup_y + smove_y + sel_id = 20 + setup_y + set_tv = "" + +on slang == 0 + sel_id = settings.user1 + 11 // OSD lang +on slang == 1 + set_lang = settings.dvd_lang_menu +on slang == 2 + set_lang = settings.dvd_lang_audio +on slang == 3 + set_lang = settings.dvd_lang_spu + +on set_lang == "en" + sel_id = 11 +on set_lang == "ru" + sel_id = 12 +on set_lang == "de" + sel_id = 13 +on set_lang == "fr" + sel_id = 14 +on set_lang == "es" + sel_id = 15 +on set_lang == "pl" + sel_id = 16 + +// set selection cursor +on sel_id == 1 + scur_x = 114; scur_y = 101; + draw_sel = 1 +on sel_id == 2 + scur_x = 319; scur_y = 101; + draw_sel = 1 +on sel_id >= 3 && sel_id <= 5 + scur_x = 82 + 153 * (sel_id-3); scur_y = 188; + draw_sel = 2 +on sel_id >= 6 && sel_id <= 8 + scur_x = 80 + 154 * (sel_id-6); scur_y = 251; + draw_sel = 3 +on sel_id >= 9 && sel_id <= 10 + scur_x = 162 + 145 * (sel_id-9); scur_y = 303; + draw_sel = 4 +on sel_id >= 11 && sel_id <= 16 + scur_x = 120 + 62 * (sel_id-11); scur_y = 398; + draw_sel = 5 + +// draw selection +on draw_sel == 1 + ssel1.x = scur_x; ssel1.y = scur_y +on draw_sel == 2 + ssel2.x = scur_x; ssel2.y = scur_y +on draw_sel == 3 + ssel3.x = scur_x; ssel3.y = scur_y +on draw_sel == 4 + ssel4.x = scur_x; ssel4.y = scur_y + ssel4.visible = 1; +on draw_sel == 5 + ssel5.x = scur_x; ssel5.y = scur_y + ssel5.visible = 1 + +/////////////////// +// save settings +on set_sel_id == 1 + settings.audioout = "analog" +on set_sel_id == 2 + settings.audioout = "digital" +on set_sel_id == 3 + settings.tvtype = "letterbox" + allow_balloons = 0 + screen.tvout = settings.tvout + allow_balloons = 1 +on set_sel_id == 4 + settings.tvtype = "panscan" + allow_balloons = 0 + screen.tvout = settings.tvout + allow_balloons = 1 +on set_sel_id == 5 + settings.tvtype = "wide" + allow_balloons = 0 + screen.tvout = settings.tvout + allow_balloons = 1 +on set_sel_id == 6 + settings.tvout = "composite" + screen.tvout = settings.tvout + saved_tvout = settings.tvout +on set_sel_id == 7 + settings.tvout = "ypbpr" + screen.tvout = settings.tvout + saved_tvout = settings.tvout +on set_sel_id == 8 + settings.tvout = "rgb" + screen.tvout = settings.tvout + saved_tvout = settings.tvout +on set_sel_id == 9 + settings.tvstandard = "pal" + screen.tvstandard = settings.tvstandard +on set_sel_id == 10 + settings.tvstandard = "ntsc" + screen.tvstandard = settings.tvstandard +on slang == 0 + on set_sel_id >= 11 && set_sel_id <= 16 + settings.user1 = set_sel_id - 11 + set_lang = 1 + screen.font = font1 + sel_id = settings.user1 + 11 // OSD lang +on slang != 0 + on set_sel_id == 11 + save_lang = "en" + on set_sel_id == 12 + save_lang = "ru" + on set_sel_id == 13 + save_lang = "de" + on set_sel_id == 14 + save_lang = "fr" + on set_sel_id == 15 + save_lang = "es" + on set_sel_id == 16 + save_lang = "pl" +on slang == 1 + on save_lang != "" + settings.dvd_lang_menu = save_lang +on slang == 2 + on save_lang != "" + settings.dvd_lang_audio = save_lang +on slang == 3 + on save_lang != "" + settings.dvd_lang_spu = save_lang + +on settings.tvstandard == "pal" || settings.tvstandard == "ntsc" + on set_sel_id == 20 + settings.user2 = 1 - settings.user2 + load_settings = 2 + +on set_sel_id == 21 + settings.user3 = settings.user3 + 1 + load_settings = 2 + set_lang = 1 + +on set_sel_id == 22 + settings.user4 = settings.user4 + 1 + load_settings = 2 + set_lang = 1 + +on settings.tvstandard == "pal" || settings.tvstandard == "ntsc" || settings.tvstandard == "" + on set_sel_id == 23 + set_tv = "480p" +on settings.tvstandard == "480p" + on set_sel_id == 23 + set_tv = "576p" +on settings.tvstandard == "576p" + on set_sel_id == 23 + set_tv = "720p" +on settings.tvstandard == "720p" + on set_sel_id == 23 + set_tv = "1080i" +on settings.tvstandard == "1080i" + on set_sel_id == 23 + set_tv = "pal" + +on set_sel_id == 24 + settings.hq_jpeg = settings.hq_jpeg + 1 + load_settings = 2 + +on set_sel_id == 26 + settings.hdd_speed = "next" + load_settings = 2 + +on settings.user5 < 900 + on set_sel_id == 27 + u = settings.user5 + // 30 45 60 120 180 300 600 900 + settings.user5 = u + 15 + (u == 0) * 15 + (u > 45) * 45 + (u > 120) * 60 + (u > 180) * 180 + load_settings = 2 +on settings.user5 >= 900 + on set_sel_id == 27 + settings.user5 = 0 + load_settings = 2 + +on settings.user6 < 60 + on set_sel_id == 25 + u = settings.user6 + // 1 2 3 5 10 15 20 30 60 + settings.user6 = u + 1 + (u == 3) + (u >= 5) * 4 + (u > 15) * 5 + (u > 20) * 20 + load_settings = 2 +on settings.user6 >= 60 + on set_sel_id == 25 + settings.user6 = 0 + load_settings = 2 + +on settings.user7 < 120 + on set_sel_id == 28 + u = settings.user7 + // 15, 30, 45, 60, 90, 120 + settings.user7 = u + 15 + (u > 45) * 15 + load_settings = 2 +on settings.user7 >= 120 + on set_sel_id == 28 + settings.user7 = 0 + load_settings = 2 + +on set_tv == "pal" + settings.tvout = saved_tvout + s2text1.color = colors.white +on set_tv != "pal" && set_tv != "" + settings.tvout = "ypbpr" + settings.user2 = 0 + s2text1.color = colors.lightgrey +on set_tv != "" + set_sel_id = -1 + settings.tvstandard = set_tv + load_settings = 2 + show_timer_balloon = "wait" + screen.update = "now" + s2text4.timer = 1000 + +on set_tv != "" + on s2text4.timer == 0 + screen.tvout = settings.tvout + screen.tvstandard = settings.tvstandard + +on set_tv == "" + s2text4.timer = 0 + show_balloon = "" + +on settings.user3 >= num_charsets + settings.user3 = 0 +on settings.user4 >= num_charsets + settings.user4 = 0 diff --git a/mmsl/ssaver.mmsl b/mmsl/ssaver.mmsl new file mode 100644 index 0000000..bac6ba9 --- /dev/null +++ b/mmsl/ssaver.mmsl @@ -0,0 +1,145 @@ +scr_timeout = settings.user5 * 1000 +scr_timeout2 = settings.user7 * 60000 +scr_small_timeout = 300 +scr_img_timeout = 3000 + +on settings.user5 > -1 + scr_timeout = settings.user5 * 1000 + scr.timer = scr_timeout + +on settings.user7 > -1 + scr_timeout2 = settings.user7 * 60000 + scr2.timer = scr_timeout2 + +add rect scr + scr.group = "scr" + scr.visible = 0 + scr.timer = scr_timeout + +add text scr2 + scr2.group = "scr" + scr2.visible = 0 + scr2.timer = scr_timeout2 + +on sleep_timer == 1 + on pad.key != "" + pad.key = "" + sleep_timer = 0 + +on screensaver == 1 + on pad.key != "" + pad.key = "" + screensaver = 0 + scr2.timer = scr_timeout2 + +on screensaver == 0 + scrimg.visible = 0 + set_def_palette = 1 + screen.fullscreen = 0 + scr.visible = 0 + scr.timer = scr_timeout + +on sleep_timer == 0 + scr.timer = scr_timeout + scr2.timer = scr_timeout2 + cancel_popup = 1 + +on pad.key != "" + scr.timer = scr_timeout + scr2.timer = scr_timeout2 + +on drive.mediatype != "-" + scr.timer = scr_timeout + scr2.timer = scr_timeout2 + +on update_list_loop == 1 + on scr.timer == 0 + scr.timer = scr_small_timeout + on scr2.timer == 0 + scr2.timer = scr_small_timeout + +on (player.playing != 0 && player.speed != 0) || sleep_timer == 1 + on scr.timer == 0 + scr.timer = scr_timeout +on player.playing != 0 && player.speed != 0 + on scr2.timer == 0 + scr2.timer = scr_timeout2 + +on screensaver == 0 && scr_timeout > 0 && sleep_timer == 0 && flash.progress < 1 && (player.playing == 0 || player.speed == 0) + on scr.timer == 0 + screensaver = 1 +on screensaver == 0 && scr_timeout > 0 && sleep_timer == 0 && flash.progress < 1 && (player.playing == 0 || player.speed == 0 || do_play_audio == 1) + on pad.key == "program" + screensaver = 1 + +on sleep_timer == 0 && scr_timeout2 > 0 && flash.progress < 1 && (player.playing == 0 || player.speed == 0) + on scr2.timer == 0 + sleep_timer = 1 + +on screensaver == 1 + on sleep_timer == 1 + screensaver = 0 + + kernel.print = "Starting screensaver..." + + delete .group == "scr" && .type != "text" // trick to avoid deleting of sleep-timer + + add rect scr + scr.group = "scr" + scr.color = 0 + scr.x = 0 + scr.y = 0 + scr.width = 640 + scr.height = 480 + scr.color = 0 + scr.backcolor = 0 + scr.visible = 1 + screen.palette = "img/ssaver.act" + screen.palidx = 0 + screen.palalpha = 255 + + screen.update = "now" + screen.fullscreen = 1 + + add image scrimg + scrimg.group = "scr" + scrimg.src = "img/screensaver.gif" + scrimg.x = 320 - scrimg.width/2 + scrimg.y = 240 - scrimg.height/2 + scrimg.timer = scr_img_timeout + scrimg.visible = 1 + +on sleep_timer == 1 + kernel.print = "Sleep timer ELAPSED!" + continue_cnt = 6 + continue_msg = "Player will be turned off in " + popup_timer = 1000 + auto_close_popup = 0 + show_popup_text = "" + continue_msg + continue_cnt + " seconds" + add image cancel + cancel.group = "popup" + cancel.halign = "center" + cancel.valign = "center" + cancel.src = "img/player/cancel.gif" + cancel.x = popup.x + cancel.y = popup.y + 55 + + on continue_cnt <= 1 + on popuptext.timer == 0 + sleep_timer = 0 + kernel.power = 0 // TURN OFF! + + on continue_cnt > 1 + on popuptext.timer == 0 + continue_cnt = continue_cnt - 1 + popuptext.value = "" + continue_msg + continue_cnt + " seconds" + popuptext.timer = 1000 + + on cancel_popup == 1 + sleep_timer = 0 + +on screensaver == 1 + on scrimg.timer == 0 + scrimg.x = kernel.random % (640-scrimg.width) + scrimg.y = kernel.random % (480-scrimg.height) + scrimg.timer = scr_img_timeout diff --git a/mmsl/startup.mmsl b/mmsl/startup.mmsl new file mode 100644 index 0000000..25cb578 --- /dev/null +++ b/mmsl/startup.mmsl @@ -0,0 +1,284 @@ +// Sigmaplayer Firmware + +kernel.print = "F/W ver " + kernel.firmware_version +kernel.print = "MMSL ver " + kernel.mmsl_version +kernel.print = "TV: " + screen.tvstandard + +// define our colors: +colors.darkblue = 5 +colors.blueback = 6; // =15 +colors.lightblueback = 64; +colors.yellow = 245; +colors.yellowback = 242; +colors.grey = 153; +colors.lightgrey = 247; +colors.white = 255; + +set_def_palette = 1 + +on set_def_palette == 1 + screen.palette = "img/pal.act" + + // define transparent color: + screen.palidx = 0 + screen.palalpha = 0 + screen.palidx = 6 + screen.palalpha = 0x66 + screen.palidx = 15 + screen.palalpha = 0x80 + screen.palidx = 64 + screen.palalpha = 0xc0 + screen.palidx = 153 + screen.palalpha = 0x50 + screen.palidx = 242 + screen.palalpha = 0x80 + + screen.color = colors.white + screen.trcolor = 0 + +def_screen_back = "img/logontsc.jpg" +screen.back = def_screen_back +saved_tvout = settings.tvout +saved_display_pad = "" +allow_balloons = 1 + + + +//////////////////////////////////////////////////// +// Set fonts & charsets: +on set_lang == 1 + font1 = "fonts/West23.fnt" + font2 = "fonts/West36.fnt" + iso_charset = "iso8859-1" + subt_charset = "iso8859-1" +on settings.user3 == 0 // cp1251 + cyr_iso_charset = "cp1251" +on settings.user3 == 1 // koi8-r + cyr_iso_charset = "koi8-r" +on settings.user1 == 1 // 'ru' + on set_lang == 1 + font1 = "fonts/Cyr24.fnt" + font2 = "fonts/Cyr27.fnt" + iso_charset = cyr_iso_charset + subt_charset = cyr_iso_charset +on settings.user1 == 5 // 'pl' + on set_lang == 1 + font1 = "fonts/Central23.fnt" + font2 = "fonts/Central38.fnt" + iso_charset = "iso8859-2" + subt_charset = "iso8859-2" + +settings.user3 = settings.user3 +set_lang = 1 +msgfont = "fonts/Cyr24.fnt" +screen.font = font1 + +/// --- SigmaPlayer PATCH for Russian defaults: +check_default_lang = 1 +on settings.user16 == 0 + on check_default_lang == 1 + kernel.print = "Setting Russian defaults" + settings.user1 = 1 // 'ru' + settings.user16 = 1 + set_lang = 1 + screen.font = font1 +/// --- + +restore_timer_balloon = "" + +startup = 1 +redraw_iso = 0 +allow_zoom = 0 +allow_osd = 0 + +load_adjustments = 1 +adjustment = 0 +player_source_set = 0 + +// screen saver +include "ssaver.mmsl" + +//////////////////////////////////////////////////// +// tray open/close & media detect +on pad.key == "eject" + do_eject = 1 +on drive.tray == "open" + on startup == 1 + open_tray = 1 + + on do_eject == 1 + do_eject = 0 + pad.clear = "all" + pad.display = "CLOSE" + show_timer_balloon = "close" + drive.tray = "toggle" + +on drive.tray == "close" + on do_eject == 1 + do_eject = 0 + open_tray = 1 + drive.tray = "toggle" + +///////////////////////////// +// OPEN tray +on open_tray == 1 + last_tray = "open" + + cleanup_all = 1 + + pad.display = "OPEN" + show_static_balloon = "open" + +on last_tray == "open" + on drive.tray == "close" + last_tray = "close" + show_balloon = "" + pad.clear = "all" + pad.display = "CLOSE" + +on drive.tray != "open" + on drive.mediatype == "none" + dvd = 0 + iso = 0 + pad.clear = "all" + pad.display = "no dISC" + screen.preload = "" + +on cleanup_all == 1 + delete .group != "mute" && .group != "scr" + dvd = 0 + iso = 0 + setup = 0 + allow_zoom = 0 + cancel_zoom = 1 + cancel_osd = 1 + cancel_popup = 1 + adjustment = 0 + player_source_set = 0 + screen.update = 1 + pad.clear = "all" + screen.back = def_screen_back + screen.preload = "" // clear picture cache (memory saving) + + +////////////////////////////////////////////////////// +on pad.key == "setup" + do_setup = 1 +on drive.mediatype == "none" // && drive.tray != "open" + on setup == 0 + on do_setup == 1 + do_setup = 0 + setup = 1 + delete .group != "mute" && .group != "scr" + screen.back = def_screen_back + +//////////////////////////////////////////////////////////// +// volume and display adjustments, messages and balloons: + +include "adjust.mmsl" + + +/// +include "flash.mmsl" + +////////////////////////////////////////////////////// + +on drive.mediatype == "iso" || drive.mediatype == "audio" || drive.mediatype == "mixed" + dvd = 0 + cancel_setup = 1 + iso = 1 + +on redraw_iso == 1 + iso = 1 + +on iso == 1 + kernel.print = "Media: " + drive.mediatype + include "iso.mmsl" + +// clean-up if media changed - delete all objects +on drive.mediatype == "iso" || drive.mediatype == "audio" || drive.mediatype == "mixed" || redraw_iso == 1 + on iso == 0 + player.command = "stop" + playing = 0 + del_info = 1 + pad.clear = "all" + redraw_iso = 0 + cancel_search = 1 + cancel_osd = 1 + cancel_popup = 1 + adjustment = 0 + do_play_audio = 0 + do_play_video = 0 + do_show_photo = 0 + delete .group != "mute" && .group != "scr" + screen.preload = "" // clear picture cache (memory saving) + screensaver = 0 + sleep_timer = 0 + screen.update = 1 + +////////////////////////////////////////////////////// +// some DVD-related controls +on drive.mediatype == "dvd" + iso = 0 + cancel_setup = 1 + dvd = 1 + + on dvd == 0 + // always stop playing if media changed + player.command = "stop" + cancel_search = 1 + cancel_osd = 1 + cancel_popup = 1 + cancel_zoom = 1 + adjustment = 0 + delete .group != "mute" && .group != "scr" + screen.back = def_screen_back + screen.preload = "" // clear picture cache (memory saving) + allow_zoom = 0 + allow_osd = 0 + screen.update = 1 + + on pad.key == "play" + dvd = 1 + + on setup == 0 + on pad.key == "enter" + cleanup_all = 1 + drive.mediatype = "iso" + + on do_setup == 1 + do_setup = 0 + setup = 1 + +on dvd == 1 + kernel.print = "Media: DVD" + show_timer_balloon = "dvd" + include "dvd.mmsl" + +on setup == 1 + on do_setup == 1 + do_setup = 0 + cancel_setup = 1 + drive.mediatype = "" + include "setup.mmsl" + +//////////////////////////////////////////////////////////// +// include search dialog, common for DVD and file player: + +include "search.mmsl" + +//////////////////////////////////////////////////////////// +// include OSD info support, common for DVD and file player: + +include "osd.mmsl" + +//////////////////////////////////////////////////////////// +// include zoom/scroll support, common for DVD and file player: + +include "zoom.mmsl" + +//////////////////////////////////////////////////////////// +// include Text popup support: + +include "popup.mmsl" + diff --git a/mmsl/startup.mmso b/mmsl/startup.mmso new file mode 100644 index 0000000000000000000000000000000000000000..4051e547645258895d239091c3a4de5aa3a15891 GIT binary patch literal 81047 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½$^fX=3+j-J0(^a!sZX2(F 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 NcmPES_mPF7{{d@F=-vPT literal 0 HcmV?d00001 diff --git a/mmsl/zoom.mmsl b/mmsl/zoom.mmsl new file mode 100644 index 0000000..bd2d602 --- /dev/null +++ b/mmsl/zoom.mmsl @@ -0,0 +1,73 @@ +//////////////////////////// +// display zoom/scroll: + +zoom_mode = 0 + +on do_cancel_zoom == 1 + screen.update = 0 + screen.hzoom = 100 + screen.vzoom = 100 + screen.hscroll = 0 + screen.hscroll = 0 + screen.update = 1 + +on cancel_zoom == 1 + do_cancel_zoom = 1 + zoom_mode = 0 + +on allow_zoom == 1 + on pad.key == "zoom" + do_zoom = 1 + + on do_zoom == 1 + do_cancel_zoom = 1 + + on zoom_mode == 0 + on do_zoom == 1 + do_zoom = 0 + zoom_mode = 1 + show_fast_balloon = "zoomon" + on zoom_mode != 0 + on pad.key == "enter" + do_scroll = 1 + + on do_zoom == 1 + do_zoom = 0 + zoom_mode = 0 + show_fast_balloon = "zoomoff" + + on zoom_mode == 1 + on do_scroll == 1 + do_scroll = 0 + zoom_mode = 2 + show_fast_balloon = "scrollon" + on zoom_mode == 2 + on do_scroll == 1 + do_scroll = 0 + zoom_mode = 1 + show_fast_balloon = "zoomon" + + on zoom_mode == 2 + on pad.key == "left" + screen.hscroll = screen.hscroll - 30 + on pad.key == "right" + screen.hscroll = screen.hscroll + 30 + on pad.key == "up" + screen.vscroll = screen.vscroll - 30 + on pad.key == "down" + screen.vscroll = screen.vscroll + 30 + on zoom_mode == 1 + on pad.key == "left" + screen.update = 0 + screen.hzoom = "in" + screen.vzoom = "in" + screen.update = 1 + on pad.key == "right" + screen.update = 0 + screen.hzoom = "out" + screen.vzoom = "out" + screen.update = 1 + on pad.key == "up" + screen.vzoom = "in" + on pad.key == "down" + screen.vzoom = "out" diff --git a/src/COPYING b/src/COPYING new file mode 100644 index 0000000..d60c31a --- /dev/null +++ b/src/COPYING @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 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 Library 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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 Library General +Public License instead of this License. diff --git a/src/Makefile b/src/Makefile new file mode 100644 index 0000000..83a438f --- /dev/null +++ b/src/Makefile @@ -0,0 +1,138 @@ +######################################################################### +# +# SigmaPlayer source project - makefile +# \file Makefile +# \author bombur +# \version 0.3 +# \date 11.12.2008 4.05.2007 4.07.2004 +# +########################################################################## + + +########################################################################## + +MAIN_SRC := init.cpp + +PROJECT_SRC := \ + divx.c \ + module.cpp \ + module-dvd.cpp \ + module-init.cpp \ + audio.cpp \ + video.cpp \ + avi.cpp \ + mpg.cpp \ + bitstream.cpp \ + cdda.cpp \ + media.cpp \ + script.cpp \ + script-explorer.cpp \ + script-objects.cpp \ + script-player.cpp \ + player.cpp \ + subtitle.cpp \ + settings.cpp + +GUI_SRC := \ + gui/window.cpp \ + gui/console.cpp \ + gui/res.cpp \ + gui/jpeg.cpp \ + gui/giflib.cpp \ + gui/image.cpp \ + gui/rect.cpp \ + gui/text.cpp \ + gui/font.cpp + +MMSL_SRC := \ + mmsl/mmsl.cpp \ + mmsl/mmsl-file.cpp + +ifeq "$(PLAYER_MODEL)" "Technosonic" +SPCFLAGS += -DSP_PLAYER_TECHNOSONIC=1 +LIBSP_SPECIFIC := \ + libsp/MP/sp_module.cpp \ + libsp/MP/sp_fip.cpp \ + libsp/MP/sp_khwl.cpp \ + libsp/MP/sp_cdrom.cpp \ + libsp/MP/sp_eeprom.cpp \ + libsp/MP/sp_i2c.cpp +endif + +ifeq "$(PLAYER_MODEL)" "DreamX108" +SPCFLAGS += -DSP_PLAYER_DREAMX108=1 +LIBSP_SPECIFIC := \ + libsp/MP/sp_module.cpp \ + libsp/MP/sp_fip.cpp \ + libsp/MP/sp_khwl.cpp \ + libsp/MP/sp_cdrom.cpp \ + libsp/MP/sp_eeprom.cpp \ + libsp/MP/sp_i2c.cpp +endif + +ifeq "$(PLAYER_MODEL)" "Mecotek" +SPCFLAGS += -DSP_PLAYER_MECOTEK=1 +LIBSP_SPECIFIC := \ + libsp/MP/sp_module.cpp \ + libsp/MP/sp_fip.cpp \ + libsp/MP/sp_khwl.cpp \ + libsp/MP/sp_cdrom.cpp \ + libsp/MP/sp_eeprom.cpp \ + libsp/MP/sp_i2c.cpp +endif + +LIBSP_SRC := \ + libsp/sp_misc.cpp \ + libsp/sp_msg.cpp \ + libsp/sp_mpeg.cpp \ + libsp/sp_video.cpp \ + libsp/sp_khwl_colors.cpp \ + libsp/sp_flash.cpp \ + libsp/containers/string.cpp \ + libsp/containers/membin.cpp \ + $(LIBSP_SPECIFIC) + +PREBUILD := cd contrib/libjpeg && make && cd ../libmad && make && cd ../.. + +EXTERNAL_STATIC_LINKS_WITH := \ + contrib/libjpeg/libjpeg.a \ + contrib/libmad/.libs/libmad.a + +# contrib/memcpy.o \ +# contrib/memset.o \ +# contrib/setjmp.o \ +# contrib/longjmp.o \ +# contrib/strcmp.o \ +# contrib/strlen.o \ + + +SPINCLUDE = libsp/ +JPEGINCLUDE = contrib/libjpeg/ + +SPCFLAGS += -msoft-float -fno-exceptions -I$(SPINCLUDE) -I$(JPEGINCLUDE) +SPCXXFLAGS += -fpermissive -fno-rtti + +LOCAL_MAKEFILE := Makefile + +TARGET_TYPE := EXECUTABLE + +SRC := $(PROJECT_SRC) $(GUI_SRC) $(MMSL_SRC) $(LIBSP_SRC) + +USE_STD_LIB := 1 + +COMPILKIND = release + +PREPROCESSORFLAGS += -D__STDC_LIMIT_MACROS -DSP_ARM=1 -D_GNU_SOURCE + +MAKE_CLEAN = rm -fr *.gdb && cd contrib/libmad && make clean && cd ../.. +OTHER_CLEAN = contrib/memcpy.o contrib/memset.o contrib/setjmp.o contrib/strlen.o contrib/strcmp.o contrib/longjmp.o contrib/libjpeg/*.o contrib/libjpeg/*.a + +CROSS = arm-elf- +LDFLAGS = -elf2flt="-s262144 -r" -s --static +EXEFLAGS = -msoft-float -lutil -lstdc++ + + +include Makefile.inc + +#divx.o: divx.c +# arm-elf-gcc -I. -I$(SPINCLUDE) -c -O1 -o $@ $< diff --git a/src/Makefile.inc b/src/Makefile.inc new file mode 100644 index 0000000..891fa7e --- /dev/null +++ b/src/Makefile.inc @@ -0,0 +1,330 @@ +######################################################################### +# +# SigmaPlayer source project - makefile additional code +# \file Makefile.inc +# \author bombur +# \version 0.1 +# \date 4.07.2004 +# +########################################################################## + +ifndef DEPEND_FILE + DEPEND_FILE = Makefile.d +endif + +ifndef MAKEFILE_NAME + MAKEFILE_NAME = Makefile +endif + +ifndef LOCAL_MAKEFILE + LOCAL_MAKEFILE = $(MAKEFILE_NAME) +endif + +ifndef CCOMPILER + CCOMPILER := gcc +endif + +ifndef CXXCOMPILER + CXXCOMPILER := g++ +endif + +CC := $(CROSS)$(CCOMPILER) +CXX := $(CROSS)$(CXXCOMPILER) +LD := $(CROSS)$(LD) +OBJCOPY := $(CROSS)objcopy +AR := $(CROSS)ar + +ifndef LINKER + LINKER = $(CC) # must be = and not := +else + LINKER := $(CROSS)$(LINKER) +endif + +LOCALDIR = $(shell pwd) + +MAKEARGS = -f $(MAKEFILE_NAME) +PREPROCESSORFLAGS += $(SPCFLAGS) -I. -D_REENTRANT + +ASFLAGS = $(PREPROCESSORFLAGS) + +ifeq ($(findstring leakchecker, $(COMPILKIND)), leakchecker) +EXEFLAGS += `glib-config --libs glib` +PREPROCESSORFLAGS += -DUSE_LEAK_CHECKER=1 +endif + +ifeq ($(findstring withthreads, $(COMPILKIND)), withthreads) +PREPROCESSORFLAGS += -DWITH_THREADS=1 +LDFLAGS += -lpthread +endif + +ifeq ($(findstring withfeeblemm, $(COMPILKIND)), withfeeblemm) +PREPROCESSORFLAGS += -DWITH_FEEBLEMM=1 +endif + +ifeq ($(findstring withdl, $(COMPILKIND)), withdl) +EXEFLAGS += -ldl -rdynamic +endif + +ifeq ($(findstring kernel, $(COMPILKIND)), kernel) +PREPROCESSORFLAGS += -D__KERNEL__ -DMODULE -DEXPORT_SYMTAB +ifeq ($(findstring uclinux, $(COMPILKIND)), uclinux) +ifndef MUM_KI +MUM_KI=-I$(UCLINUX_KERNEL)/include +endif +ifndef KERNELSTYLE +KERNELSTYLE= \ + -fomit-frame-pointer \ + -fno-strict-aliasing \ + -fno-common \ + -pipe \ + -fno-builtin +PREPROCESSORFLAGS += -D__linux__ +endif +else +ifndef MUM_KI +MUM_KI=-I/usr/src/linux-$(shell uname -r)/include -I/usr/src/linux-2.4/include +endif +ifndef KERNELSTYLE +KERNELSTYLE= \ + -fomit-frame-pointer \ + -fno-strict-aliasing \ + -fno-common \ + -mpreferred-stack-boundary=2 \ + -pipe +endif +endif + +ifndef DISABLE_WARNINGS +CWARNINGS+= \ + -Wno-import \ + -Wunused \ + -Wimplicit \ + -Wmain \ + -Wreturn-type \ + -Wswitch \ + -Wtrigraphs \ + -Wchar-subscripts \ + -Wparentheses \ + -Wpointer-arith \ + -Wcast-align \ +# -Wuninitialized \ + -Wno-uninitialized + -O3 +else +CWARNINGS+= -O3 +endif + +CFLAGS += $(KERNELSTYLE) $(MUM_KI) + +else + +ifeq ($(findstring release, $(COMPILKIND)), release) + + PREPROCESSORFLAGS += -U_DEBUG -DNDEBUG + CFLAGS += -O3 + CXXFLAGS += -O3 + +else + + PREPROCESSORFLAGS += -D_DEBUG=1 + +ifeq "$(CCOMPILER)" "gcc" +ifeq "$(CROSS)" "" + CFLAGS += -gdwarf-2 -g3 + CXXFLAGS += -gdwarf-2 -g3 +endif +else + CFLAGS += -g + CXXFLAGS += -g +endif +endif + + +ifndef DISABLE_WARNINGS +COMMONWARNINGS+= \ + -Wundef \ + -Wall \ + -Wchar-subscripts \ + -Wsign-compare \ + -Wno-uninitialized \ + -O3 +# -Wmissing-prototypes \ +# -Wuninitialized \ +# -Werror +CWARNINGS:=$(COMMONWARNINGS) -Wnested-externs -Wmissing-declarations +CXXWARNINGS:=$(COMMONWARNINGS) -fcheck-new +endif + +ifndef USE_STD_LIB + PREPROCESSORFLAGS += -nostdinc +endif + +endif + +CFLAGS += $(PREPROCESSORFLAGS) $(CWARNINGS) +CXXFLAGS += $(PREPROCESSORFLAGS) $(SPCXXFLAGS) $(CXXWARNINGS) + +TARGET_TYPE := $(strip $(TARGET_TYPE)) + +ifeq "$(TARGET_TYPE)" "LIBRARY" +ifeq ($(findstring static, $(COMPILKIND)), static) +TARGET_TYPE := OBJECT_LIBRARY +else +TARGET_TYPE := SHARED_LIBRARY +endif +endif + +ifeq ($(findstring static, $(COMPILKIND)), static) +STATIC_LINKS_WITH += $(LINKS_WITH) +LDFLAGS += -static +else +ifeq ($(findstring implicit, $(COMPILKIND)), implicit) +IMPLICIT_LINKS_WITH := $(LINKS_WITH) +else +DEPENDS_ON += $(LINKS_WITH) +endif +endif + +ifdef OBJECT_LIBRARY_USE_AR +OBJECT_LIBRARY = $(AR) rc $@ $^ +else +OBJECT_LIBRARY = $(LD) -r $^ -o $@ +endif +SHARED_LIBRARY = $(LINKER) -shared $^ $(LDFLAGS) -o $@ +EXECUTABLE = $(LINKER) $^ $(LDFLAGS) $(EXEFLAGS) -o $@ +DEPEND_COMMAND = $(CC) $(MUM_KI) $(PREPROCESSORFLAGS) -MM -E $(SRC) $(MAIN_SRC) +BINARY_FILE = $(OBJCOPY) -O binary $< $@ + +define MICROCODE + $(SPASM) $^ -o $@ + $(SPBIN) -h $@ +endef + +COPY_FILE_LIST = sed 's/^.*://' | sed 's/\\$$//' | tr ' ' '\n' | sed '/^$$/d' | \ + sed '/^\//d' | \ + sed 's/^\([^/]\)/$(subst /,\/,$(LOCALDIR))\/\1/' | \ + sed '{ : rm_dotdot s/[^/]*\/\.\.\/// ; t rm_dotdot }' | sort -u | \ + xargs cp -f --parents --target-directory=$(1) + +COPY_FILE_LIST2 = sed 's/^.*://' | sed 's/\\$$//' | tr ' ' '\n' | sed '/^$$/d' | \ + sed '/^\//d' | sort -u | \ + xargs -l1 cp -f --parents --target-directory=$(1) + +REDUCE_DIR_TREE = cd $(1) ; x=1 ; while [ $$x -eq 1 ] ; \ + do x=0 ; [ `ls -1 | wc -l` -eq 1 -a -d `ls -1 | head -n 1` ] && \ + y=`ls -1` && mv $$y/* . && rmdir $$y && x=1 ; \ + done ; exit 0 + +SRC := $(strip $(SRC)) +MAIN_SRC := $(strip $(MAIN_SRC)) +OBJ := $(addsuffix .o, $(basename $(SRC))) +MAIN_OBJ := $(addsuffix .o, $(basename $(MAIN_SRC))) +EXE := $(basename $(MAIN_SRC)) +SUBDIR := $(dir $(STATIC_LINKS_WITH) $(IMPLICIT_LINKS_WITH) $(DEPENDS_ON)) + +ifneq ($(findstring clean, $(MAKECMDGOALS)) ,clean) + ifneq "$(SRC) $(MAIN_SRC)" " " + DEPEND := $(shell cat $(DEPEND_FILE) 2>/dev/null) + DEPEND := $(subst \, ,$(DEPEND)) + DEPEND := $(subst :, , $(DEPEND)) + DEPEND := $(filter-out %.o, $(DEPEND)) + DEPEND := $(SRC) $(MAIN_SRC) $(DEPEND) + DEPEND := $(sort $(DEPEND)) + endif +endif + +ifeq "$(TARGET_TYPE)" "OBJFILE" +TARGET := $(OBJ) +endif + +ifeq "$(TARGET_TYPE)" "EXECUTABLE" +TARGET := $(EXE) +endif + +all: prebuild $(SUBDIR) $(TARGET) postbuild + +local: prebuild $(TARGET) postbuild + +ifeq "$(TARGET_TYPE)" "SHARED_LIBRARY" +copy_shared: $(TARGET) $(SUBDIR) + [ -d $(LIBDIR) ] && cp -f $(TARGET) $(LIBDIR) +else +copy_shared: $(SUBDIR) +endif + +ifeq "$(TARGET_TYPE)" "OBJECT_LIBRARY" +$(TARGET): $(OBJ) $(STATIC_LINKS_WITH) $(EXTERNAL_STATIC_LINKS_WITH) + $(OBJECT_LIBRARY) +endif + +ifeq "$(TARGET_TYPE)" "SHARED_LIBRARY" +$(TARGET): $(OBJ) $(STATIC_LINKS_WITH) $(EXTERNAL_STATIC_LINKS_WITH) $(IMPLICIT_LINKS_WITH) + $(SHARED_LIBRARY) +endif + +ifeq "$(TARGET_TYPE)" "EXECUTABLE" +$(TARGET): %: %.o $(OBJ) $(STATIC_LINKS_WITH) $(EXTERNAL_STATIC_LINKS_WITH) $(IMPLICIT_LINKS_WITH) + $(EXECUTABLE) +endif + +ifeq "$(TARGET_TYPE)" "MICROCODE" +$(TARGET): $(SRC) + $(MICROCODE) +endif + +ifeq "$(TARGET_TYPE)" "BINARY_FILE" +$(TARGET): $(EXE) + $(BINARY_FILE) + +$(EXE): %: %.o $(OBJ) $(STATIC_LINKS_WITH) $(EXTERNAL_STATIC_LINKS_WITH) $(IMPLICIT_LINKS_WITH) + $(EXECUTABLE) +endif + +$(STATIC_LINKS_WITH): $(@D) + +$(IMPLICIT_LINKS_WITH): $(@D) + +$(DEPENDS_ON): $(@D) + +$(SUBDIR): + @ echo ---- Subdir $(SRCDIR)$@ + $(MAKE) $(MAKEARGS) -C $@ $(MAKECMDGOALS) SRCDIR=$(SRCDIR)$@ + +$(SRC): + +$(MAIN_SRC): + +prebuild: +ifneq "$(PREBUILD)" "" + @echo "*** Start prebuild step ***" + $(PREBUILD) + @echo "*** Finish prebuild step ***" +endif + +postbuild: +ifneq "$(POSTBUILD)" "" + @echo "*** Start postbuild step ***" + $(POSTBUILD) + @echo "*** Finish postbuild step ***" +endif + +$(DEPEND_FILE): $(DEPEND) $(LOCAL_MAKEFILE) +ifneq "$(TARGET_TYPE)" "MICROCODE" +ifneq "$(SRC) $(MAIN_SRC)" " " + $(DEPEND_COMMAND) | sed 's/:/: $(LOCAL_MAKEFILE)/' > $@ +endif +endif + +ifneq ($(findstring clean, $(MAKECMDGOALS)) ,clean) +-include $(DEPEND_FILE) +endif + +totalclean: + $(MAKE_CLEAN) + $(RM) $(OBJ) $(MAIN_OBJ) $(TARGET) $(DEPEND_FILE) $(OTHER_CLEAN) *~ + +clean: + $(RM) $(OBJ) $(MAIN_OBJ) $(TARGET) $(DEPEND_FILE) *~ + +%.o: %.S + $(CC) -x assembler-with-cpp -c -D__ASSEMBLY__ -o $@ $< diff --git a/src/audio.cpp b/src/audio.cpp new file mode 100644 index 0000000..9ccd1ba --- /dev/null +++ b/src/audio.cpp @@ -0,0 +1,1759 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - Audio player source file. + * \file audio.cpp + * \author bombur + * \version 0.1 + * \date 07.03.2007 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "script.h" +#include "player.h" +#include "media.h" + + +#define AUDIO_USE_FAST_OUTPUT +//#define AUDIO_USE_FAST_OUTPUT_PTR + +#ifdef INTERNAL_AUDIO_PLAYER + +#include + +#define AUDIO_INTERNAL +#include "audio.h" + +static bool audio_msg = true; +#define MSG if (audio_msg) msg + +static Audio *audio = NULL; +static int num_packets = 0, info_cnt = 0, max_info_cnt = 32; + +const int min_avg_num = 10; +const int mp3_mintmpbuf = 16384; +const int mp3_numtmpbuf = mp3_mintmpbuf*8; + +typedef struct AudioDir +{ + SPString path; + DIR *dir; +} AudioDir; + +static SPClassicList *audio_dir_list = NULL; +static SPDLinkedList *audio_prev_list = NULL; + + +#ifdef WIN32 +#define audio_lseek _lseeki64 +#else +#define audio_lseek lseek +#endif + + +// return big-endian 16-bit +static inline WORD mp3_scale(mad_fixed_t sample) +{ + // round + sample += (1L << (MAD_F_FRACBITS - 16)); + + // clip + if (sample >= MAD_F_ONE) + sample = MAD_F_ONE - 1; + else if (sample < -MAD_F_ONE) + sample = -MAD_F_ONE; + + // quantize + return (WORD)(((sample >> (MAD_F_FRACBITS + 1 - 8)) & 0xff) | ((sample >> 5) & 0xff00)); +} + +static int normal_mp3_output(BYTE *data, struct mad_pcm *pcm) +{ + unsigned int nchannels, nsamples; + mad_fixed_t const *left_ch, *right_ch; + + // pcm->samplerate contains the sampling frequency + nchannels = pcm->channels; + nsamples = pcm->length; + left_ch = pcm->samples[0]; + right_ch = pcm->samples[1]; + + WORD *d = (WORD *)data; + if (nchannels == 2) + { + while (nsamples--) + { + *d++ = mp3_scale(*left_ch++); + *d++ = mp3_scale(*right_ch++); + } + } else + { + while (nsamples--) + { + // output sample(s) in 16-bit signed big-endian PCM + *d++ = mp3_scale(*left_ch++); + } + } + return (DWORD)d - (DWORD)data; +} + +#ifdef AUDIO_USE_FAST_OUTPUT + +static const DWORD and1_mask = 0x1FE00000, and2_mask = 0x1FE000; + +static int fast_mp3_output(BYTE *data, struct mad_pcm *pcm) +{ + unsigned int nchannels, nsamples; + mad_fixed_t const *left_ch, *right_ch; + + // pcm->samplerate contains the sampling frequency + nchannels = pcm->channels; + nsamples = pcm->length; + left_ch = pcm->samples[0]; + right_ch = pcm->samples[1]; + + register DWORD and1 = and1_mask, and2 = and2_mask; + if (nchannels == 2) + { + DWORD *bd = (DWORD *)data; + register DWORD d, d1, d2; + + while (nsamples--) + { + d1 = *left_ch++; + d2 = *right_ch++; + d = (d1 & and1) >> 21; + d |= (d2 & and1) >> 5; + d |= (d2 & and2) << 11; + d |= (d1 & and2) >> 5; + *bd++ = d; + } + return (DWORD)bd - (DWORD)data; + } else + { + WORD *bw = (WORD *)data; + DWORD d, d1; + while (nsamples--) + { + d1 = *left_ch++; + d = (d1 & and1) >> 21; + d |= (d1 & and2) >> 5; + *bw++ = (WORD)d; + } + return (DWORD)bw - (DWORD)data; + } +} + +#endif + +//////////////////////////////////////////////////// + +Audio::Audio() +{ + audio_numbufs = 226; + audio_bufsize = 4608; + audio_bufidx = 0; + + audio_samplerate = audio_channels = -1; + samples_per_frame = 1152; + + audio_frame_number = 0; + + bitrate = 0; + out_bps = 1; // avoid div.zero + + avg_bitrate = 0; avg_num = 0; + filesize = 0; + cur_offset = 0; + cur_tmpoffset = 0; + length = 0; + seek_offset = 0; + + needs_resample = false; + resample_packet_size = 4096; + + mp3_tmpbuf = NULL; + mp3_tmppos = 0; mp3_tmpstart = 0; mp3_tmpread = 0; + mp3_want_more = false; + + ac3_scan_header = -1; + ac3_framesize = 0; + ac3_gatherinfo = true; + ac3_gather_always = false; // disabled by video player + + is_partial_packet = false; + is_partial_next_packet = false; + + cur_bitrate = cur_audio_format = -1; + cur_samples = cur_bits = cur_channels = -1; + + playing = false; + stopping = false; + wait = false; + fast = false; + fd = -1; + + saved_pts = 0; + + mux_buf = NULL; + mux_numbufs = 8; + mux_bufsize = 32768; + + audio_fmt = RIFF_AUDIO_UNKNOWN; + fmtex = NULL; + + max_info_cnt = 32; + + mp3_output = NULL; +} + +Audio::~Audio() +{ + SPSafeFree(mp3_tmpbuf); + SPSafeFree(mux_buf); + SPSafeFree(fmtex); +} + +DWORD Audio::GetBE(BYTE *buf, int offset, int numbytes) +{ + DWORD res = 0; + int nb_1 = numbytes - 1; + + buf += offset; + + for (int n = 0; n < numbytes; n++) + { + res |= (*buf++) << (nb_1-- << 3); + } + + return res; +} + +int Audio::SetVbrLength(int total_num_frames, int vbr_filesize) +{ + length = (int)(INT64(1000) * total_num_frames * samples_per_frame / audio_samplerate); + if (vbr_filesize > 0 && (filesize == 0 || vbr_filesize < filesize)) + filesize = vbr_filesize; + if (length > 0) + { + bitrate = (int)(filesize * 8000 / length); + } + if (fd >= 0) + { + script_totaltime_callback(length / 1000); + } + return 0; +} + +int Audio::ParseXingHeader(BYTE *buf, int len) +{ + if (audio_samplerate <= 0) + return -1; + int flags = GetBE(buf, 4, 4); + int offs = 8; + int total_num_frames = 0; + int vbr_filesize = 0; + if (flags & AUDIO_XING_HAS_NUMFRAMES) + { + if (offs + 4 > len) + return -1; + total_num_frames = GetBE(buf, offs, 4); + offs += 4; + } + if (flags & AUDIO_XING_HAS_FILESIZE) + { + if (offs + 4 > len) + return -1; + vbr_filesize = GetBE(buf, offs, 4); + offs += 4; + } + SetVbrLength(total_num_frames, vbr_filesize); + if (flags & AUDIO_XING_HAS_TOC) + { + int table_num = MIN(100, len - offs); + if (table_num < 1) + return -1; + toc_length = length; + seek_toc.SetN(table_num); + for (int i = 0; i < 100; i++, offs++) + seek_toc[i] = (DWORD)(filesize * GetBE(buf, offs, 1) / 256); + } + return 0; +} + +int Audio::ParseVBRIHeader(BYTE *buf, int len) +{ + if (audio_samplerate <= 0) + return -1; + if (len < 26) + return -1; + int total_num_frames = GetBE(buf, 14, 4); + int vbr_filesize = GetBE(buf, 10, 4); + SetVbrLength(total_num_frames, vbr_filesize); + + int table_num = GetBE(buf, 18, 2) + 1; + int scale = GetBE(buf, 20, 2); + int numb = GetBE(buf, 22, 2); + int num_toc_frames = GetBE(buf, 24, 2); + + table_num = MIN(table_num, (len - 26) / numb); + if (table_num < 1) + return 0; + + toc_length = (int)(INT64(1000) * table_num * num_toc_frames * samples_per_frame / audio_samplerate); + seek_toc.SetN(table_num); + DWORD last_pos = 0; + for (int i = 0; i < table_num; i++) + seek_toc[i] = last_pos = last_pos + GetBE(buf, 26 + i * numb, numb) * scale; + return 0; +} + +int Audio::ParseAC3Header(BYTE *buf, int len, bool fill_info) +{ + int sample_rate, channels, lfe; + + // fast&dirty way to get frame size + if (!fill_info) + return mpeg_parse_ac3_header(buf, len, NULL, NULL, NULL, NULL, &ac3_framesize); + + if (mpeg_parse_ac3_header(buf, len, &bitrate, &sample_rate, &channels, &lfe, &ac3_framesize) < 0) + return -1; + + if (bitrate > 0) + { + out_bps = bitrate / 8; + + if (filesize > 0 && length == 0) + { + int new_length = (int)(filesize * 8000 / bitrate); + if (new_length != length) + { + length = new_length; + script_totaltime_callback(length / 1000); + } + } + } + + if (fmtex == NULL) + { + fmtex = (RIFF_WAVEFORMATEX *)SPmalloc(sizeof(RIFF_WAVEFORMATEX)); + memset(fmtex, 0, sizeof(RIFF_WAVEFORMATEX)); + } + if (fmtex != NULL) + { + WORD nch = (WORD)((channels << 8) | lfe); + if (fmtex->n_samples_per_sec != (DWORD)sample_rate || fmtex->n_channels != nch) + { + fmtex->n_samples_per_sec = sample_rate; + fmtex->n_channels = nch; + audio_update_format_string(); + } + } + + if (!audio->ac3_gather_always) + audio->ac3_gatherinfo = false; + + return 0; +} + +int Audio::ParseMp3Packet(MpegPacket *packet, BYTE * &buf, int len) +{ + mp3_stream.error = MAD_ERROR_NONE; + + if (!wait && mp3_want_more) + { + int bl = len; + if (mp3_tmpread > 0) + { + mp3_tmpstart += mp3_tmpread; + if (mp3_tmpstart + mp3_mintmpbuf > mp3_numtmpbuf || + mp3_tmppos + bl > mp3_numtmpbuf) + { + mp3_tmppos -= mp3_tmpstart; + memcpy(mp3_tmpbuf, mp3_tmpbuf + mp3_tmpstart, mp3_tmppos); + cur_tmpoffset += mp3_tmpstart; + mp3_tmpstart = 0; + } + } + if ((bl > 0 && bl < mp3_numtmpbuf) || mp3_tmpread > 0) + { + if (mp3_tmppos + bl > mp3_numtmpbuf) + bl = mp3_numtmpbuf - mp3_tmppos; + if (bl <= 0) + bl = 0; + else + { + // TODO: optimize - remove this memcpy! + memcpy(mp3_tmpbuf + mp3_tmppos, buf, bl); + } + mp3_tmppos += bl; + mad_stream_buffer(&mp3_stream, mp3_tmpbuf + mp3_tmpstart, mp3_tmppos - mp3_tmpstart); + + mp3_want_more = false; + } + mp3_tmpread = 0; + } + do + { + mp3_stream.error = MAD_ERROR_NONE; + if (wait || mad_frame_decode(&mp3_frame, &mp3_stream) == 0) + { + if (!wait) + { + mp3_tmpstart = mp3_stream.this_frame - mp3_tmpbuf; + mp3_tmpread = mp3_stream.next_frame - mp3_stream.this_frame; + mp3_want_more = false; + } + + if (mpeg_find_free_blocks(MPEG_BUFFER_2) == 0) + { + // we'll wait... + wait = true; + return 0; + } + + wait = false; + + BYTE *data = mpeg_getcurbuf(MPEG_BUFFER_2); + mad_synth_frame(&mp3_synth, &mp3_frame); + + cur_offset = cur_tmpoffset + mp3_tmpstart; + + if (audio->avg_num == 0) + { + seek_offset = cur_offset; + if (filesize > 0) + filesize -= seek_offset; + int hdrlen = mp3_stream.bufend - mp3_stream.ptr.byte; + if (hdrlen > 4) + { + if (mp3_frame.header.layer == 0) + audio->samples_per_frame = 384; + else if (mp3_frame.header.layer == 2 && mp3_frame.header.flags & MAD_FLAG_LSF_EXT) + audio->samples_per_frame = 576; + else + audio->samples_per_frame = 1152; + audio_samplerate = mp3_synth.pcm.samplerate; + + if (strncasecmp((char *)mp3_stream.ptr.byte, "Xing", 4) == 0 || + strncasecmp((char *)mp3_stream.ptr.byte, "Info", 4) == 0) + { + audio->ParseXingHeader((BYTE *)mp3_stream.ptr.byte, hdrlen); + } + else if (strncasecmp((char *)mp3_stream.ptr.byte, "VBRI", 4) == 0) + { + audio->ParseVBRIHeader((BYTE *)mp3_stream.ptr.byte, hdrlen); + } + } + audio_update_format_string(); + } + + audio->avg_bitrate = audio->avg_bitrate * audio->avg_num + mp3_frame.header.bitrate; + audio->avg_bitrate /= ++audio->avg_num; + + if (length == 0 && audio->avg_bitrate > 0 && audio->avg_num >= min_avg_num) + { + if (filesize > 0) + { + length = (int)(filesize * 8000 / audio->avg_bitrate); + script_totaltime_callback(length / 1000); + } + + if ((audio->avg_num - min_avg_num) % 1000 == 0) + audio_update_format_string(); + } + + memset((BYTE *)packet + 4, 0, sizeof(MpegPacket) - 4); + + packet->pts = 0; + packet->nframeheaders = 0xffff; + packet->firstaccessunitpointer = 0; + packet->type = 1; + + packet->pData = data; + +#ifdef AUDIO_USE_FAST_OUTPUT_PTR + packet->size = mp3_output(data, &mp3_synth.pcm); +#else +#ifdef AUDIO_USE_FAST_OUTPUT + if (fast) + packet->size = fast_mp3_output(data, &mp3_synth.pcm); + else +#endif + packet->size = normal_mp3_output(data, &mp3_synth.pcm); +#endif + +#if 0 +{ +FILE *fp; +//fp = fopen("out.raw", "ab"); +//fwrite(packet->pData, packet->size, 1, fp); +fp = fopen("out.mp3", "ab"); +fwrite(mp3_stream.this_frame, mp3_tmpread, 1, fp); +fclose(fp); +} +#endif + + if (packet->size > 0) + { + if ((int)mp3_synth.pcm.samplerate != audio_samplerate || + mp3_synth.pcm.channels != audio_channels) + { + MpegAudioPacketInfo defaudioparams; + defaudioparams.type = eAudioFormat_PCM; + defaudioparams.samplerate = mp3_synth.pcm.samplerate; + defaudioparams.numberofbitspersample = 16; + defaudioparams.numberofchannels = mp3_synth.pcm.channels; + defaudioparams.fromstream = TRUE; + mpeg_setaudioparams(&defaudioparams); + + out_bps = defaudioparams.samplerate * defaudioparams.numberofbitspersample + * defaudioparams.numberofchannels / 8; + + audio_samplerate = mp3_synth.pcm.samplerate; + audio_channels = mp3_synth.pcm.channels; + } + + mpeg_setbufidx(MPEG_BUFFER_2, packet); + if (len == 0) + return 1; + return len; + } + } + else if (mp3_stream.error == MAD_ERROR_BUFLEN) + mp3_want_more = true; + } while (MAD_RECOVERABLE(mp3_stream.error)); + + return 0; +} + +int Audio::ParsePCMPacket(MpegPacket *packet, BYTE * &buf, int len) +{ + if (len <= 0) + return 0; + BYTE *out = buf; + int outlen = len, inlen = len; + + if (needs_resample) + { + if (mpeg_find_free_blocks(MPEG_BUFFER_2) == 0) + { + wait = true; + return 0; + } + + wait = false; + out = mpeg_getcurbuf(MPEG_BUFFER_2); + outlen = resample_packet_size; + int numread = mpeg_PCM_resample_to_LPCM(buf, inlen, out, &outlen); + if (numread <= 0) + return 0; + else + { + len = numread; + buf += numread; + } + } else + { + wait = false; + inlen = 0; + mpeg_PCM_to_LPCM(out, outlen); + } + + memset((BYTE *)packet + 4, 0, sizeof(MpegPacket) - 4); + packet->type = 1; + packet->pData = out; + packet->size = outlen; + packet->nframeheaders = 0xffff; + packet->firstaccessunitpointer = 0; + + // increase bufidx + mpeg_setbufidx(needs_resample ? MPEG_BUFFER_2 : MPEG_BUFFER_1, packet); + + return len; +} + +int Audio::ParseAC3Packet(MpegPacket *packet, BYTE * &buf, int len) +{ + if (len <= 0) + { + return 0; + } + + if (ac3_scan_header > 0 && len > 7 - ac3_scan_header) + { + memcpy(ac3_header + ac3_scan_header, buf, 7 - ac3_scan_header); + if (ParseAC3Header(ac3_header, 7, ac3_gatherinfo) < 0) + { + // frankly to say, the rest of saved header may contain the next sync.word, + // but we skip it to keep the code simple. + ac3_scan_header = -1; + } else + { + ac3_framesize -= ac3_scan_header; + ac3_scan_header = 0; + } + } + if (ac3_scan_header != 0) + { + BYTE *b = buf; + int l = len; + for (; l > 0; l--, b++) + { + if (*b == 0x0b) + { + if (l < 7) + { + ac3_scan_header = l; + memcpy(ac3_header, b, ac3_scan_header); + break; + } + if (ParseAC3Header(b, l, ac3_gatherinfo) == 0) + { + ac3_scan_header = 0; + ac3_framesize = (b - buf); + break; + } + } + } + } + + is_partial_packet = is_partial_next_packet; + + is_partial_next_packet = false; + if (ac3_scan_header == 0) + { + if (ac3_framesize >= 0) + { + int left = len; + BYTE *b = buf; + + if (ac3_framesize < left) + { + // limit packet to current framesize, if it is set + len = ac3_framesize; + + left -= ac3_framesize; + b += ac3_framesize; + ac3_framesize = 0; + // find *next* ac3 header + if (ParseAC3Header(b, left, ac3_gatherinfo) < 0) + { + if (left < 7) + { + // the header was just too short + ac3_scan_header = left; + memcpy(ac3_header, b, ac3_scan_header); + // send incomplete next header to the driver + len += left; + } else + { + // did not found, we'll try raw byte search... + msg("AC3: Warning! Frame sync lost!\n"); + ac3_scan_header = -1; + return 0; + } + } + if (len == 0) + { + len = Min(ac3_framesize, left); + if (ac3_framesize >= left) + ac3_framesize -= left; + } + audio_frame_number++; + } + else + { + ac3_framesize -= left; + is_partial_next_packet = true; + } + } + } + + memset((BYTE *)packet + 4, 0, sizeof(MpegPacket) - 4); + packet->type = 1; + packet->pData = buf; + packet->size = len; + +#if 0 +{ +FILE *fp; +fp = fopen("out.ac3", "ab"); +fwrite(packet->pData, packet->size, 1, fp); +fclose(fp); +} +#endif +#if 0 +{ BYTE *pd = packet->pData; +static int num_audio_p = 0; +msg("- [%d, %d] ---------------------------------------------", num_audio_p++, packet->size); + for (int kk = 0; kk < packet->size; kk++) + { + if ((kk % 16) == 0) msg("\n"); + msg("%02x ", *pd++); + + } +msg("\n---\n"); +fflush(stdout); +} +#endif + + packet->nframeheaders = 0xffff; + packet->firstaccessunitpointer = 0; + +#if 0 + if (packet->pts != 0) + msg("[%d,%d]\t\tsize=%d\t\tpts=%d\n", num_packets, packet->type, packet->size, (int)packet->pts); +#endif + + // increase bufidx + mpeg_setbufidx(MPEG_BUFFER_1, packet); + + return len; +} + +int Audio::ParseRawPacket(MpegPacket *packet, BYTE * &buf, int len) +{ + if (len == 0) + return 0; + + memset((BYTE *)packet + 4, 0, sizeof(MpegPacket) - 4); + packet->type = 1; + packet->pData = buf; + packet->size = len; + + packet->nframeheaders = 0xffff; + packet->firstaccessunitpointer = 0; + + // increase bufidx + mpeg_setbufidx(MPEG_BUFFER_1, packet); + return len; +} + +int Audio::Seek(LONGLONG pos, int msecs) +{ + if (pos < 0) + return -1; + khwl_stop(); + + audio_lseek(fd, pos + seek_offset, SEEK_SET); + cur_tmpoffset = pos; + + audio_reset(); + media_skip_buffer(NULL); + + mpeg_setpts(INT64(90) * msecs); + return audio_continue_play(); +} + + +/////////////////////////////////////////////////// + +RIFF_AUDIO_FORMAT audio_get_audio_format(int fmt) +{ + switch (fmt) + { + case 0x50: + return RIFF_AUDIO_MP2; + case 0x55: + return RIFF_AUDIO_MP3; + case 0x0001: + return RIFF_AUDIO_PCM; + case 0x2000: + return RIFF_AUDIO_AC3; + case 0x2001: + return RIFF_AUDIO_DTS; + case 0x674f: + case 0x676f: + case 0x6750: + case 0x6770: + case 0x6751: + case 0x6771: + return RIFF_AUDIO_VORBIS; + } + return RIFF_AUDIO_UNKNOWN; +} + +SPString audio_get_audio_format_string(RIFF_AUDIO_FORMAT fmt) +{ + if (fmt < 7) + { + const char *afmt[] = { "Unknown", "None", "LPCM", "PCM", "MPEGL1", "MPEGL2", "MP3", "AC3", "DTS", "Vorbis" }; + return afmt[fmt + 1]; + } + return SPString(); +} + +int audio_init(RIFF_AUDIO_FORMAT fmt, bool fast) +{ + if (audio != NULL) + audio_deinit(); + + audio = new Audio(); + audio->audio_fmt = fmt; + + bool need_2nd_buf = false; + + if (audio->audio_fmt == RIFF_AUDIO_MP3 || audio->audio_fmt == RIFF_AUDIO_MP2) + { + mad_stream_init(&audio->mp3_stream); + mad_frame_init(&audio->mp3_frame); + mad_synth_init(&audio->mp3_synth); + + audio->fast = fast; + if (fast) + { + audio->mp3_stream.options |= MAD_OPTION_HALFSAMPLERATE; + audio->mp3_output = &fast_mp3_output; + } else + { + audio->mp3_output = &normal_mp3_output; + } + + // allocate MP3 frame buffer + audio->mp3_tmpbuf = (BYTE *)SPmalloc(mp3_numtmpbuf); + if (audio->mp3_tmpbuf == NULL) + { + msg_error("Audio: Cannot allocate stream buffer.\n"); + return 0; + } + + fip_write_special(FIP_SPECIAL_MP3, 1); + + audio->audio_numbufs = 226; + audio->audio_bufsize = 4608; + need_2nd_buf = true; + } + else if (audio->audio_fmt == RIFF_AUDIO_PCM) + { + audio->needs_resample = mpeg_is_resample_needed() == TRUE; + + if (audio->needs_resample) + { + audio->audio_numbufs = 250; + audio->audio_bufsize = audio->resample_packet_size; + need_2nd_buf = true; + } + } + else if (audio->audio_fmt == RIFF_AUDIO_AC3) + { + fip_write_special(FIP_SPECIAL_DOLBY, 1); + } + else if (audio->audio_fmt == RIFF_AUDIO_DTS) + { + fip_write_special(FIP_SPECIAL_DTS, 1); + } + if (need_2nd_buf) + mpeg_setbuffer(MPEG_BUFFER_2, BUF_BASE, audio->audio_numbufs, audio->audio_bufsize); + + audio->audio_bufidx = 0; + + audio_reset(); + + audio_update_format_string(); + + return 1; +} + +int audio_reset() +{ + if (audio != NULL) + { + audio->mp3_want_more = true; + audio->wait = false; + audio->mp3_tmppos = 0; + audio->mp3_tmpstart = 0; + audio->mp3_tmpread = 0; + } + return 0; +} + +int audio_deinit() +{ + if (audio != NULL) + { + if (audio->audio_fmt == RIFF_AUDIO_MP3 || audio->audio_fmt == RIFF_AUDIO_MP2) + { + mad_synth_finish(&audio->mp3_synth); + mad_frame_finish(&audio->mp3_frame); + mad_stream_finish(&audio->mp3_stream); + } + } + SPSafeDelete(audio); + return 1; +} + +int audio_parse_packet(MpegPacket *packet, BYTE * &buf, int len) +{ + if (audio != NULL) + { + switch (audio->audio_fmt) + { + case RIFF_AUDIO_MP3: + case RIFF_AUDIO_MP2: + return audio->ParseMp3Packet(packet, buf, len); + case RIFF_AUDIO_PCM: + return audio->ParsePCMPacket(packet, buf, len); + case RIFF_AUDIO_AC3: + return audio->ParseAC3Packet(packet, buf, len); + default: + return audio->ParseRawPacket(packet, buf, len); + } + } + return 0; +} + +//////////////////////////////////////////////////////// + +int audio_play(char *filepath, bool is_folder, bool continue_next) +{ + if (is_folder) + { + if (filepath != NULL) + audio_delete_filelist(); + filepath = audio_get_next(filepath, continue_next); + if (filepath == NULL) + return -1; + } + + if (filepath == NULL) + { + fip_write_special(FIP_SPECIAL_PLAY, 1); + fip_write_special(FIP_SPECIAL_PAUSE, 0); + mpeg_setspeed(MPEG_SPEED_NORMAL); + return 0; + } + + if (strncasecmp(filepath, "/cdrom/", 7) != 0 && strncasecmp(filepath, "/hdd/", 5) != 0) + return -1; + + MPEG_NUM_PACKETS = 512; + mpeg_init(MPEG_1, TRUE, TRUE, FALSE); + + int ret = media_open(filepath, MEDIA_TYPE_AUDIO); + if (ret < 0) + { + msg_error("Audio: media open FAILED.\n"); + return ret; + } + MSG("Audio: start...\n"); + + num_packets = 0; + info_cnt = 0; + + audio->mux_buf = (BYTE *)SPmalloc(audio->mux_numbufs * audio->mux_bufsize); + if (audio->mux_buf == NULL) + { + msg_error("Audio: Cannot allocate input buffer.\n"); + return -1; + } + mpeg_setbuffer(MPEG_BUFFER_1, audio->mux_buf, audio->mux_numbufs, audio->mux_bufsize); + + // write dvd-specific FIP stuff... + const char *digits = " 00000"; + fip_write_string(digits); + fip_write_special(FIP_SPECIAL_COLON1, 1); + fip_write_special(FIP_SPECIAL_COLON2, 1); + + fip_write_special(FIP_SPECIAL_PLAY, 1); + fip_write_special(FIP_SPECIAL_PAUSE, 0); + + script_video_info_callback(""); + + audio->playing = true; + audio->stopping = false; + mpeg_start(); + + if (is_folder) + { + player_update_source(filepath); + } + + return 0; +} + +int audio_continue_play() +{ + fip_write_special(FIP_SPECIAL_PLAY, 1); + fip_write_special(FIP_SPECIAL_PAUSE, 0); + mpeg_play(); + return 0; +} + +void audio_update_info() +{ + if (audio == NULL) + return; + static int old_secs = 0; + KHWL_TIME_TYPE displ; + displ.pts = 0; + displ.timeres = 90000; + khwl_getproperty(KHWL_TIME_SET, etimSystemTimeClock, sizeof(displ), &displ); + + if ((LONGLONG)displ.pts != audio->saved_pts) + { + if ((LONGLONG)displ.pts >= 0) + { + audio->saved_pts = displ.pts; + + char fip_out[10]; + int secs = (int)(displ.pts / 90000); + if (secs < 0) + secs = 0; + if (secs >= 10*3600) + secs = 10*3600-1; + if (secs != old_secs) + { + script_time_callback(secs); + + fip_out[0] = ' '; + fip_out[1] = ' '; + fip_out[2] = (char)((secs/3600) + '0'); + int secs3600 = secs%3600; + fip_out[3] = (char)(((secs3600/60)/10) + '0'); + fip_out[4] = (char)(((secs3600/60)%10) + '0'); + fip_out[5] = (char)(((secs3600%60)/10) + '0'); + fip_out[6] = (char)(((secs3600%60)%10) + '0'); + fip_out[7] = '\0'; + fip_write_string(fip_out); + + old_secs = secs; + } + } + } +} + +/// Advance playing +int audio_loop() +{ + if (audio == NULL) + return 1; + + if (audio->stopping) + { + if (mpeg_wait(TRUE) == 1) + { + return audio_stop() ? 1 : 0; + } + } + + static BYTE *buf = NULL; + static MEDIA_EVENT event = MEDIA_EVENT_OK; + static int len = 0; + + if (!audio->stopping) + { + if (audio_ready()) + { + buf = NULL; + event = MEDIA_EVENT_OK; + len = 0; + int ret = media_get_next_block(&buf, &event, &len); + if (ret > 0 && buf == NULL) + { + msg_error("Audio: Not initialized. STOP!\n"); + return 1; + } + if (ret == -1) + { + msg_error("Audio: Error getting next block!\n"); + return 1; + } + else if (ret == 0) // wait... + { + return 0; + } + + if (event == MEDIA_EVENT_STOP) + { + MSG("Audio: STOP Event triggered!\n"); + mpeg_play_normal(); + audio->stopping = true; + return 0; + } + } + + // send multiple frames (packets) in one chunk + for (;;) + { + MpegPacket *packet = mpeg_feed_getlast(); + if (packet == NULL) // well, it won't really help + return 0; + + int numread = audio_parse_packet(packet, buf, len); + if (numread == 0) + break; + len -= numread; + buf += numread; + // increase bufidx + mpeg_feed(MPEG_FEED_AUDIO); + num_packets++; + } + } + + if (info_cnt++ > max_info_cnt) + { + info_cnt = 0; + audio_update_info(); + } + + return 0; +} + +BOOL audio_ready() +{ + return audio == NULL || !audio->wait; +} + +/// Pause playing +BOOL audio_pause() +{ + fip_write_special(FIP_SPECIAL_PLAY, 0); + fip_write_special(FIP_SPECIAL_PAUSE, 1); + + mpeg_setspeed(MPEG_SPEED_PAUSE); + + return TRUE; +} + +/// Stop playing +BOOL audio_stop(BOOL continue_next) +{ + if (audio != NULL) + { + if (audio->playing) + { + mpeg_deinit(); + audio->playing = false; + } + audio->stopping = false; + media_close(); + audio_deinit(); + + // check for file list + if (audio_dir_list != NULL) + { + if (audio_play(NULL, true, continue_next == TRUE) >= 0) + return FALSE; + } + } + return TRUE; +} + +int audio_get_bitrate() +{ + if (audio == NULL) + return 0; + // use known/VBR rate + if (audio->bitrate > 0) + return audio->bitrate; + // use average/CBR rate + if (audio->avg_bitrate > 0 && audio->avg_num >= min_avg_num) + return audio->avg_bitrate; + return 0; +} + +int audio_get_output_bps() +{ + if (audio == NULL) + return 0; + if (!audio->is_partial_packet) + return audio->out_bps; + return 0; +} + +/// Seek to given time and play +BOOL audio_seek(int seconds, int from) +{ + if (audio == NULL) + return FALSE; + + // use TOC to search + int msecs = seconds * 1000; + + if (from == SEEK_CUR) + { + // get current pts + int cur_msecs = (int)(mpeg_getpts() / 90); +#if 0 + // get stream pts + int br = audio_get_bitrate(); + if (br > 0) + cur_msecs = (int)(audio->cur_offset * 8000 / br); + else + { + script_error_callback(SCRIPT_ERROR_INVALID); + return FALSE; + } +#endif + msecs += cur_msecs; + } + + if (msecs < 0) + msecs = 0; + + LONGLONG pos = -1; + // use TOC + if (from == SEEK_SET && audio->seek_toc.GetN() > 0 && audio->toc_length > 0 && msecs < audio->toc_length) + { + int numtoc = audio->seek_toc.GetN(); + int idx = numtoc * msecs / audio->toc_length; + LONGLONG p1 = audio->seek_toc[idx]; + LONGLONG p2 = idx < numtoc - 1 ? audio->seek_toc[idx + 1] : audio->filesize; + int delta_toc = audio->toc_length / numtoc; + pos = p1 + (p2 - p1) * (msecs - idx * delta_toc) / delta_toc; + if (audio->bitrate > 0) + msecs = (int)(pos * 8000 / audio->bitrate); + } + // use known/VBR rate + else + { + int br = audio_get_bitrate(); + if (br > 0) + { + pos = (LONGLONG)msecs * br / 8000; + msecs = (int)(pos * 8000 / br); + } else + pos = -1; + } + + if (pos < 0) + { + script_error_callback(SCRIPT_ERROR_INVALID); + return FALSE; + } + return audio->Seek(pos, msecs) == 0; +} + +int audio_forward() +{ + audio_seek(10, SEEK_CUR); + return 0; +} +int audio_rewind() +{ + audio_seek(-10, SEEK_CUR); + return 0; +} + +void audio_setdebug(BOOL ison) +{ + audio_msg = ison == TRUE; +} +BOOL audio_getdebug() +{ + return audio_msg; +} + +// internal funcs used by media +BOOL audio_open(const char *filepath) +{ + if (audio != NULL && audio->fd != -1) + audio_close(); + + script_time_callback(0); + script_audio_info_callback(""); + script_video_info_callback(""); + + if (filepath == NULL) + return FALSE; + int fd = cdrom_open(filepath, O_RDONLY); + if (fd < 0) + { + msg_error("Audio: Cannot open file %s.\n", filepath); + return FALSE; + } + + struct stat64 statbuf; + LONGLONG filesize = 0, cur_offs = 0; + if (cdrom_stat(filepath, &statbuf) >= 0) + filesize = statbuf.st_size; + + // Read first 12 bytes and check that this is an AVI file + RIFF_AUDIO_FORMAT afmt = RIFF_AUDIO_UNKNOWN; + BYTE data[20]; + if (read(fd, data, 16) != 16) + { + msg_error("Audio: Cannot read header.\n"); + return FALSE; + } + RIFF_WAVEFORMATEX *fmtex = NULL; + if (strncasecmp((char *)data, "RIFF", 4) == 0 && strncasecmp((char *)data+8, "WAVE", 4) == 0) + { + MSG("Audio: * WAVE format detected!\n"); + // read WAVE header + if (strncasecmp((char *)data+12, "fmt ", 4) == 0) + { + fmtex = audio_read_formatex(fd, NULL, 0); + if (fmtex != NULL) + afmt = audio_get_audio_format(fmtex->w_format_tag); + } + for (;;) + { + read(fd, data, 8); + if (strncasecmp((char *)data, "data", 4) == 0) + break; + else if (strncasecmp((char *)data, "fact", 4) == 0) + { + int size = GET_DWORD(data + 4); + audio_lseek(fd, size, SEEK_CUR); + } + else + { + audio_lseek(fd, -8, SEEK_CUR); + break; + } + } + cur_offs = audio_lseek(fd, 0, SEEK_CUR); + if (filesize > 0) + filesize -= cur_offs; + } else + { + cur_offs = 0; + SPString fp = filepath; + if (fp.FindNoCase(".mp3") >= 0) + { + MSG("Audio: * MP3 format detected!\n"); + afmt = RIFF_AUDIO_MP3; + + // skip ID3v2 + if (strncasecmp((char *)data, "ID3", 3) == 0 && data[3] < 0xff && data[4] < 0xff) + { + int id3_size = GET_SYNCSAFE_DWORD(data + 6); + if (data[5] & 0x10) // footer present + id3_size += 10; + cur_offs = id3_size + 10; + } + } + else if (fp.FindNoCase(".mp2") >= 0) + { + MSG("Audio: * MPEGL2 format detected!\n"); + afmt = RIFF_AUDIO_MP2; + } + else if (fp.FindNoCase(".mp1") >= 0 || fp.FindNoCase(".mpa") >= 0) + { + MSG("Audio: * MPEGL1 format detected!\n"); + afmt = RIFF_AUDIO_MP1; + } + else if (fp.FindNoCase(".ac3") >= 0) + { + MSG("Audio: * AC-3 format detected!\n"); + afmt = RIFF_AUDIO_AC3; + } + else if (fp.FindNoCase(".ogg") >= 0) + { + MSG("Audio: * OGG Vorbis format detected!\n"); + afmt = RIFF_AUDIO_VORBIS; + } + + audio_lseek(fd, cur_offs, SEEK_SET); + } + + if (afmt == RIFF_AUDIO_UNKNOWN) + { + msg_error("Audio: Unknown audio format!\n"); + return FALSE; + } + + MSG("Audio: Setting audio params.\n"); + audio_setaudioparams(afmt, fmtex, 1); + + if (audio_init(afmt, false) == 0) + return -1; + + audio->fd = fd; + audio->fmtex = fmtex; + audio->filesize = filesize; + audio->cur_offset = 0; + + if (afmt == RIFF_AUDIO_LPCM || afmt == RIFF_AUDIO_PCM) + { + if (audio->fmtex != NULL) + { + audio->bitrate = (audio->fmtex->n_samples_per_sec + * audio->fmtex->w_bits_per_sample + * audio->fmtex->n_channels); + audio_update_format_string(); + if (audio->bitrate > 0) + { + audio->length = (int)(filesize * 8000 / audio->bitrate); + script_totaltime_callback(audio->length / 1000); + } + } + } + + return TRUE; +} + +BOOL audio_close() +{ + if (audio != NULL && audio->fd >= 0) + { + close(audio->fd); + audio->fd = -1; + return TRUE; + } + return FALSE; +} + +int audio_read(BYTE *buf, int *len) +{ + if (audio == NULL || buf == NULL || len == NULL || *len < 1) + return 0; + int n = 0, newlen = 0; + + while (newlen < *len) + { + n = read (audio->fd, buf + newlen, *len - newlen); + if (n == 0) + break; + if (n < 0) + { + if (errno == EINTR) + continue; + else + break; + } + newlen += n; + } + + if (newlen < 1) return 0; + *len = newlen; + return 1; +} + +RIFF_WAVEFORMATEX *audio_read_formatex(int fd, BYTE *buf, int len) +{ + RIFF_WAVEFORMATEX *wfe; + if (len <= 0) + { + if (read(fd, &len, 4) != 4) + return NULL; + } + if (len < 0 || len >= (long)sizeof(RIFF_WAVEFORMATEX)) + len = sizeof(RIFF_WAVEFORMATEX); + wfe = (RIFF_WAVEFORMATEX *)SPmalloc(sizeof(RIFF_WAVEFORMATEX)); + if (wfe != NULL) + { + memset(wfe, 0, sizeof(RIFF_WAVEFORMATEX)); + if (buf != NULL) + memcpy(wfe, buf, len); + else + { + if (read(fd, wfe, len) != len) + { + SPfree(wfe); + return NULL; + } + } + if (wfe->cb_size != 0) + { + wfe = (RIFF_WAVEFORMATEX *)SPrealloc(wfe, sizeof(RIFF_WAVEFORMATEX) + wfe->cb_size); + if (wfe != NULL) + { + if (read(fd, (BYTE *)wfe + sizeof(RIFF_WAVEFORMATEX), wfe->cb_size) != wfe->cb_size) + { + SPfree(wfe); + return NULL; + } + } + } + } + return wfe; +} + +void audio_update_format_string() +{ + if (audio != NULL && audio->audio_fmt != RIFF_AUDIO_UNKNOWN) + { + int br = audio_get_bitrate(); + if (br != audio->cur_bitrate || audio->audio_fmt != audio->cur_audio_format || (audio->fmtex != NULL + && ((int)audio->fmtex->n_samples_per_sec != audio->cur_samples || + (int)audio->fmtex->w_bits_per_sample != audio->cur_bits || + (int)audio->fmtex->n_channels != audio->cur_channels + ))) + { + audio->cur_audio_format = audio->audio_fmt; + audio->cur_bitrate = br; + if (audio->fmtex != NULL) + { + audio->cur_samples = audio->fmtex->n_samples_per_sec; + audio->cur_bits = audio->fmtex->w_bits_per_sample; + audio->cur_channels = audio->fmtex->n_channels; + } + + SPString fmt = audio_get_audio_format_string(audio->audio_fmt); + audio->audio_fmt_str = fmt; + if ((audio->audio_fmt == RIFF_AUDIO_PCM || audio->audio_fmt == RIFF_AUDIO_LPCM) + && audio->fmtex != NULL) + { + audio->audio_fmt_str.Printf(" @ %d Hz, %d bits %s", + audio->fmtex->n_samples_per_sec, + audio->fmtex->w_bits_per_sample, + audio->fmtex->n_channels > 1 ? "stereo" : "mono"); + } + else + { + if (br > 0) + audio->audio_fmt_str.Printf(" @ %d kbps", (br + 500) / 1000); + } + if (audio->audio_fmt == RIFF_AUDIO_AC3 && audio->fmtex != NULL) + { + audio->audio_fmt_str.Printf(", %d Hz, ", audio->fmtex->n_samples_per_sec); + if (audio->fmtex->n_channels == 0x0100) + audio->audio_fmt_str.Printf("mono"); + else if (audio->fmtex->n_channels == 0x0200) + audio->audio_fmt_str.Printf("stereo"); + else + audio->audio_fmt_str.Printf("%d.%d", (audio->fmtex->n_channels >> 8) & 0xff, + audio->fmtex->n_channels & 0xff); + } + MSG("Audio: * Audio format detected: %s.\n", *audio->audio_fmt_str); + script_audio_info_callback(audio->audio_fmt_str); + } + } +} + +int audio_setaudioparams(RIFF_AUDIO_FORMAT fmt, RIFF_WAVEFORMATEX *wfe, int halfrate) +{ + MpegAudioPacketInfo defaudioparams; + + // set audio format + switch (fmt) + { + case RIFF_AUDIO_MP2: + case RIFF_AUDIO_MP3: + defaudioparams.type = eAudioFormat_PCM; + if (wfe != NULL) + wfe->w_bits_per_sample = 16; + max_info_cnt = 20; + break; + case RIFF_AUDIO_AC3: + defaudioparams.type = eAudioFormat_AC3; + if (wfe != NULL) + wfe->w_bits_per_sample = 24; + max_info_cnt = 10; + break; +/* case RIFF_AUDIO_DTS: + defaudioparams.type = eAudioFormat_DTS; + break; +*/ + case RIFF_AUDIO_PCM: + case RIFF_AUDIO_LPCM: + defaudioparams.type = eAudioFormat_PCM; + max_info_cnt = 32; + break; + default: + msg_error("AVI: Audio format %d not supported!\n", fmt); + return -1; + } + + if (wfe != NULL) + { + if (wfe->n_samples_per_sec > 0 && wfe->w_bits_per_sample >= 8 && wfe->n_channels > 0) + { + defaudioparams.samplerate = wfe->n_samples_per_sec / halfrate; + defaudioparams.numberofbitspersample = wfe->w_bits_per_sample; + defaudioparams.numberofchannels = wfe->n_channels; + defaudioparams.fromstream = TRUE; + } else + return -1; + } else + { + defaudioparams.samplerate = 48000; + defaudioparams.numberofbitspersample = 24; + defaudioparams.numberofchannels = 2; + defaudioparams.fromstream = TRUE; + } + + if (mpeg_setaudioparams(&defaudioparams) < 0) + { + return -1; + } + + if (audio != NULL) + { + if (defaudioparams.type == eAudioFormat_AC3) + { + audio->out_bps = 0; // don't guess bitrate + audio->ac3_gatherinfo = true; + } + else + audio->out_bps = defaudioparams.samplerate * defaudioparams.numberofbitspersample + * defaudioparams.numberofchannels / 8; + + if (audio->fmtex == NULL && wfe != NULL) + { + int wfe_size = sizeof(RIFF_WAVEFORMATEX) + wfe->cb_size; + audio->fmtex = (RIFF_WAVEFORMATEX *)SPmalloc(wfe_size); + if (audio->fmtex != NULL) + { + memcpy(audio->fmtex, wfe, wfe_size); + audio_update_format_string(); + } + } + } + + return 0; +} + +void audio_set_ac3_getinfo(bool always) +{ + if (audio != NULL) + audio->ac3_gather_always = always; +} + +//////////////////////////////////////////////////////// + +char *audio_get_next(char *filepath, bool get_next) +{ + static const char *audio_file_ext[] = { ".mp3", ".mp2", ".mp1", ".mpa", ".ac3", ".wav", ".ogg", NULL }; + static char tmp_path[4096]; + + if (audio_dir_list == NULL) + { + if (filepath == NULL) + return NULL; + audio_dir_list = new SPClassicList(); + audio_prev_list = new SPDLinkedList(); + if (audio_dir_list == NULL || audio_prev_list == NULL) + return NULL; + audio_dir_list->Reserve(20); + audio_prev_list->SetSize(10); + tmp_path[0] = '\0'; + AudioDir ad; + ad.dir = NULL; + ad.path = filepath; + audio_dir_list->Add(ad); + } + + if (audio_dir_list->GetN() > 0) + { + if (!get_next) + { + if (audio_prev_list == NULL) + return NULL; + DLElement *el = audio_prev_list->GetLast(); + if (el == NULL) + return NULL; + strcpy(tmp_path, el->GetItem()); + audio_prev_list->Delete(el); + return tmp_path; + } + + for (;;) + { + AudioDir &ad = (*audio_dir_list)[audio_dir_list->GetN() - 1]; + if (ad.dir == NULL) + { + // scan for files + ad.dir = cdrom_opendir(ad.path); + } + if (ad.dir != NULL) + { + struct dirent *d = cdrom_readdir(ad.dir); + if (d == NULL) + { + cdrom_closedir(ad.dir); + audio_dir_list->Remove(audio_dir_list->GetN() - 1); + if (audio_dir_list->GetN() < 1) + break; + continue; + } + if (d->d_name[0] == '.' && d->d_name[1] == '\0') + continue; + + SPString path = ad.path + SPString("/") + SPString(d->d_name); + + struct stat64 statbuf; + if (cdrom_stat(path, &statbuf) < 0) + { + msg("Cannot stat %s\n", *path); + continue; + } + if (S_ISDIR(statbuf.st_mode)) + { + if (d->d_name[0] == '.') // hidden + continue; + AudioDir ad; + ad.dir = NULL; + ad.path = path; + audio_dir_list->Add(ad); + continue; + } + // check for file type + for (int k = 0; audio_file_ext[k] != NULL; k++) + { + if (strstr(d->d_name, audio_file_ext[k]) != NULL) + { + if (tmp_path[0] != '\0') + { + DLElement *el = new DLElement(tmp_path); + if (el == NULL) + return NULL; + audio_prev_list->Add(el); + } + + strcpy(tmp_path, path); + msg("Audio: Playing %s...\n", tmp_path); + return tmp_path; + } + } + } + else + break; + } + } + audio_delete_filelist(); + return NULL; +} + +void audio_delete_filelist() +{ + if (audio_dir_list != NULL) + { + for (int i = 0; i < audio_dir_list->GetN(); i++) + { + AudioDir &ad = (*audio_dir_list)[i]; + cdrom_closedir(ad.dir); + } + SPSafeDelete(audio_dir_list); + } + + if (audio_prev_list != NULL) + { + audio_prev_list->Delete(); + SPSafeDelete(audio_prev_list); + } + +} + + +#endif diff --git a/src/audio.h b/src/audio.h new file mode 100644 index 0000000..140b74f --- /dev/null +++ b/src/audio.h @@ -0,0 +1,264 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - Audio player (WAV/MP3) header file + * \file audio.h + * \author bombur + * \version 0.1 + * \date 07.03.2007 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#ifndef SP_AUDIO_H +#define SP_AUDIO_H + +// see audio_get_audio_format_string() +typedef enum +{ + RIFF_AUDIO_UNKNOWN = -1, + RIFF_AUDIO_NONE = 0, + RIFF_AUDIO_LPCM, + RIFF_AUDIO_PCM, + RIFF_AUDIO_MP1, + RIFF_AUDIO_MP2, + RIFF_AUDIO_MP3, + RIFF_AUDIO_AC3, + RIFF_AUDIO_DTS, + RIFF_AUDIO_VORBIS, +} RIFF_AUDIO_FORMAT; + +typedef enum +{ + AUDIO_XING_HAS_NUMFRAMES = 0x0001, + AUDIO_XING_HAS_FILESIZE = 0x0002, + AUDIO_XING_HAS_TOC = 0x0004, + AUDIO_XING_HAS_VBR_SCALE = 0x0008, +} AUDIO_XING_FLAGS; + +#ifdef WIN32 +#pragma pack(1) +#endif + +typedef struct ATTRIBUTE_PACKED +{ + WORD w_format_tag; + WORD n_channels; + DWORD n_samples_per_sec; + DWORD n_avg_bytes_per_sec; + WORD n_block_align; + WORD w_bits_per_sample; + WORD cb_size; +} RIFF_WAVEFORMATEX; + +#ifdef WIN32 +#pragma pack() +#endif + +typedef struct RiffAudioTrack +{ + RIFF_WAVEFORMATEX *wfe; // audio format + + DWORD flags; + long mp3rate; // mp3 bitrate kbs + bool a_vbr; // Variable BitRate? + long padrate; // byte rate used for zero padding + + long audio_strn; // Audio stream number + long audio_samples; // Number of samples + LONGLONG audio_bytes; // Total number of bytes of audio data + long audio_chunks; // Chunks of audio data in the file + + char audio_tag[4]; // Tag of audio data + long audio_pos; // Audio position: chunk + + LONGLONG a_codech_off; // absolute offset of audio codec information + LONGLONG a_codecf_off; // absolute offset of audio codec information + +} RiffAudioTrack; + +#ifdef AUDIO_INTERNAL + + +/// Audio player class +class Audio +{ +public: + /// ctor + Audio(); + + /// dtor + ~Audio(); + + int ParseMp3Packet(MpegPacket *packet, BYTE * &buf, int len); + int ParsePCMPacket(MpegPacket *packet, BYTE * &buf, int len); + int ParseAC3Packet(MpegPacket *packet, BYTE * &buf, int len); + int ParseRawPacket(MpegPacket *packet, BYTE * &buf, int len); + + int ParseXingHeader(BYTE *buf, int len); + int ParseVBRIHeader(BYTE *buf, int len); + int ParseAC3Header(BYTE *buf, int len, bool fill_info = true); + + int Seek(LONGLONG pos, int msecs); + +public: + struct mad_stream mp3_stream; + struct mad_frame mp3_frame; + struct mad_synth mp3_synth; + + RIFF_AUDIO_FORMAT audio_fmt; + SPString audio_fmt_str; + + RIFF_WAVEFORMATEX *fmtex; + + int audio_numbufs, audio_bufsize; + int audio_bufidx; + int audio_samplerate, audio_channels; + int samples_per_frame; + + int audio_frame_number; + int is_partial_packet, is_partial_next_packet; + + // for seeking + int bitrate, avg_bitrate, avg_num; + // output bitrate - for PTS calc. + int out_bps; + /// audio data full size, in bytes. + LONGLONG filesize, cur_offset, cur_tmpoffset; + /// audio length, in msecs. + int length; + + // displayed values + int cur_bitrate, cur_audio_format; + int cur_samples, cur_bits, cur_channels; + + bool needs_resample; + bool fast; + int resample_packet_size; + + BYTE *mp3_tmpbuf; + int mp3_tmppos, mp3_tmpstart, mp3_tmpread; + bool mp3_want_more; + bool wait; + + int ac3_scan_header; + BYTE ac3_header[8]; + int ac3_framesize; + bool ac3_gatherinfo, ac3_gather_always; + + BYTE *mux_buf; + int mux_numbufs, mux_bufsize; + + bool playing, stopping; + LONGLONG saved_pts; + int fd; + + SPList seek_toc; + LONGLONG seek_offset; + /// length of TOC, in msecs + int toc_length; + + int (*mp3_output)(BYTE *data, struct mad_pcm *pcm); + +private: + /// Get big-endian number + DWORD GetBE(BYTE *buf, int offset, int numbytes); + /// Set length & filesize from VBR headers data + int SetVbrLength(int total_num_frames, int vbr_filesize); + + +}; +#endif + +#ifdef __cplusplus +//extern "C" { +#endif + +/// Play audio file +int audio_play(char *filepath = NULL, bool is_folder = false, bool continue_next = true); + +/// Advance playing +int audio_loop(); + +/// Pause playing +BOOL audio_pause(); + +/// Stop playing +BOOL audio_stop(BOOL continue_next = TRUE); + +/// Seek to given time and play +BOOL audio_seek(int seconds, int from); + +/// Continue audio play +int audio_continue_play(); + +int audio_forward(); +int audio_rewind(); + +void audio_setdebug(BOOL ison); +BOOL audio_getdebug(); + + +/// Initialize audio player +int audio_init(RIFF_AUDIO_FORMAT fmt, bool fast); +/// Deinitialize audio player +int audio_deinit(); + +/// Get next file name from the list +char *audio_get_next(char *filepath, bool get_next); +/// Delete file list (free memory) +void audio_delete_filelist(); + +/// Return TRUE if audio decoder is ready +BOOL audio_ready(); +/// Reset audio and drop old frames +int audio_reset(); + +// internal funcs used by media +BOOL audio_open(const char *filepath); + +BOOL audio_close(); + +int audio_read(BYTE *buf, int *len); + +/// Parse audio packet +int audio_parse_packet(MpegPacket *packet, BYTE * &buf, int len); + +RIFF_AUDIO_FORMAT audio_get_audio_format(int fmt); + +/// Get format text string +SPString audio_get_audio_format_string(RIFF_AUDIO_FORMAT fmt); + +/// little-endian! +RIFF_WAVEFORMATEX *audio_read_formatex(int fd, BYTE *buf, int len); +/// Set audio params +int audio_setaudioparams(RIFF_AUDIO_FORMAT fmt, RIFF_WAVEFORMATEX *wfe, int halfrate); + +/// Get audio bitrate +int audio_get_bitrate(); + +/// Get audio output bytes-per-second +int audio_get_output_bps(); + +void audio_update_format_string(); + +/// Set by video player to save some CPU resources... +void audio_set_ac3_getinfo(bool always); + +#ifdef __cplusplus +//} +#endif + +#endif // of SP_AUDIO_H diff --git a/src/avi.cpp b/src/avi.cpp new file mode 100644 index 0000000..3a085e0 --- /dev/null +++ b/src/avi.cpp @@ -0,0 +1,1003 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - AVI player source file. + * \file avi.cpp + * \author bombur + * \version 0.1 + * \date 07.03.2007 + * + * Based on AVILIB (C) 1999 Rainer Johanni + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "script.h" +#include "player.h" +#include "media.h" + +#ifdef INTERNAL_VIDEO_PLAYER + +#define VIDEO_INTERNAL + +#include "audio.h" +#include "video.h" +#include "avi.h" + +#include "script-vars.h" + +#define MSG if (video_msg) msg + +VideoAvi::VideoAvi() +{ + type = VIDEO_CONTAINER_AVI; + + video_strn = 0; + avi_flags = 0; + bitmap_info_header = NULL; + video_superindex.idx = NULL; + + memset(video_tag1, 0, 4); + memset(video_tag2, 0, 4); + movi_start = 0; + idx_start = -1; + idx_offset = -1; + + has_indx = false; + num_idx = 0; + + buf_idx = (AviOldIndexEntry *)SPmalloc(MAX(max_buf_idx_size[0], max_buf_idx_size[1])); + buf_idx_size = 0; + + cur_old_buf_idx = 0; + cur_buf_video_idx = 0; + + cur_indx_buf_idx = 0; + cur_indx_buf_pos = 0; + cur_indx_base_offset = 0; + cur_buf_idx_size = 1; +} + +VideoAvi::~VideoAvi() +{ + SPSafeFree(bitmap_info_header); + SPSafeFree(video_superindex.idx); + SPSafeFree(buf_idx); +} + +//////////////////////////////////////////////// + +BOOL VideoAvi::Parse() +{ + long i, n; + BYTE *hdrl_data; + BYTE data[256]; + long header_offset = 0, hdrl_len = 0; + int j; + VIDEO_CHUNK_TYPE lasttag = VIDEO_CHUNK_UNKNOWN; + bool vids_strh_seen = false; + bool vids_strf_seen = false; + bool auds_strh_seen = false; + int num_stream = 0; + LONGLONG oldpos = -1, newpos = -1; + + // Read first 12 bytes and check that this is an AVI file + if (video_read(data, 12) != 12) + { + msg_error("AVI: Read error.\n"); + return FALSE; + } + if (strncasecmp((char *)data, "RIFF", 4) != 0 || strncasecmp((char *)data+8, "AVI ", 4) != 0) + { + msg_error("AVI: File header not found. Wrong file format.\n"); + return FALSE; + } + + // Go through the AVI file and extract the header list, + // the start position of the 'movi' list and an optionally present idx1 tag + + hdrl_data = 0; + movi_start = 0; + idx_start = -1; + + // find all main AVI parts + for (;;) + { + // EOF? + if (video_read(data, 8) != 8) + break; + newpos = video_lseek(0, SEEK_CUR); + if (oldpos == newpos) + { + msg_error("AVI: AVI file is broken.\n"); + return FALSE; + } + oldpos=newpos; + + n = GET_DWORD(data+4); + n = PAD_EVEN(n); + + if (strncasecmp((char *)data, "LIST", 4) == 0) + { + if (video_read(data, 4) != 4) + { + msg_error("AVI: Read error.\n"); + return FALSE; + } + n -= 4; + if (n <= 0) + break; + if (strncasecmp((char *)data, "hdrl", 4) == 0) + { + hdrl_len = n; + hdrl_data = (BYTE *) SPmalloc(n); + if (hdrl_data == NULL) + return FALSE; + + header_offset = (long)video_lseek(0, SEEK_CUR); + if (video_read(hdrl_data, n) != n) + { + msg_error("AVI: Read error.\n"); + return FALSE; + } + } + else if (strncasecmp((char *)data, "movi", 4) == 0) + { + movi_start = video_lseek(0, SEEK_CUR); + if (video_lseek(n, SEEK_CUR) == (LONGLONG)-1) + break; + } + else + { + if (video_lseek(n, SEEK_CUR) == (LONGLONG)-1) + break; + } + } + else if(strncasecmp((char *)data, "idx1", 4) == 0) + { + // n must be a multiple of 16, but the reading does not + // break if this is not the case + num_idx = n / sizeof(AviOldIndexEntry); + idx_start = video_lseek(0, SEEK_CUR); + + if (buf_idx == NULL) + return FALSE; + buf_idx_size = MIN(n, max_buf_idx_size[0]); + // \warning! little-endian ONLY! + if (video_read((BYTE *)buf_idx, buf_idx_size) != buf_idx_size) + { + buf_idx_size = 0; + num_idx = 0; + } + video_lseek(n - buf_idx_size, SEEK_CUR); + } + else + video_lseek(n, SEEK_CUR); + } + + if (hdrl_data == NULL) + { + msg_error("AVI: no AVI header found.\n"); + return FALSE; + } + if (movi_start == 0) + { + msg_error("AVI: no movie data found.\n"); + return FALSE; + } + + // parse header + for (i = 0; i < hdrl_len; ) + { + if (strncasecmp((char *)hdrl_data+i,"LIST", 4) == 0) + { + i+= 12; + continue; + } + + n = GET_DWORD(hdrl_data+i+4); + n = PAD_EVEN(n); + + if (strncasecmp((char *)hdrl_data + i, "avih", 4) == 0) + { + i += 8; + avi_flags = GET_DWORD(hdrl_data+i+12); + if (avi_flags & AVI_FLAG_MUSTUSEINDEX) + { + msg_error("Avi: Reordered stream cannot be played correctly!\n"); + } + if (!(avi_flags & AVI_FLAG_ISINTERLEAVED)) + { + msg_error("Avi: Non-interleaved stream cannot be played correctly!\n"); + } + /* + if (!(avi_flags & AVI_FLAG_TRUSTCKTYPE)) + { + msg("Avi: We cannot trust key-frames? Maybe...\n"); + } + */ + } + else if (strncasecmp((char *)hdrl_data + i, "strh", 4) == 0) + { + i += 8; + if (strncasecmp((char *)hdrl_data + i, "vids", 4) == 0 && !vids_strh_seen) + { + scale = GET_DWORD(hdrl_data+i+20); + rate = GET_DWORD(hdrl_data+i+24); + if (scale != 0) + fps = (float)rate / (float)scale; + video_frames = GET_DWORD(hdrl_data+i+32); + video_strn = num_stream; + vids_strh_seen = true; + lasttag = VIDEO_CHUNK_VIDEO; + } + else if (strncasecmp ((char *)hdrl_data+i,"auds",4) == 0 && ! auds_strh_seen) + { + //inc audio tracks + cur_track = num_tracks; + ++num_tracks; + + if (num_tracks > VIDEO_MAX_AUDIO_TRACKS) + { + msg_error("Avi: Only %d audio tracks supported!\n", VIDEO_MAX_AUDIO_TRACKS); + return FALSE; + } + + track[cur_track].flags = GET_DWORD(hdrl_data+i+8); + track[cur_track].padrate = GET_DWORD(hdrl_data+i+24); + track[cur_track].audio_samples = GET_DWORD(hdrl_data+i+32); + track[cur_track].a_vbr = GET_DWORD(hdrl_data+i+44) == 0; // samplesize -- 0? + track[cur_track].audio_strn = num_stream; + track[cur_track].a_codech_off = header_offset + i; + lasttag = VIDEO_CHUNK_AUDIO; + } + else if (strncasecmp ((char *)hdrl_data+i, "iavs", 4) == 0 && ! auds_strh_seen) + { + msg_error("AVI: DV AVI Type 1 not supported.\n"); + return FALSE; + } + else + lasttag = VIDEO_CHUNK_UNKNOWN; + num_stream++; + } + else if (strncasecmp((char *)hdrl_data+i, "dmlh", 4) == 0) + { + total_frames = GET_DWORD(hdrl_data+i+8); + i += 8; + } + else if (strncasecmp((char *)hdrl_data+i,"strf", 4) == 0) + { + i += 8; + if (lasttag == VIDEO_CHUNK_VIDEO) + { + RIFF_BITMAPINFOHEADER bih; + memcpy(&bih, hdrl_data + i, sizeof(RIFF_BITMAPINFOHEADER)); + bih.bi_size = GET_DWORD((BYTE *)&bih.bi_size); + bitmap_info_header = (RIFF_BITMAPINFOHEADER *)SPmalloc(bih.bi_size); + if (bitmap_info_header != NULL) + { + memcpy(bitmap_info_header, hdrl_data + i, bih.bi_size); + } + + width = GET_DWORD(hdrl_data+i+4); + height = GET_DWORD(hdrl_data+i+8); + memcpy(fourcc, hdrl_data+i+16, 4); fourcc[4] = 0; + + vids_strf_seen = true; + } + else if (lasttag == VIDEO_CHUNK_AUDIO) + { + LONGLONG lpos = video_lseek(0, SEEK_CUR); + video_lseek(header_offset + i + sizeof(RIFF_WAVEFORMATEX), SEEK_SET); + + RIFF_WAVEFORMATEX *wfe = audio_read_formatex(fd, hdrl_data + i, hdrl_len - i); + + video_lseek(lpos, SEEK_SET); + + if (wfe != NULL) + { + track[cur_track].a_codecf_off = header_offset + i; + track[cur_track].mp3rate = 8 * wfe->n_avg_bytes_per_sec / 1000; + int sampsize = MAX(((wfe->w_bits_per_sample + 7) / 8) + * wfe->n_channels, 4); + track[cur_track].audio_bytes = + track[cur_track].audio_samples * sampsize; + } + + track[cur_track].wfe = wfe; + } + } + else if(strncasecmp((char *)hdrl_data+i, "indx", 4) == 0) + { + MSG("AVI: New v2.0 index detected!\n"); + if (!(avi_flags & AVI_FLAG_TRUSTCKTYPE)) + { + msg_error("AVI: Index may contain invalid keyframes!\n"); + } + + if (lasttag == VIDEO_CHUNK_VIDEO) + { + BYTE *a = hdrl_data + i; + memcpy (video_superindex.fcc, a, 4); a += 4; + video_superindex.dwSize = GET_DWORD(a); a += 4; + video_superindex.wLongsPerEntry = GET_WORD(a); a += 2; + video_superindex.bIndexSubType = *a; a += 1; + video_superindex.bIndexType = *a; a += 1; + video_superindex.nEntriesInUse = GET_DWORD(a); a += 4; + memcpy (video_superindex.dwChunkId, a, 4); a += 4; + + // 3 * reserved + a += 4; a += 4; a += 4; + + if (video_superindex.bIndexSubType != 0) + { + msg("AVI: Invalid ODML superindex header.\n"); + } + + int superidx_size = video_superindex.wLongsPerEntry + * video_superindex.nEntriesInUse * sizeof (DWORD); + video_superindex.idx = superidx_size < 256*1024 ? + (AviSuperIndexEntry *)SPmalloc (superidx_size) : NULL; + if (video_superindex.idx != NULL) + { + // position of ix## chunks + for (DWORD j = 0; j < video_superindex.nEntriesInUse; ++j) + { + video_superindex.idx[j].qwOffset = GET_ULONGLONG (a); a += 8; + video_superindex.idx[j].dwSize = GET_DWORD (a); a += 4; + video_superindex.idx[j].dwDuration = GET_DWORD (a); a += 4; + } + has_indx = true; + } + } + i += 8; + } + else if ((strncasecmp((char *)hdrl_data+i,"JUNK", 4) == 0) || + (strncasecmp((char *)hdrl_data+i,"strn", 4) == 0) || + (strncasecmp((char *)hdrl_data+i,"strd", 4) == 0) || + (strncasecmp((char *)hdrl_data+i,"vprp", 4) == 0)) + { + // do not reset lasttag + i += 8; + } else + { + i += 8; + lasttag = VIDEO_CHUNK_UNKNOWN; + } + i += n; + } + + SPfree(hdrl_data); + + if (!vids_strh_seen || !vids_strf_seen) + { + msg_error("AVI: No video found.\n"); + return FALSE; + } + + video_tag1[0] = (char)(video_strn/10 + '0'); + video_tag1[1] = (char)(video_strn%10 + '0'); + video_tag1[2] = 'd'; + video_tag1[3] = bitmap_info_header != NULL + && bitmap_info_header->bi_compression == 0 ? 'b' : 'c'; + + video_tag2[0] = (char)(video_strn/10 + '0'); + video_tag2[1] = (char)(video_strn%10 + '0'); + video_tag2[2] = 'd'; + video_tag2[3] = video_tag1[3] == 'b' ? 'c' : 'b'; + + // Audio tag is set to "99wb" if no audio present + if (track[0].wfe == NULL || track[0].wfe->n_channels < 1) + track[0].audio_strn = 99; + + first_track = -1; + int good_first_track = -1; + for(i = 0, j = 0; j < num_tracks + 1; ++j) + { + if (j == video_strn) + continue; + if (first_track < 0) + first_track = i; + // if not disabled + if (good_first_track < 0 && (track[i].flags & 1) == 0) + good_first_track = i; + track[i].audio_tag[0] = (char)(j/10 + '0'); + track[i].audio_tag[1] = (char)(j%10 + '0'); + track[i].audio_tag[2] = 'w'; + track[i].audio_tag[3] = 'b'; + ++i; + } + + // set first non-disabled track. + // we can change them later with video_set_audio_track(). + if (good_first_track >= 0) + first_track = good_first_track; + + cur_track = first_track; + + video_lseek(movi_start, SEEK_SET); + + // if the file has an idx1, check if this is relative + // to the start of the file or to the start of the 'movi' list + idx_offset = -1; + if (idx_start > 0 && !has_indx) + { + int num_buf_idx = buf_idx_size / sizeof(AviOldIndexEntry); + bool found_av = false; + for (i = 0; i < num_buf_idx; i++) + { + if (strncasecmp((char *)&buf_idx[i].chunkid, (char *)video_tag1, 3) == 0) + { + found_av = true; + break; + } + } + // if no video chunks present in current buffer, search for audio... + if (!found_av) + { + for (i = 0; !found_av && i < num_buf_idx; i++) + { + for (j = 0; j < num_tracks; ++j) + { + if (strncasecmp((char *)&buf_idx[i].chunkid, track[j].audio_tag, 4) == 0) + { + found_av = true; + break; + } + } + } + } + + if (found_av) + { + LONGLONG pos = buf_idx[i].offset; + LONGLONG len = buf_idx[i].size; + if (pos >= 0 && len > 0) + { + video_lseek(pos, SEEK_SET); + if (video_read(data, 8) == 8) + { + // index from start of file? + if (strncasecmp((char *)data, (char *)&buf_idx[i].chunkid, 4) == 0 + && GET_DWORD(data + 4) == len) + { + idx_offset = 0; + } + } + if (idx_offset < 0) + { + video_lseek(pos + movi_start - 4,SEEK_SET); + if (video_read(data, 8) == 8) + { + // index from start of 'movi' list? + if (strncasecmp((char *)data, (char *)&buf_idx[i].chunkid, 4) == 0 + && GET_DWORD((unsigned char *)data+4) == len) + { + idx_offset = movi_start - 4; + } + } + } + } + } + // broken index... + if (idx_offset >= 0) + { + AddIdx1Block(); + } + } + + // Reposition the file + video_lseek(movi_start, SEEK_SET); + abs_video_pos = -1; + video_pos_base = 0; + video_pos = 0; + audio_pos = 0; + cur_key_offs = last_chunk_offs = cur_offs = video_offs = movi_start; + frame_pos = 0; + last_frame_pos = -1; + + return TRUE; +} + +////////////////////////////////////////////////////////////////// + +VIDEO_CHUNK_TYPE VideoAvi::GetNext(BYTE *buf, int buflen, int *pos, int *left, int *len) +{ + // there was a partial chunk read + int tmp_read; + if (chunkleft > 0) + { + *len = MIN(chunkleft, buflen); + + chunkleft -= buflen; + if (chunkleft < 0) + chunkleft = 0; + + if (chunktype != VIDEO_CHUNK_UNKNOWN) + { + if ((tmp_read = video_read(buf, PAD_EVEN(*len))) == 0) + { + return VIDEO_CHUNK_EOF; + } + } else + video_lseek(PAD_EVEN(*len), SEEK_CUR); + + if (chunkleft == 0) + { + if (chunktype == VIDEO_CHUNK_AUDIO_PARTIAL) + return VIDEO_CHUNK_AUDIO; + else if (chunktype == VIDEO_CHUNK_VIDEO_PARTIAL) + return VIDEO_CHUNK_VIDEO; + } + if (chunktype != VIDEO_CHUNK_UNKNOWN) + return chunktype; + + //buf += PAD_EVEN(*len); + //*pos += PAD_EVEN(*len); + //*left -= PAD_EVEN(*len); + } + + *len = 0; + chunktype = VIDEO_CHUNK_UNKNOWN; + //chunkleft = 0; + + int curpos = *pos; + LONGLONG start_offs; + + // in recovery mode, use buffered read + if (look4chunk) + { + buflen = video_read(buf, buflen - curpos); + if (buflen <= 0) + return VIDEO_CHUNK_EOF; + start_offs = cur_offs; + buflen += curpos; + } + + + // we're at the start of the chunk and got chunk header + while (curpos < buflen) + { + // if we got a list tag, ignore it + BYTE hdr_data[8]; + char *hdr = (char *)hdr_data; + + if (!look4chunk) + { + if ((tmp_read = video_read(hdr_data, 8)) == 0) + { + return VIDEO_CHUNK_EOF; + } + } else + { + curpos = (int)(cur_offs - start_offs); + hdr = (char *)(buf + curpos); + } + + cur_offs += 8; + + int n, reallen; + if (strncasecmp(hdr, "RIFF", 4) == 0 || strncasecmp(hdr, "LIST", 4) == 0) + { + ResetRecoveryMode(); + video_lseek(4, SEEK_CUR); + + cur_offs += 4; + continue; + } + if (*left < 8) + { + chunktype = VIDEO_CHUNK_FRAGMENT; + break; + } + + reallen = n = GET_DWORD((BYTE *)hdr + 4); + + //buf += 8; + //*pos += 8; + //*left -= 8; + bool partial = false; + + frame_pos++; + + if (strncasecmp(hdr, video_tag1, 3) == 0) + { +//msg("00dc\n"); +//printf("00dc: [%d] n=%d %d/%d\n", frame_pos, n, *pos, buflen); + abs_video_pos++; + video_pos++; + if (n == 0) + continue; + + ResetRecoveryMode(); + + int read_n; + if (*pos + n > buflen) + { + // XXXXXXXXXXXXXXX + if (no_partial) + { + frame_pos--; + abs_video_pos--; + video_pos--; + video_lseek(-8, SEEK_CUR); + return VIDEO_CHUNK_UNKNOWN; + } + + chunkleft = n - buflen + *pos; + read_n = n = buflen - *pos; + partial = true; + + } else + read_n = PAD_EVEN(n); + + if ((tmp_read = video_read(buf, read_n)) == 0) + { + return VIDEO_CHUNK_EOF; + } + + *len = n; + video_packet_len = reallen; + chunktype = partial ? VIDEO_CHUNK_VIDEO_PARTIAL : VIDEO_CHUNK_VIDEO; + last_chunk_offs = video_offs = cur_offs - 8; + cur_offs += read_n; + last_chunk_len = reallen; + break; + } + else if (cur_track >= 0 && strncasecmp(hdr, track[cur_track].audio_tag, 4) == 0) + { +//msg("01wb\n"); + audio_pos++; + if (n == 0) + continue; + + ResetRecoveryMode(); + + int read_n; + if (*pos + n > buflen) + { + // XXXXXXXXXXXXXXX + if (no_partial) + { + frame_pos--; + audio_pos--; + video_lseek(-8, SEEK_CUR); + return VIDEO_CHUNK_UNKNOWN; + } + + chunkleft = n - buflen + *pos; + read_n = n = buflen - *pos; + partial = true; + } else + read_n = PAD_EVEN(n); + + + if ((tmp_read = video_read(buf, read_n)) == 0) + { + return VIDEO_CHUNK_EOF; + } +#if 0 +{ +FILE *fp = fopen("out-avi.mp3", "ab"); +fwrite(buf, n, 1, fp); +fclose(fp); +} +#endif + *len = n; + chunktype = partial ? VIDEO_CHUNK_AUDIO_PARTIAL : VIDEO_CHUNK_AUDIO; + last_chunk_offs = cur_offs - 8; + cur_offs += read_n; + last_chunk_len = reallen; + track[cur_track].audio_pos++; + break; + } + else + { + if (hdr[0] == 'i' && hdr[1] == 'd' && hdr[2] == 'x' && hdr[3] == '1') + { + ResetRecoveryMode(); + + //chunktype = VIDEO_CHUNK_EOF; + chunktype = VIDEO_CHUNK_UNKNOWN; + //buf += PAD_EVEN(n); + //*pos += PAD_EVEN(n); + //*left -= PAD_EVEN(n); + + cur_offs += PAD_EVEN(n); + video_lseek(cur_offs, SEEK_SET); + + //return chunktype; + continue; + } + else if ((hdr[2] == 'w' && hdr[3] == 'b') || + (hdr[0] == 'i' && hdr[1] == 'x') || + (hdr[0] == 'J' && hdr[1] == 'U' && hdr[2] == 'N' && hdr[3] == 'K')) + { + ResetRecoveryMode(); + + //buf += PAD_EVEN(n); + //*pos += PAD_EVEN(n); + //*left -= PAD_EVEN(n); + chunktype = VIDEO_CHUNK_UNKNOWN; + + video_lseek(PAD_EVEN(n), SEEK_CUR); + + cur_offs += PAD_EVEN(n); + } + // file is corrupted ? + else + { + cur_offs -= 7; + + if (!look4chunk) + { + LONGLONG file_offs = video_lseek(0, SEEK_CUR) - 8; + msg("AVI: File corrupted @ " PRINTF_64d ". Trying to recover...\n", file_offs); + + video_lseek(-7, SEEK_CUR); + + chunkleft = 0; + look4chunk = true; + + script_error_callback(SCRIPT_ERROR_CORRUPTED); + + return VIDEO_CHUNK_RECOVERY; + } + } + } + } + return chunktype; +} + +int VideoAvi::GetNextIndexes() +{ + if ((idx_offset >= 0 && idx_start > 0) || has_indx) + { + media_update_fip(); + + LONGLONG buf_idx_offs; + if (has_indx) + { + cur_buf_idx_size = video_superindex.idx[cur_indx_buf_idx].dwSize; + if (cur_indx_buf_pos >= cur_buf_idx_size) + { + cur_indx_buf_idx++; + cur_indx_buf_pos = 0; + } + if ((DWORD)cur_indx_buf_idx >= video_superindex.nEntriesInUse) + return -1; + + cur_buf_idx_size -= cur_indx_buf_pos; + buf_idx_offs = video_superindex.idx[cur_indx_buf_idx].qwOffset + cur_indx_buf_pos; + } else + { + cur_buf_idx_size = (num_idx - cur_old_buf_idx) * sizeof(AviOldIndexEntry); + buf_idx_offs = idx_start + cur_old_buf_idx * sizeof(AviOldIndexEntry); + } + + buf_idx_size = MIN(max_buf_idx_size[has_indx ? 1 : 0], cur_buf_idx_size); + + if (buf_idx_size < 1) + return -1; + if (video_lseek(buf_idx_offs, SEEK_SET) < 0) + return -1; + buf_idx_size = video_read((BYTE *)buf_idx, buf_idx_size); + //msg("^^^^^^^ read = %d\n", buf_idx_size); + return has_indx ? AddIdx2Block() : AddIdx1Block(); + } + return -1; +} + +int VideoAvi::GetNextKeyFrame() +{ + int n = 0; + LONGLONG curpos = 0; + const int extra_bytes = 5; + BYTE buf[8 + extra_bytes]; + + //*len = 0; + if (buf_idx == NULL) + return -1; + + // we're at the start of the chunk and got chunk header + for (int i = 0; ; i++) + { + // split our task... + if (i > 30) + { + media_update_fip(); + //if (info_cnt++ > 1) + { + // info_cnt = 0; + video_update_info(); + } + last_chunk_offs = curpos; + last_chunk_len = n; + return 0; + } + + curpos = video_lseek(0, SEEK_CUR); + int extra = read(fd, buf, 8 + extra_bytes) - 8; + + if (extra < 0) + break; + if (strncasecmp((char *)buf, "LIST", 4) == 0) + { + video_lseek(4 - extra, SEEK_CUR); + continue; + } + n = GET_DWORD(buf + 4); + frame_pos++; + if (strncasecmp((char *)buf, video_tag1, 3) == 0) + { + abs_video_pos++; + video_pos++; + scr = INT64(90000) * abs_video_pos * scale / rate; + if (video_fmt == RIFF_VIDEO_MPEG4) + { + // VOP start code + if (n > 4 && buf[8] == 0 && buf[9] == 0 && buf[10] == 1 && buf[11] == 0xb6) + { + if ((buf[12] & 0xc0) == 0) // I-frame + { + goto found; + } + } + // or we have to search for it... + else + { + int nleft = n - extra; + while (nleft > 0) + { + int nb = MIN(nleft, max_buf_idx_size[0]); + nb = video_read((BYTE *)buf_idx, nb); + if (nb == 0) + break; + BYTE *b = (BYTE *)buf_idx; + // find VOP start + int vlen = nb; + for (; vlen >= 0 && (b[0] != 0 || b[1] != 0 + || b[2] != 1 || b[3] != 0xb6); vlen--) + { + b++; + } + if (vlen > 0 && (b[4] & 0xc0) == 0) // I-frame + { + goto found; + } + extra += nb; + nleft -= nb; + } + } + } + else if (video_fmt == RIFF_VIDEO_DIV3) + { + // VOP start code + if (n > 2 && (buf[8] & 0xc0) == 0) // I-frame + { + goto found; + } + } + } + /* + else if (cur_track >= 0 && strncasecmp((char *)buf, track[cur_track].audio_tag, 4) == 0) + { + audio_pos++; + } + */ + video_lseek(PAD_EVEN(n) - extra, SEEK_CUR); + } + + MSG("AVI: I-FRAME seek EOF!\n"); + return -1; + +found: + last_chunk_offs = curpos; + last_chunk_len = n; + + cur_key_offs = video_offs = curpos; + video_lseek(video_offs, SEEK_SET); + MSG("AVI: I-FRAME at %d/%d (%d)\n", abs_video_pos, video_frames, curpos); + video_packet_len = n; + AddIndex(abs_video_pos, video_packet_len, video_offs); + return 1; +} + +int VideoAvi::AddIdx1Block() +{ + if (buf_idx == NULL || idx_offset < 0) + return -1; + + AviOldIndexEntry *b = buf_idx; + DWORD chunkid1 = GET_DWORD((BYTE *)video_tag1); + DWORD chunkid2 = GET_DWORD((BYTE *)video_tag2); + for (int i = buf_idx_size / sizeof(AviOldIndexEntry); i > 0; i--, b++) + { + if (b->chunkid == chunkid1 || b->chunkid == chunkid2) + { + if (b->flags & AVI_IDX1_FLAG_KEYFRAME) + { + if (cur_buf_video_idx > last_key_pos + min_delta_keyframes) + { + if (AddIndex(cur_buf_video_idx, b->size, b->offset + idx_offset) < 0) + return -1; + } + } + cur_buf_video_idx++; + } + } + cur_old_buf_idx += buf_idx_size / sizeof(AviOldIndexEntry); + return 0; +} + +int VideoAvi::AddIdx2Block() +{ + if (buf_idx == NULL) + return -1; + + AviNewIndexEntry *b; + int bis = buf_idx_size; + if (cur_indx_buf_pos == 0) // read header + { + AviNewIndex *ni = (AviNewIndex *)buf_newidx; + cur_indx_base_offset = ni->qwBaseOffset - 8; + + b = (AviNewIndexEntry *)(ni + 1); + bis -= sizeof(AviNewIndex); + } + else + b = buf_newidx; + for (int i = bis / sizeof(AviNewIndexEntry); i > 0; i--, b++) + { + if (!(b->size & AVI_IDX2_FLAG_KEYFRAME)) + { + if (cur_buf_video_idx > last_key_pos + min_delta_keyframes) + { + if (AddIndex(cur_buf_video_idx, b->size, cur_indx_base_offset + b->offset) < 0) + return -1; + } + } + cur_buf_video_idx++; + } + + cur_indx_buf_pos += buf_idx_size; + return 0; +} + +void VideoAvi::ResetRecoveryMode() +{ + if (look4chunk) + { + video_lseek(cur_offs, SEEK_SET); + look4chunk = false; + script_update_variable(SCRIPT_VAR_PLAYER_SPEED); + } +} + +#endif diff --git a/src/avi.h b/src/avi.h new file mode 100644 index 0000000..a1347fe --- /dev/null +++ b/src/avi.h @@ -0,0 +1,178 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - AVI player header file + * \file avi.h + * \author bombur + * \version 0.1 + * \date 07.03.2007 + * + * Based on AVILIB (C) 1999 Rainer Johanni + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#ifndef SP_AVI_H +#define SP_AVI_H + +#include "video.h" + +#ifdef VIDEO_INTERNAL + +#ifdef WIN32 +#pragma pack(1) +#endif + +typedef struct AviOldIndexEntry +{ + DWORD chunkid; + DWORD flags; + DWORD offset; + DWORD size; +} AviOldIndexEntry; + +typedef struct AviNewIndexEntry +{ + DWORD offset; + DWORD size; +} AviNewIndexEntry; + +typedef struct AviSuperIndexEntry +{ + LONGLONG qwOffset; // absolute file offset + DWORD dwSize; // size of index chunk at this offset + DWORD dwDuration; // time span in stream ticks +} AviSuperIndexEntry; + +typedef struct AviSuperIndex +{ + char fcc[4]; + DWORD dwSize; // size of this chunk + WORD wLongsPerEntry; // size of each entry in aIndex array (must be 8 for us) + BYTE bIndexSubType; // future use. must be 0 + BYTE bIndexType; // one of AVI_INDEX_* codes + DWORD nEntriesInUse; // index of first unused member in aIndex array + char dwChunkId[4]; // fcc of what is indexed + DWORD dwReserved[3]; // meaning differs for each index type/subtype. + AviSuperIndexEntry *idx; // where are the ix## chunks +} AviSuperIndex; + +typedef struct AviNewIndex +{ + char fcc[4]; // ’ix##’ + DWORD cb; + WORD wLongsPerEntry; // must be sizeof(aIndex[0])/sizeof(DWORD) + BYTE bIndexSubType; // must be 0 + BYTE bIndexType; // must be AVI_INDEX_OF_CHUNKS + DWORD nEntriesInUse; // + char dwChunkId[4]; // ’##dc’ or ’##db’ or ’##wb' etc.. + LONGLONG qwBaseOffset; // all dwOffsets in aIndex are relative to this + DWORD dwReserved3; // must be 0 +} AviNewIndex; + +typedef struct RIFF_BITMAPINFOHEADER +{ + DWORD bi_size; + DWORD bi_width; + DWORD bi_height; + WORD bi_planes; + WORD bi_bit_count; + DWORD bi_compression; + DWORD bi_size_image; + DWORD bi_x_pels_per_meter; + DWORD bi_y_pels_per_meter; + DWORD bi_clr_used; + DWORD bi_clr_important; +} RIFF_BITMAPINFOHEADER; + +#ifdef WIN32 +#pragma pack() +#endif + +enum AVI_FLAGS +{ + AVI_FLAG_HASINDEX = 0x00000010, // Index at end of file + AVI_FLAG_MUSTUSEINDEX = 0x00000020, + AVI_FLAG_ISINTERLEAVED = 0x00000100, + AVI_FLAG_TRUSTCKTYPE = 0x00000800, // Use CKType to find key frames + AVI_FLAG_WASCAPTUREFILE = 0x00010000, + AVI_FLAG_COPYRIGHTED = 0x00020000, +}; + +enum AVI_IDX1_FLAGS +{ + AVI_IDX1_FLAG_KEYFRAME = 0x00000010, + + AVI_IDX2_FLAG_KEYFRAME = 0x80000000, +}; + + +const int max_buf_idx_size[2] = { 32768 /* idx1 */, 32768/2 /* indx2.0 */ }; +const int index_block_size = 1024; + +/// AVI container player class +class VideoAvi : public Video +{ +public: + /// ctor + VideoAvi(); + + /// dtor + virtual ~VideoAvi(); + +public: + virtual BOOL Parse(); + virtual VIDEO_CHUNK_TYPE GetNext(BYTE *buf, int buflen, int *pos, int *left, int *len); + /// Returns 0 if found, -1 if failed, 1 for EOF. + virtual int GetNextIndexes(); + /// Find next key-frame in raw mode + virtual int GetNextKeyFrame(); + + RIFF_BITMAPINFOHEADER *bitmap_info_header; + + char video_tag1[4], video_tag2[4]; + int video_strn, avi_flags; + LONGLONG movi_start; + LONGLONG idx_start; + LONGLONG idx_offset; // base offset - must be added to idx offsets + int num_idx; + + union + { + AviOldIndexEntry *buf_idx; + AviNewIndexEntry *buf_newidx; + }; + int buf_idx_size; + + bool has_indx; + AviSuperIndex video_superindex; + int cur_indx_buf_idx, cur_indx_buf_pos, cur_buf_idx_size; + LONGLONG cur_indx_base_offset; + + int cur_old_buf_idx, cur_buf_video_idx; + +protected: + int AddIndexBlock(); + int AddIdx1Block(); + int AddIdx2Block(); + + void ResetRecoveryMode(); +}; + +#endif + +/////////////////////////////////////////////////////////////// + +#endif // of SP_AVI_H diff --git a/src/bitstream.cpp b/src/bitstream.cpp new file mode 100644 index 0000000..bc735ea --- /dev/null +++ b/src/bitstream.cpp @@ -0,0 +1,180 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - Bitstream reader source file. + * \file bitstream.cpp + * \author bombur + * \version 0.1 + * \date 1.04.2007 + * + * Portions of code taken from libavcodec. + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#include + +#include + +WORD *dec_buf[MAX_BITSTREAM_BUFS], *dec_b[MAX_BITSTREAM_BUFS]; +int dec_len[MAX_BITSTREAM_BUFS] = { 0 }, dec_next_bitidx_shift[MAX_BITSTREAM_BUFS] = { 0 }; +bool dec_eof[MAX_BITSTREAM_BUFS] = { false }; + +static DWORD *enc_buf = NULL; +BOOL enc_eof = FALSE; +DWORD *enc_b = NULL; +int enc_maxlen = 0; + +bitstream_callback callback_func[MAX_BITSTREAM_BUFS] = { NULL }; + +void bitstream_set_callback(int N, bitstream_callback c) +{ + callback_func[N] = c; +} + +int bitstream_decode_buf_init(int N, BYTE *buf, int len, BOOL read_more) +{ + int bytes = (DWORD)buf & 1; + WORD *b = (WORD *)((DWORD)buf & (~1)); + dec_b[N] = dec_buf[N] = b; + + dec_next_bitidx_shift[N] = bytes * 8; + dec_len[N] = len + bytes; + dec_eof[N] = !read_more; + + return 0; +} + +int bitstream_decode_next(int N, int &dec_left) +{ + if (callback_func[N](BITSTREAM_MODE_INPUT, NULL, 0) < 0) + { + dec_eof[N] = true; + return -1; + } + dec_left = dec_len[N]; + return 0; +} + + +int bitstream_decode_eof(int N, DWORD &dec_v, int &dec_bitidx, int &dec_left) +{ + if (dec_eof[N]) + return -1; + register DWORD next_b; + + if (dec_left == 0) + { + if (bitstream_decode_next(N, dec_left) < 0) + return -1; + next_b = *dec_b[N]++; + + if (dec_next_bitidx_shift[N] == 0) + { + // v = cc dd XX YY + // b2 = 34 12 | 78 56 + dec_v |= BWSWAP(next_b) << (31 - 16 - dec_bitidx); + dec_bitidx += 16; + dec_left -= 2; + + } else // dec_next_bitidx_shift == 8 + { + // v = cc dd XX YY + // b2 = 12 cd | 56 34 + + if (dec_bitidx < 8) + { + dec_v |= (next_b >> 8) << (31 - 8 - dec_bitidx); + next_b = *dec_b[N]++; + dec_v |= BWSWAP(next_b) << (31 - 24 - dec_bitidx); + dec_bitidx += 24; + dec_left -= 4; + } + else // dec_bitidx >= 8 + { + dec_v |= (next_b >> 8) << (31 - 8 - dec_bitidx); + dec_bitidx += 8; + dec_left -= 2; + } + } + } else // dec_left == 1 + { + next_b = *dec_b[N]; + if (bitstream_decode_next(N, dec_left) < 0) + return -1; + + if (dec_next_bitidx_shift[N] == 0) + { + // v = cc dd XX YY + // b1 = cd 12 + // b2 = 56 34 | 9a 78 + + if (dec_bitidx < 8) + { + dec_v |= (next_b & 0xff) << (31 - 8 - dec_bitidx); + next_b = *dec_b[N]++; + dec_v |= BWSWAP(next_b) << (31 - 24 - dec_bitidx); + dec_bitidx += 24; + dec_left -= 2; + } + else // dec_bitidx >= 8 + { + dec_v |= (next_b & 0xff) << (31 - 8 - dec_bitidx); + dec_bitidx += 8; + } + } + else // dec_next_bitidx_shift == 8 + { + // v = cc dd XX YY + // b1 = cd 12 + // b2 = 34 cd | 78 56 + + dec_v |= (next_b & 0xff) << (31 - 8 - dec_bitidx); + next_b = *dec_b[N]++; + dec_v |= (next_b >> 8) << (31 - 16 - dec_bitidx); + dec_bitidx += 16; + dec_left -= 2; + } + } + return 0; +} + +//////////////////////////////////////////////////////////////////// + +int bitstream_encode_buf_init(BYTE *buf, int len) +{ + int bytes = (4 - ((DWORD)buf & 3)) & 3; + enc_b = enc_buf = (DWORD *)(((DWORD)buf + 3) & (~3)); + enc_maxlen = (len & (~3)) - 4 - bytes; + + if (enc_maxlen < 1) + return -1; + + enc_eof = false; + return 0; +} + + +int bitstream_flush_output_callback(BITSTREAM_MODE mode, int enc_len) +{ + // [0] ??? + if (callback_func[0](mode, (BYTE *)enc_buf, enc_len) < 0) + { + enc_eof = true; + return -1; + } + return 0; +} + diff --git a/src/bitstream.h b/src/bitstream.h new file mode 100644 index 0000000..85042d9 --- /dev/null +++ b/src/bitstream.h @@ -0,0 +1,217 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - Bitstream reader header file + * \file bitstream.h + * \author bombur + * \version 0.1 + * \date 1.04.2007 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#ifndef SP_BITSTREAM_H +#define SP_BITSTREAM_H + +#define MAX_BITSTREAM_BUFS 5 + +#define INLINE SP_INLINE +#define STATIC static + +typedef enum +{ + BITSTREAM_MODE_DONE = 0, + BITSTREAM_MODE_INPUT = 1, + BITSTREAM_MODE_OUTPUT = 2, +} BITSTREAM_MODE; + +#ifdef __cplusplus +extern "C" { +#endif + +typedef int (*bitstream_callback)(BITSTREAM_MODE status, BYTE *outbuf, int outlen); + +void bitstream_set_callback(int N, bitstream_callback); + +int bitstream_decode_buf_init(int N, BYTE *buf, int len, BOOL read_more); +//int bitstream_decode_eof(int N, DWORD &dec_v, int &dec_bitidx, int &dec_left); + +int bitstream_encode_buf_init(BYTE *buf, int len); +int bitstream_flush_output_callback(BITSTREAM_MODE mode, int enc_len); + +///////////////////////////////////////////////////// + +extern WORD *dec_b[MAX_BITSTREAM_BUFS]; +extern int dec_len[MAX_BITSTREAM_BUFS], dec_next_bitidx_shift[MAX_BITSTREAM_BUFS]; +extern DWORD *enc_b; +extern int enc_maxlen; +extern BOOL enc_eof; + +#ifdef __cplusplus +} +#endif + +#define bitstream_show_bits(ret, dec_v, dec_bitidx, n) \ +{\ + ret = dec_v >> (32 - n);\ +} + +#define bitstream_skip_bits(N, dec_v, dec_bitidx, dec_left, n) \ +{ \ + dec_bitidx -= (n); \ + dec_v <<= (n); \ + if (dec_bitidx < 16) \ + { \ + register DWORD next_b = *dec_b[N]++; \ + dec_v |= BWSWAP(next_b) << (31 - 16 - dec_bitidx); \ + dec_bitidx += 16; \ + dec_left -= 2; \ + } \ +} + +#define bitstream_get_bits(ret, N, dec_v, dec_bitidx, dec_left, n) \ +{ \ + bitstream_show_bits(ret, dec_v, dec_bitidx, n); \ + bitstream_skip_bits(N, dec_v, dec_bitidx, dec_left, n); \ +} + +#define bitstream_decode_start(N, dec_v, dec_bitidx, dec_bitleft) \ +{ \ + register DWORD v2; \ + dec_v = *dec_b[N]++; \ + v2 = *dec_b[N]++; \ + dec_v = (BWSWAP(dec_v) << 16) | BWSWAP(v2); \ + dec_bitidx = 31 - dec_next_bitidx_shift[N]; \ + dec_v <<= dec_next_bitidx_shift[N]; \ + dec_bitleft = dec_len[N] - 4; \ +} + +#define bitstream_get_bits1(ret, N, dec_v, dec_bitidx, dec_left) \ +{ \ + bitstream_show_bits(ret, dec_v, dec_bitidx, 1); \ + bitstream_skip_bits(N, dec_v, dec_bitidx, dec_left, 1); \ +} + +#define bitstream_get_bits012(ret, N, dec_v, dec_bitidx, dec_left) \ +{ \ + const DWORD rets[4] = { /*00b*/0, /*01b*/0, /*10b*/1, /*11b*/2 }; \ + const DWORD skips[4] = { 1, 1, 2, 2 }; \ + bitstream_show_bits(ret, dec_v, dec_bitidx, 2); \ + bitstream_skip_bits(N, dec_v, dec_bitidx, dec_left, skips[ret]); \ + ret = rets[ret]; \ +} + +///////////////////////////////////////////////////////////////////////// +#define bitstream_flush_output(mode, enc_v, enc_bitidx, enc_len) \ +{ \ + DWORD enc_tmp = enc_v; \ + register int shift; \ + if (!enc_eof) \ + { \ + BSWAP(enc_tmp); \ + *enc_b++ = enc_tmp; \ + for (shift = 24; enc_bitidx < 31; shift -= 8, enc_bitidx += 8) \ + { \ + enc_len++; \ + } \ + } \ + \ + if (bitstream_flush_output_callback(mode, enc_len) < 0) \ + { \ + enc_bitidx -= 32; \ + enc_len -= 4; \ + return -1; \ + } \ +} + +#define bitstream_encode_next(enc_v, enc_bitidx, enc_len) \ +{ \ + enc_bitidx += 32; \ + enc_len += 4; \ + if (enc_len >= enc_maxlen) \ + { \ + bitstream_flush_output(BITSTREAM_MODE_OUTPUT, enc_v, enc_bitidx, enc_len); \ + enc_len = 0; \ + enc_v = 0; \ + enc_bitidx = 31; \ + } \ + else \ + { \ + BSWAP(enc_v); \ + *enc_b++ = enc_v; \ + enc_v = 0; \ + } \ +} + +#if 1 +#define bitstream_put_bits(n, data, enc_v, enc_bitidx, enc_len) \ +{ \ + register int shift = enc_bitidx + 1 - (n); \ + if (shift >= 0) \ + { \ + enc_v |= (data) << shift; \ + enc_bitidx -= (n); \ + if (enc_bitidx < 0) \ + bitstream_encode_next(enc_v, enc_bitidx, enc_len); \ + } else \ + { \ + shift = -shift; \ + enc_v |= (data) >> shift; \ + enc_bitidx -= (n) - shift; \ + if (enc_bitidx < 0) \ + bitstream_encode_next(enc_v, enc_bitidx, enc_len); \ + enc_v |= (data) << (32 - shift); \ + enc_bitidx -= shift; \ + if (enc_bitidx < 0) \ + bitstream_encode_next(enc_v, enc_bitidx, enc_len); \ + } \ +} +#else +int FORCEINLINE bitstream_put_bits(int n, DWORD data, DWORD &enc_v, int &enc_bitidx, int &enc_len) +{ + register int shift = enc_bitidx + 1 - (n); + if (shift >= 0) + { + enc_v |= (data) << shift; + enc_bitidx -= (n); + if (enc_bitidx < 0) + { + if (bitstream_encode_next(enc_v, enc_bitidx, enc_len) < 0) + return -1; + } + } else + { + shift = -shift; + enc_v |= (data) >> shift; + enc_bitidx -= (n) - shift; + if (enc_bitidx < 0) + { + if (bitstream_encode_next(enc_v, enc_bitidx, enc_len) < 0) + return -1; + } + enc_v |= (data) << (32 - shift); + enc_bitidx -= shift; + if (enc_bitidx < 0) + { + if (bitstream_encode_next(enc_v, enc_bitidx, enc_len) < 0) + return -1; + } + } + return 0; +} +#endif + + +#endif // of SP_BITSTREAM_H diff --git a/src/cdda.cpp b/src/cdda.cpp new file mode 100644 index 0000000..2c150c3 --- /dev/null +++ b/src/cdda.cpp @@ -0,0 +1,553 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - CDDA (CD-Audio) player impl. + * \file cdda.cpp + * \author bombur + * \version 0.1 + * \date 2.08.2004 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + + +#include "script.h" + +#include "cdda.h" +#include "media.h" + + +extern int cdrom_handle; + + +static Cdda *cdda = NULL; + +static bool cdda_msg = true; +#define MSG if (cdda_msg) msg + + +bool cdda_read_track(CddaTrack *track, int i) +{ + struct cdrom_tocentry e; + memset(&e, 0, sizeof(struct cdrom_tocentry)); + e.cdte_track = (BYTE)i; + e.cdte_format = CDROM_MSF; + if (ioctl(cdrom_handle, CDROMREADTOCENTRY, &e) >= 0) + { + if ((e.cdte_ctrl & CDROM_DATA_TRACK) == 0) + { + if (track != NULL) + { + track->idx = i; + track->frame_idx = e.cdte_addr.msf.minute * CD_SECS * CD_FRAMES + + e.cdte_addr.msf.second * CD_FRAMES + e.cdte_addr.msf.frame; + } + return true; + } + } + return false; +} + +Cdda *cdda_open() +{ + int i; + if (cdda != NULL) + return cdda; + +#ifdef WIN32 + // only for real device! + if (cdrom_handle == 2) + { + MCI_OPEN_PARMS mciOpen; + MCI_STATUS_PARMS mciParams; + MCI_SET_PARMS mciSet; + mciOpen.lpstrDeviceType = (LPCSTR)MCI_DEVTYPE_CD_AUDIO; + mciSendCommand(0, MCI_OPEN, MCI_OPEN_TYPE | MCI_OPEN_TYPE_ID, (DWORD)&mciOpen); + + mciSet.dwTimeFormat = MCI_FORMAT_MSF; + mciSendCommand(mciOpen.wDeviceID, MCI_SET, MCI_SET_TIME_FORMAT, (DWORD)&mciSet); + + mciParams.dwItem = MCI_STATUS_NUMBER_OF_TRACKS; + mciSendCommand(mciOpen.wDeviceID, MCI_STATUS, MCI_STATUS_ITEM, (DWORD)&mciParams); + int numtr = mciParams.dwReturn; + + cdda = new Cdda(); + cdda->tracks.Reserve(numtr); + + for (i = 0; i < numtr; i++) + { + mciParams.dwItem = MCI_CDA_STATUS_TYPE_TRACK; + mciParams.dwTrack = i + 1; + mciSendCommand(mciOpen.wDeviceID, MCI_STATUS, MCI_STATUS_ITEM | MCI_TRACK, (DWORD)&mciParams); + if (mciParams.dwReturn == MCI_CDA_TRACK_AUDIO) + { + CddaTrack track; + mciParams.dwItem = MCI_STATUS_LENGTH; + mciParams.dwTrack = i + 1; + mciSendCommand(mciOpen.wDeviceID, MCI_STATUS, MCI_STATUS_ITEM | MCI_TRACK, (DWORD)&mciParams); + + track.length = MCI_MSF_MINUTE(mciParams.dwReturn) * CD_SECS * CD_FRAMES + + MCI_MSF_SECOND(mciParams.dwReturn) * CD_FRAMES + + MCI_MSF_FRAME(mciParams.dwReturn); + + mciParams.dwItem = MCI_STATUS_POSITION; + mciParams.dwTrack = i + 1; + mciSendCommand(mciOpen.wDeviceID, MCI_STATUS, MCI_STATUS_ITEM | MCI_TRACK, (DWORD)&mciParams); + + track.frame_idx = MCI_MSF_MINUTE(mciParams.dwReturn) * CD_SECS * CD_FRAMES + + MCI_MSF_SECOND(mciParams.dwReturn) * CD_FRAMES + + MCI_MSF_FRAME(mciParams.dwReturn); + + cdda->tracks.Add(track); + } + } + mciSendCommand(mciOpen.wDeviceID, MCI_CLOSE, 0, 0); + return cdda; + } else + { + return NULL; + } +#else + + if (cdrom_handle < 0) + { + msg_error("CDDA: Cannot find opened CD-ROM handle.\n"); + return NULL; + } + + struct cdrom_tochdr toc; + if (ioctl(cdrom_handle, CDROMREADTOCHDR, &toc) < 0) + { + return NULL; + } + + cdda = new Cdda(); + cdda->track1 = toc.cdth_trk0; + cdda->track2 = toc.cdth_trk1; + + cdda->tracks.Reserve(cdda->track2 - cdda->track1 + 1); + int track_idx = 0; + cdda->total_length = 0; + for (i = cdda->track1; i <= cdda->track2; i++) + { + CddaTrack track; + if (cdda_read_track(&track, i)) + { + track_idx = cdda->tracks.Add(track); + if (track_idx > 0) + cdda->tracks[track_idx - 1].length = track.frame_idx - cdda->total_length; + cdda->total_length = track.frame_idx; + } + } + CddaTrack track_leadout; + if (cdda_read_track(&track_leadout, CDROM_LEADOUT)) + { + cdda->tracks[track_idx].length = track_leadout.frame_idx - cdda->total_length; + cdda->total_length = track_leadout.frame_idx; + } + + return cdda; +#endif +} + +BOOL cdda_close() +{ + cdda_stop(); + SPSafeDelete(cdda); + return TRUE; +} + +int cdda_play(char *path) +{ + if (cdrom_handle < 0) + return -1; + if (path == NULL) + { + fip_write_special(FIP_SPECIAL_PLAY, 1); + fip_write_special(FIP_SPECIAL_PAUSE, 0); + mpeg_setspeed(MPEG_SPEED_NORMAL); + + return 0; + } + + if (strncasecmp(path, "/cdrom/", 7) != 0) + return -1; + + int ret = media_open(path, MEDIA_TYPE_CDDA); + if (ret < 0 || cdda == NULL) + { + msg_error("CDDA: media open FAILED.\n"); + return ret; + } + MSG("CDDA: start...\n"); + + if (strlen(path) > 7) + { + cdda->cur_trackid = atoi(path + 7) - 1; + } else + cdda->cur_trackid = 0; + if (cdda->cur_trackid < 0 || cdda->cur_trackid >= cdda->tracks.GetN()) + return -1; + cdda->cur = cdda->tracks[cdda->cur_trackid].frame_idx; + MSG("CDDA: * Play Track = %d (pos = %d).\n", cdda->cur_trackid+1, cdda->cur); + + cdda->feof = false; + + mpeg_init(MPEG_1, TRUE, FALSE, FALSE); + mpeg_setbuffer(MPEG_BUFFER_1, BUF_BASE, 8, 30576); + + MSG("CDDA: Setting default audio params.\n"); + MpegAudioPacketInfo defaudioparams; + defaudioparams.type = eAudioFormat_PCM; + defaudioparams.samplerate = 44100; + defaudioparams.numberofbitspersample = 16; + defaudioparams.numberofchannels = 2; + defaudioparams.fromstream = 0; + mpeg_setaudioparams(&defaudioparams); + + // write dvd-specific FIP stuff... + fip_write_special(FIP_SPECIAL_CD, 1); + const char *digits = " 00000"; + fip_write_string(digits); + fip_write_special(FIP_SPECIAL_COLON1, 1); + fip_write_special(FIP_SPECIAL_COLON2, 1); + + fip_write_special(FIP_SPECIAL_PLAY, 1); + fip_write_special(FIP_SPECIAL_PAUSE, 0); + + script_audio_info_callback(""); + script_video_info_callback(""); + + cdda->playing = true; + mpeg_start(); + + return 0; +} + +int cdda_get_length(char *path) +{ + if (path == NULL) + return 0; + if (strncasecmp(path, "/cdrom/", 7) != 0) + return 0; + if (cdda_open() == NULL) + return 0; + int trackid = atoi(path + 7) - 1; + if (trackid < 0 || trackid >= cdda->tracks.GetN()) + return 0; + return cdda->tracks[trackid].length / CD_FRAMES; +} + +int cdda_get_numtracks() +{ + if (cdda_open() == NULL) + return 0; + return cdda->tracks.GetN(); +} + +BOOL cdda_feof() +{ + if (cdda == NULL || cdda->feof) + return TRUE; + return FALSE; +} + +int cdda_read(BYTE *buf, int numblocks) +{ + if (cdrom_handle < 0 || cdda == NULL) + { + return 0; + } + if (cdda->feof) + { + return 0; + } + if (cdda->cur < 0 || cdda->cur_trackid < 0) + { + return 0; + } + + int maxbl = cdda->tracks[cdda->cur_trackid].frame_idx + cdda->tracks[cdda->cur_trackid].length - 1; + + //MSG("CDDA: %5d / %5d [%d]\n", cdda->cur, maxbl, cdda->feof); + + if (cdda->cur + numblocks > maxbl) + { + numblocks = maxbl - cdda->cur; + cdda->feof = true; + } + if (numblocks < 1) + return 0; + cdrom_read_audio rda; + rda.addr.msf.minute = (BYTE)(cdda->cur / CD_FRAMES / CD_SECS); + rda.addr.msf.second = (BYTE)((cdda->cur / CD_FRAMES) % CD_SECS); + rda.addr.msf.frame = (BYTE)(cdda->cur % CD_FRAMES); + rda.addr_format = CDROM_MSF; + rda.nframes = numblocks; + rda.buf = buf; + + if (ioctl(cdrom_handle, CDROMREADAUDIO, &rda) < 0) + return 0; + + // LPCM requires big-endian byte order + mpeg_PCM_to_LPCM(buf, numblocks * MPEG_PACKET_LENGTH); + cdda->cur += numblocks; + return numblocks; +} + +void cdda_update_info() +{ + if (cdda == NULL) + return; + static int old_secs = 0; + KHWL_TIME_TYPE displ; + displ.pts = 0; + displ.timeres = 90000; + if (mpeg_is_displayed()) + khwl_getproperty(KHWL_TIME_SET, etimSystemTimeClock, sizeof(displ), &displ); +// displ.pts -= pts_base; + + if (cdda->old_trackid != cdda->cur_trackid || (LONGLONG)displ.pts != cdda->saved_pts) + { + if (cdda->cur_trackid >= 0 && (LONGLONG)displ.pts >= 0) + { + if (cdda->cur_trackid != cdda->old_trackid) + { + script_totaltime_callback(cdda->tracks[cdda->cur_trackid].length / CD_FRAMES); + + script_cdda_track_callback(cdda->cur_trackid + 1); + + old_secs = -1; + } + + cdda->old_trackid = cdda->cur_trackid; + cdda->saved_pts = displ.pts; + + char fip_out[10]; + int secs = (int)(displ.pts / 90000); + int ccid = cdda->cur_trackid + 1; + if (ccid < 0) + ccid = 0; + if (ccid > 99) + ccid = 99; + if (secs < 0) + secs = 0; + if (secs >= 10*3600) + secs = 10*3600-1; + if (secs != old_secs) + { + script_time_callback(secs); + + fip_out[0] = (char)((ccid / 10) + '0'); + fip_out[1] = (char)((ccid % 10) + '0'); + fip_out[2] = (char)((secs/3600) + '0'); + int secs3600 = secs%3600; + fip_out[3] = (char)(((secs3600/60)/10) + '0'); + fip_out[4] = (char)(((secs3600/60)%10) + '0'); + fip_out[5] = (char)(((secs3600%60)/10) + '0'); + fip_out[6] = (char)(((secs3600%60)%10) + '0'); + fip_out[7] = '\0'; + fip_write_string(fip_out); + + old_secs = secs; + } + } + } +} + + +int cdda_player_loop() +{ + static int info_cnt = 0; + if (cdrom_handle < 0 || cdda == NULL || cdda->cur < 0) + return 1; + + BYTE *buf = NULL; + + MEDIA_EVENT event = MEDIA_EVENT_OK; + int len = 0; + + int ret = media_get_next_block(&buf, &event, &len); + if (ret > 0 && buf == NULL) + { + msg_error("CDDA: Not initialized. STOP!\n"); + return 1; + } + if (ret == -1) + { + msg_error("CDDA: Error getting next block!\n"); + return 1; + } + else if (ret == 0) // wait... + { + return 0; + } + + if (event == MEDIA_EVENT_STOP) + { + MSG("CDDA: STOP Event triggered!\n"); + return 1; + } + BYTE *base = buf; + + MpegPacket *packet = NULL; + packet = mpeg_feed_getlast(); + if (packet == NULL) // well, it won't really help + return 0; + memset((BYTE *)packet + 4, 0, sizeof(MpegPacket) - 4); + + packet->pts = 0;//pts_base; + packet->nframeheaders = 0xffff; + packet->firstaccessunitpointer = 0; + packet->type = 1; + packet->pData = base; + packet->size = MPEG_PACKET_LENGTH; + + // increase bufidx + mpeg_setbufidx(MPEG_BUFFER_1, packet); + + mpeg_feed(MPEG_FEED_AUDIO); + + if (info_cnt++ > 32) + { + info_cnt = 0; + cdda_update_info(); + } + + return 0; +} + +BOOL cdda_pause() +{ + fip_write_special(FIP_SPECIAL_PLAY, 0); + fip_write_special(FIP_SPECIAL_PAUSE, 1); + + mpeg_setspeed(MPEG_SPEED_PAUSE); + + return TRUE; +} + +BOOL cdda_stop() +{ + if (cdda != NULL) + { + if (cdda->playing) + { + mpeg_deinit(); + cdda->playing = false; + } + } + return TRUE; +} + +void cdda_setdebug(BOOL ison) +{ + cdda_msg = ison == TRUE; +} + +BOOL cdda_getdebug() +{ + return cdda_msg; +} + +BOOL cdda_seek(int seconds, int from_frame) +{ + if (cdda != NULL) + { + if (cdda->cur_trackid >= 0 && cdda->cur_trackid < cdda->tracks.GetN()) + { + // start of the current track + if (from_frame < 0) + from_frame = cdda->tracks[cdda->cur_trackid].frame_idx; + + khwl_stop(); + + cdda->cur = from_frame; + cdda->cur += seconds * CD_FRAMES; + int offs = cdda->cur - cdda->tracks[cdda->cur_trackid].frame_idx; + if (offs < 0) + { + cdda->cur = cdda->tracks[cdda->cur_trackid].frame_idx; + offs = 0; + } + if (offs >= cdda->tracks[cdda->cur_trackid].length) + cdda->cur = cdda->tracks[cdda->cur_trackid].frame_idx + cdda->tracks[cdda->cur_trackid].length - 1; + + mpeg_start(); + mpeg_setpts(offs * 90000 / CD_FRAMES); + + return TRUE; + } + } + script_error_callback(SCRIPT_ERROR_INVALID); + return FALSE; +} + +BOOL cdda_seek_track(int track) +{ + int trck = track - 1; + if (cdda == NULL || trck < 0 || trck >= cdda->tracks.GetN()) + { + script_error_callback(SCRIPT_ERROR_INVALID); + return FALSE; + } + khwl_stop(); + cdda->cur_trackid = trck; + cdda->cur = cdda->tracks[trck].frame_idx; + MSG("CDDA: * Playpos = %d.\n", cdda->cur); + + mpeg_start(); + mpeg_setpts(0); + return TRUE; +} + +void cdda_get_cur(int *track) +{ + if (track != NULL && cdda != NULL) + { + *track = cdda->cur_trackid + 1; + } + +} + +void cdda_forward() +{ + if (cdda != NULL) + cdda_seek(20, cdda->cur); +} + +void cdda_rewind() +{ + if (cdda != NULL) + cdda_seek(-20, cdda->cur); +} diff --git a/src/cdda.h b/src/cdda.h new file mode 100644 index 0000000..4e6da3c --- /dev/null +++ b/src/cdda.h @@ -0,0 +1,131 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - CDDA (CD-Audio) player header file + * \file cdda.h + * \author bombur + * \version 0.1 + * \date 2.08.2004 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#ifndef SP_CDDA_H +#define SP_CDDA_H + +class CddaTrack +{ +public: + /// ctor + CddaTrack() + { + idx = -1; + frame_idx = -1; + length = -1; + } + + int idx; + int frame_idx; + + int length; // in frames +}; + + +class Cdda +{ +public: + /// ctor + Cdda() + { + cur = -1; + track1 = -1; + track2 = -1; + total_length = -1; + + cur_trackid = -1; + old_trackid = -1; + saved_pts = -1; + + feof = false; + playing = false; + } + // current frame + int cur; + + int cur_trackid, old_trackid; + LONGLONG saved_pts; + + // first and last + int track1, track2; + + SPClassicList tracks; + int total_length; // in frames + + bool feof; + bool playing; +}; + + +//////////////////////////////////////////////////////////////////// +#ifdef __cplusplus +extern "C" { +#endif + +#include + + +Cdda *cdda_open(); +BOOL cdda_close(); + +/// Play CD-Audio disc or track +int cdda_play(char *path); + +/// Get number of tracks +int cdda_get_numtracks(); +/// Get track length, in seconds +int cdda_get_length(char *path); + +/// Advance playing +int cdda_player_loop(); + +/// Pause playing +BOOL cdda_pause(); + +/// Stop playing +BOOL cdda_stop(); + +/// Returns TRUE if track ended. +BOOL cdda_feof(); + +void cdda_setdebug(BOOL ison); +BOOL cdda_getdebug(); + +/// Seek to given time and play +BOOL cdda_seek(int seconds, int from_frame = -1); +BOOL cdda_seek_track(int track); + +void cdda_get_cur(int *track); + +void cdda_forward(); +void cdda_rewind(); + +/// Read CDDA data (used by media) +int cdda_read(BYTE *buf, int numblocks); + +#ifdef __cplusplus +} +#endif + +#endif // of SP_CDDA_H diff --git a/src/contrib/libdvdcss b/src/contrib/libdvdcss new file mode 160000 index 0000000..84a7ba8 --- /dev/null +++ b/src/contrib/libdvdcss @@ -0,0 +1 @@ +Subproject commit 84a7ba82a31f4ac73d93de108ee8eaa2d250cf5e diff --git a/src/contrib/libdvdnav b/src/contrib/libdvdnav new file mode 160000 index 0000000..fca2c93 --- /dev/null +++ b/src/contrib/libdvdnav @@ -0,0 +1 @@ +Subproject commit fca2c93a76c51c175206c64681fb607fba91b8e1 diff --git a/src/contrib/libid3tag b/src/contrib/libid3tag new file mode 160000 index 0000000..0637016 --- /dev/null +++ b/src/contrib/libid3tag @@ -0,0 +1 @@ +Subproject commit 06370167ae94fecaf09e9535dbcc6da421780ff0 diff --git a/src/contrib/libjpeg b/src/contrib/libjpeg new file mode 160000 index 0000000..9e0cea2 --- /dev/null +++ b/src/contrib/libjpeg @@ -0,0 +1 @@ +Subproject commit 9e0cea29d7ba7a2c1e763865391bc94b336da25e diff --git a/src/contrib/libmad b/src/contrib/libmad new file mode 160000 index 0000000..c2f96fa --- /dev/null +++ b/src/contrib/libmad @@ -0,0 +1 @@ +Subproject commit c2f96fa4166446ac99449bdf6905f4218fb7d6b5 diff --git a/src/contrib/longjmp.S b/src/contrib/longjmp.S new file mode 100644 index 0000000..497b571 --- /dev/null +++ b/src/contrib/longjmp.S @@ -0,0 +1,44 @@ +/* longjmp for ARM. + Copyright (C) 1997, 1998 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C 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. + + The GNU C 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 the GNU C Library; if not, write to the Free + Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + 02111-1307 USA. */ + +#include +#define _SETJMP_H +#define _ASM +#include + +/* [bombur]: */ +#define __UCLIBC_HAS_SOFT_FLOAT__ + +.globl __longjmp; +.type __longjmp,%function +.align 4; +__longjmp: + mov ip, r0 /* save jmp_buf pointer */ + + movs r0, r1 /* get the return value in place */ + moveq r0, #1 /* can't let setjmp() return zero! */ + +#if defined __UCLIBC_HAS_FLOATS__ && ! defined __UCLIBC_HAS_SOFT_FLOAT__ + lfmfd f4, 4, [ip] ! /* load the floating point regs */ +#else + add ip, ip, #48 /* skip the FP registers */ +#endif + + ldmia ip , {v1-v6, sl, fp, sp, pc} +.size __longjmp,.-__longjmp; diff --git a/src/contrib/memcpy.S b/src/contrib/memcpy.S new file mode 100644 index 0000000..87342f2 --- /dev/null +++ b/src/contrib/memcpy.S @@ -0,0 +1,318 @@ +/* + * linux/arch/arm/lib/memcpy.S + * + * Copyright (C) 1995-1999 Russell King + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * ASM optimised string functions + */ +#include +#include + + .text + +#define ENTER \ + mov ip,sp ;\ + stmfd sp!,{r4-r9,fp,ip,lr,pc} ;\ + sub fp,ip,#4 + +#define EXIT \ + LOADREGS(ea, fp, {r4 - r9, fp, sp, pc}) + +#define EXITEQ \ + LOADREGS(eqea, fp, {r4 - r9, fp, sp, pc}) + +/* + * Prototype: void memcpy(void *to,const void *from,unsigned long n); + * ARM3: cant use memcopy here!!! + */ +ENTRY(memcpy) +ENTRY(memmove) + ENTER + cmp r1, r0 + bcc 19f + subs r2, r2, #4 + blt 6f + ands ip, r0, #3 + bne 7f + ands ip, r1, #3 + bne 8f + +1: subs r2, r2, #8 + blt 5f + subs r2, r2, #0x14 + blt 3f +2: ldmia r1!,{r3 - r9, ip} + stmia r0!,{r3 - r9, ip} + subs r2, r2, #32 + bge 2b + cmn r2, #16 + ldmgeia r1!, {r3 - r6} + stmgeia r0!, {r3 - r6} + subge r2, r2, #0x10 +3: adds r2, r2, #0x14 +4: ldmgeia r1!, {r3 - r5} + stmgeia r0!, {r3 - r5} + subges r2, r2, #12 + bge 4b +5: adds r2, r2, #8 + blt 6f + subs r2, r2, #4 + ldrlt r3, [r1], #4 + ldmgeia r1!, {r4, r5} + strlt r3, [r0], #4 + stmgeia r0!, {r4, r5} + subge r2, r2, #4 + +6: adds r2, r2, #4 + EXITEQ + cmp r2, #2 + ldrb r3, [r1], #1 + ldrgeb r4, [r1], #1 + ldrgtb r5, [r1], #1 + strb r3, [r0], #1 + strgeb r4, [r0], #1 + strgtb r5, [r0], #1 + EXIT + +7: rsb ip, ip, #4 + cmp ip, #2 + ldrb r3, [r1], #1 + ldrgeb r4, [r1], #1 + ldrgtb r5, [r1], #1 + strb r3, [r0], #1 + strgeb r4, [r0], #1 + strgtb r5, [r0], #1 + subs r2, r2, ip + blt 6b + ands ip, r1, #3 + beq 1b + +8: bic r1, r1, #3 + ldr r7, [r1], #4 + cmp ip, #2 + bgt 15f + beq 11f + cmp r2, #12 + blt 10f + sub r2, r2, #12 +9: mov r3, r7, lsr #8 + ldmia r1!, {r4 - r7} + orr r3, r3, r4, lsl #24 + mov r4, r4, lsr #8 + orr r4, r4, r5, lsl #24 + mov r5, r5, lsr #8 + orr r5, r5, r6, lsl #24 + mov r6, r6, lsr #8 + orr r6, r6, r7, lsl #24 + stmia r0!, {r3 - r6} + subs r2, r2, #16 + bge 9b + adds r2, r2, #12 + blt 100f +10: mov r3, r7, lsr #8 + ldr r7, [r1], #4 + subs r2, r2, #4 + orr r3, r3, r7, lsl #24 + str r3, [r0], #4 + bge 10b +100: sub r1, r1, #3 + b 6b + +11: cmp r2, #12 + blt 13f /* */ + sub r2, r2, #12 +12: mov r3, r7, lsr #16 + ldmia r1!, {r4 - r7} + orr r3, r3, r4, lsl #16 + mov r4, r4, lsr #16 + orr r4, r4, r5, lsl #16 + mov r5, r5, lsr #16 + orr r5, r5, r6, lsl #16 + mov r6, r6, lsr #16 + orr r6, r6, r7,LSL#16 + stmia r0!, {r3 - r6} + subs r2, r2, #16 + bge 12b + adds r2, r2, #12 + blt 14f +13: mov r3, r7, lsr #16 + ldr r7, [r1], #4 + subs r2, r2, #4 + orr r3, r3, r7, lsl #16 + str r3, [r0], #4 + bge 13b +14: sub r1, r1, #2 + b 6b + +15: cmp r2, #12 + blt 17f + sub r2, r2, #12 +16: mov r3, r7, lsr #24 + ldmia r1!,{r4 - r7} + orr r3, r3, r4, lsl #8 + mov r4, r4, lsr #24 + orr r4, r4, r5, lsl #8 + mov r5, r5, lsr #24 + orr r5, r5, r6, lsl #8 + mov r6, r6, lsr #24 + orr r6, r6, r7, lsl #8 + stmia r0!, {r3 - r6} + subs r2, r2, #16 + bge 16b + adds r2, r2, #12 + blt 18f +17: mov r3, r7, lsr #24 + ldr r7, [r1], #4 + subs r2, r2, #4 + orr r3, r3, r7, lsl#8 + str r3, [r0], #4 + bge 17b +18: sub r1, r1, #1 + b 6b + + +19: add r1, r1, r2 + add r0, r0, r2 + subs r2, r2, #4 + blt 24f + ands ip, r0, #3 + bne 25f + ands ip, r1, #3 + bne 26f + +20: subs r2, r2, #8 + blt 23f + subs r2, r2, #0x14 + blt 22f +21: ldmdb r1!, {r3 - r9, ip} + stmdb r0!, {r3 - r9, ip} + subs r2, r2, #32 + bge 21b +22: cmn r2, #16 + ldmgedb r1!, {r3 - r6} + stmgedb r0!, {r3 - r6} + subge r2, r2, #16 + adds r2, r2, #20 + ldmgedb r1!, {r3 - r5} + stmgedb r0!, {r3 - r5} + subge r2, r2, #12 +23: adds r2, r2, #8 + blt 24f + subs r2, r2, #4 + ldrlt r3, [r1, #-4]! + ldmgedb r1!, {r4, r5} + strlt r3, [r0, #-4]! + stmgedb r0!, {r4, r5} + subge r2, r2, #4 + +24: adds r2, r2, #4 + EXITEQ + cmp r2, #2 + ldrb r3, [r1, #-1]! + ldrgeb r4, [r1, #-1]! + ldrgtb r5, [r1, #-1]! + strb r3, [r0, #-1]! + strgeb r4, [r0, #-1]! + strgtb r5, [r0, #-1]! + EXIT + +25: cmp ip, #2 + ldrb r3, [r1, #-1]! + ldrgeb r4, [r1, #-1]! + ldrgtb r5, [r1, #-1]! + strb r3, [r0, #-1]! + strgeb r4, [r0, #-1]! + strgtb r5, [r0, #-1]! + subs r2, r2, ip + blt 24b + ands ip, r1, #3 + beq 20b + +26: bic r1, r1, #3 + ldr r3, [r1], #0 + cmp ip, #2 + blt 34f + beq 30f + cmp r2, #12 + blt 28f + sub r2, r2, #12 +27: mov r7, r3, lsl #8 + ldmdb r1!, {r3, r4, r5, r6} + orr r7, r7, r6, lsr #24 + mov r6, r6, lsl #8 + orr r6, r6, r5, lsr #24 + mov r5, r5, lsl #8 + orr r5, r5, r4, lsr #24 + mov r4, r4, lsl #8 + orr r4, r4, r3, lsr #24 + stmdb r0!, {r4, r5, r6, r7} + subs r2, r2, #16 + bge 27b + adds r2, r2, #12 + blt 29f +28: mov ip, r3, lsl #8 + ldr r3, [r1, #-4]! + subs r2, r2, #4 + orr ip, ip, r3, lsr #24 + str ip, [r0, #-4]! + bge 28b +29: add r1, r1, #3 + b 24b + +30: cmp r2, #12 + blt 32f + sub r2, r2, #12 +31: mov r7, r3, lsl #16 + ldmdb r1!, {r3, r4, r5, r6} + orr r7, r7, r6, lsr #16 + mov r6, r6, lsl #16 + orr r6, r6, r5, lsr #16 + mov r5, r5, lsl #16 + orr r5, r5, r4, lsr #16 + mov r4, r4, lsl #16 + orr r4, r4, r3, lsr #16 + stmdb r0!, {r4, r5, r6, r7} + subs r2, r2, #16 + bge 31b + adds r2, r2, #12 + blt 33f +32: mov ip, r3, lsl #16 + ldr r3, [r1, #-4]! + subs r2, r2, #4 + orr ip, ip, r3, lsr #16 + str ip, [r0, #-4]! + bge 32b +33: add r1, r1, #2 + b 24b + +34: cmp r2, #12 + blt 36f + sub r2, r2, #12 +35: mov r7, r3, lsl #24 + ldmdb r1!, {r3, r4, r5, r6} + orr r7, r7, r6, lsr #8 + mov r6, r6, lsl #24 + orr r6, r6, r5, lsr #8 + mov r5, r5, lsl #24 + orr r5, r5, r4, lsr #8 + mov r4, r4, lsl #24 + orr r4, r4, r3, lsr #8 + stmdb r0!, {r4, r5, r6, r7} + subs r2, r2, #16 + bge 35b + adds r2, r2, #12 + blt 37f +36: mov ip, r3, lsl #24 + ldr r3, [r1, #-4]! + subs r2, r2, #4 + orr ip, ip, r3, lsr #8 + str ip, [r0, #-4]! + bge 36b +37: add r1, r1, #1 + b 24b + + .align diff --git a/src/contrib/memset.S b/src/contrib/memset.S new file mode 100644 index 0000000..a1795f5 --- /dev/null +++ b/src/contrib/memset.S @@ -0,0 +1,80 @@ +/* + * linux/arch/arm/lib/memset.S + * + * Copyright (C) 1995-2000 Russell King + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * ASM optimised string functions + */ +#include +#include + + .text + .align 5 + .word 0 + +1: subs r2, r2, #4 @ 1 do we have enough + blt 5f @ 1 bytes to align with? + cmp r3, #2 @ 1 + strltb r1, [r0], #1 @ 1 + strleb r1, [r0], #1 @ 1 + strb r1, [r0], #1 @ 1 + add r2, r2, r3 @ 1 (r2 = r2 - (4 - r3)) +/* + * The pointer is now aligned and the length is adjusted. Try doing the + * memzero again. + */ + +ENTRY(memset) + ands r3, r0, #3 @ 1 unaligned? + bne 1b @ 1 +/* + * we know that the pointer in r0 is aligned to a word boundary. + */ + orr r1, r1, r1, lsl #8 + orr r1, r1, r1, lsl #16 + mov r3, r1 + cmp r2, #16 + blt 4f +/* + * We need an extra register for this loop - save the return address and + * use the LR + */ + str lr, [sp, #-4]! + mov ip, r1 + mov lr, r1 + +2: subs r2, r2, #64 + stmgeia r0!, {r1, r3, ip, lr} @ 64 bytes at a time. + stmgeia r0!, {r1, r3, ip, lr} + stmgeia r0!, {r1, r3, ip, lr} + stmgeia r0!, {r1, r3, ip, lr} + bgt 2b + LOADREGS(eqfd, sp!, {pc}) @ Now <64 bytes to go. +/* + * No need to correct the count; we're only testing bits from now on + */ + tst r2, #32 + stmneia r0!, {r1, r3, ip, lr} + stmneia r0!, {r1, r3, ip, lr} + tst r2, #16 + stmneia r0!, {r1, r3, ip, lr} + ldr lr, [sp], #4 + +4: tst r2, #8 + stmneia r0!, {r1, r3} + tst r2, #4 + strne r1, [r0], #4 +/* + * When we get here, we've got less than 4 bytes to zero. We + * may have an unaligned pointer as well. + */ +5: tst r2, #2 + strneb r1, [r0], #1 + strneb r1, [r0], #1 + tst r2, #1 + strneb r1, [r0], #1 + RETINSTR(mov,pc,lr) diff --git a/src/contrib/setjmp.S b/src/contrib/setjmp.S new file mode 100644 index 0000000..fb99710 --- /dev/null +++ b/src/contrib/setjmp.S @@ -0,0 +1,47 @@ +/* setjmp for ARM. + Copyright (C) 1997, 1998 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C 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. + + The GNU C 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 the GNU C Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. */ + +#include +#define _SETJMP_H +#define _ASM +#include + +/* [bombur]: */ +#ifndef __UCLIBC_HAS_SOFT_FLOAT__ +#define __UCLIBC_HAS_SOFT_FLOAT__ +#endif + +.globl __sigsetjmp; +.type __sigsetjmp,%function +.align 4; +__sigsetjmp: + /* Save registers */ +#if defined __UCLIBC_HAS_FLOATS__ && ! defined __UCLIBC_HAS_SOFT_FLOAT__ + sfmea f4, 4, [r0]! +#else + add r0, r0, #48 /* skip the FP registers */ +#endif + stmia r0, {v1-v6, sl, fp, sp, lr} + + /* Restore pointer to jmp_buf */ + sub r0, r0, #48 + + /* Make a tail call to __sigjmp_save; it takes the same args. */ + B __sigjmp_save (PLT) +.size __sigsetjmp,.-__sigsetjmp; diff --git a/src/contrib/strcmp.S b/src/contrib/strcmp.S new file mode 100644 index 0000000..b2f26d6 --- /dev/null +++ b/src/contrib/strcmp.S @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2002 ARM Ltd + * 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. + * 3. The name of the company may not be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY ARM LTD ``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 ARM LTD 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. + * + * Adapted for uClibc from NetBSD strcmp.S, version 1.3 2003/04/05 + * by Erik Andersen + */ + + .text + .global strcmp; + .type strcmp,%function + .align 4; \ + +strcmp: +1: + ldrb r2, [r0], #1 + ldrb r3, [r1], #1 + cmp r2, #1 + cmpcs r2, r3 + beq 1b + sub r0, r2, r3 + mov pc, lr + +.weak strcoll; + strcoll = strcmp + diff --git a/src/contrib/strlen.S b/src/contrib/strlen.S new file mode 100644 index 0000000..65ee94d --- /dev/null +++ b/src/contrib/strlen.S @@ -0,0 +1,80 @@ +/* Copyright (C) 1998 Free Software Foundation, Inc. + This file is part of the GNU C Library. + Code contributed by Matthew Wilcox + + The GNU C 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. + + The GNU C 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 the GNU C Library; if not, write to the Free + Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA + 02111-1307 USA. */ + +#include +#include + +/* size_t strlen(const char *S) + * entry: r0 -> string + * exit: r0 = len + */ + + .text + .global strlen; + .type strlen,%function + .align 4; \ + +strlen: + bic r1, r0, $3 @ addr of word containing first byte + ldr r2, [r1], $4 @ get the first word + ands r3, r0, $3 @ how many bytes are duff? + rsb r0, r3, $0 @ get - that number into counter. + beq Laligned @ skip into main check routine if no + @ more +#if __BYTE_ORDER == __BIG_ENDIAN + orr r2, r2, $0xff000000 @ set this byte to non-zero + subs r3, r3, $1 @ any more to do? + orrgt r2, r2, $0x00ff0000 @ if so, set this byte + subs r3, r3, $1 @ more? + orrgt r2, r2, $0x0000ff00 @ then set. +#else + orr r2, r2, $0x000000ff @ set this byte to non-zero + subs r3, r3, $1 @ any more to do? + orrgt r2, r2, $0x0000ff00 @ if so, set this byte + subs r3, r3, $1 @ more? + orrgt r2, r2, $0x00ff0000 @ then set. +#endif +Laligned: @ here, we have a word in r2. Does it + tst r2, $0x000000ff @ contain any zeroes? + tstne r2, $0x0000ff00 @ + tstne r2, $0x00ff0000 @ + tstne r2, $0xff000000 @ + addne r0, r0, $4 @ if not, the string is 4 bytes longer + ldrne r2, [r1], $4 @ and we continue to the next word + bne Laligned @ +Llastword: @ drop through to here once we find a +#if __BYTE_ORDER == __BIG_ENDIAN + tst r2, $0xff000000 @ word that has a zero byte in it + addne r0, r0, $1 @ + tstne r2, $0x00ff0000 @ and add up to 3 bytes on to it + addne r0, r0, $1 @ + tstne r2, $0x0000ff00 @ (if first three all non-zero, 4th + addne r0, r0, $1 @ must be zero) +#else + tst r2, $0x000000ff @ + addne r0, r0, $1 @ + tstne r2, $0x0000ff00 @ and add up to 3 bytes on to it + addne r0, r0, $1 @ + tstne r2, $0x00ff0000 @ (if first three all non-zero, 4th + addne r0, r0, $1 @ must be zero) +#endif + mov pc,lr + +.size strlen,.-strlen; + diff --git a/src/divx-tables.h b/src/divx-tables.h new file mode 100644 index 0000000..8410b04 --- /dev/null +++ b/src/divx-tables.h @@ -0,0 +1,2072 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - DivX3/MPEG-4 tables header file + * \file divx-tables.h + * \author bombur + * \version 0.1 + * \date 1.04.2007 + * + * Portions of code taken from libavcodec. + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#ifndef SP_DIVX_TABLES_H +#define SP_DIVX_TABLES_H + +static const WORD table0_vlc[133][2] = +{ + { 0x1, 2 }, { 0x6, 3 }, { 0xf, 4 }, { 0x16, 5 }, + { 0x20, 6 }, { 0x18, 7 }, { 0x8, 8 }, { 0x9a, 8 }, + { 0x56, 9 }, { 0x13e, 9 }, { 0xf0, 10 }, { 0x3a5, 10 }, + { 0x77, 11 }, { 0x1ef, 11 }, { 0x9a, 12 }, { 0x5d, 13 }, + { 0x1, 4 }, { 0x11, 5 }, { 0x2, 7 }, { 0xb, 8 }, + { 0x12, 9 }, { 0x1d6, 9 }, { 0x27e, 10 }, { 0x191, 11 }, + { 0xea, 12 }, { 0x3dc, 12 }, { 0x13b, 13 }, { 0x4, 5 }, + { 0x14, 7 }, { 0x9e, 8 }, { 0x9, 10 }, { 0x1ac, 11 }, + { 0x1e2, 11 }, { 0x3ca, 12 }, { 0x5f, 13 }, { 0x17, 5 }, + { 0x4e, 7 }, { 0x5e, 9 }, { 0xf3, 10 }, { 0x1ad, 11 }, + { 0xec, 12 }, { 0x5f0, 13 }, { 0xe, 6 }, { 0xe1, 8 }, + { 0x3a4, 10 }, { 0x9c, 12 }, { 0x13d, 13 }, { 0x3b, 6 }, + { 0x1c, 9 }, { 0x14, 11 }, { 0x9be, 12 }, { 0x6, 7 }, + { 0x7a, 9 }, { 0x190, 11 }, { 0x137, 13 }, { 0x1b, 7 }, + { 0x8, 10 }, { 0x75c, 11 }, { 0x71, 7 }, { 0xd7, 10 }, + { 0x9bf, 12 }, { 0x7, 8 }, { 0xaf, 10 }, { 0x4cc, 11 }, + { 0x34, 8 }, { 0x265, 10 }, { 0x9f, 12 }, { 0xe0, 8 }, + { 0x16, 11 }, { 0x327, 12 }, { 0x15, 9 }, { 0x17d, 11 }, + { 0xebb, 12 }, { 0x14, 9 }, { 0xf6, 10 }, { 0x1e4, 11 }, + { 0xcb, 10 }, { 0x99d, 12 }, { 0xca, 10 }, { 0x2fc, 12 }, + { 0x17f, 11 }, { 0x4cd, 11 }, { 0x2fd, 12 }, { 0x4fe, 11 }, + { 0x13a, 13 }, { 0xa, 4 }, { 0x42, 7 }, { 0x1d3, 9 }, + { 0x4dd, 11 }, { 0x12, 5 }, { 0xe8, 8 }, { 0x4c, 11 }, + { 0x136, 13 }, { 0x39, 6 }, { 0x264, 10 }, { 0xeba, 12 }, + { 0x0, 7 }, { 0xae, 10 }, { 0x99c, 12 }, { 0x1f, 7 }, + { 0x4de, 11 }, { 0x43, 7 }, { 0x4dc, 11 }, { 0x3, 8 }, + { 0x3cb, 12 }, { 0x6, 8 }, { 0x99e, 12 }, { 0x2a, 8 }, + { 0x5f1, 13 }, { 0xf, 8 }, { 0x9fe, 12 }, { 0x33, 8 }, + { 0x9ff, 12 }, { 0x98, 8 }, { 0x99f, 12 }, { 0xea, 8 }, + { 0x13c, 13 }, { 0x2e, 8 }, { 0x192, 11 }, { 0x136, 9 }, + { 0x6a, 9 }, { 0x15, 11 }, { 0x3af, 10 }, { 0x1e3, 11 }, + { 0x74, 11 }, { 0xeb, 12 }, { 0x2f9, 12 }, { 0x5c, 13 }, + { 0xed, 12 }, { 0x3dd, 12 }, { 0x326, 12 }, { 0x5e, 13 }, + { 0x16, 7 }, +}; + +static const signed char table0_level[132] = +{ + 1, 2, 3, 4, 5, 6, 7, 8, + 9, 10, 11, 12, 13, 14, 15, 16, + 1, 2, 3, 4, 5, 6, 7, 8, + 9, 10, 11, 1, 2, 3, 4, 5, + 6, 7, 8, 1, 2, 3, 4, 5, + 6, 7, 1, 2, 3, 4, 5, 1, + 2, 3, 4, 1, 2, 3, 4, 1, + 2, 3, 1, 2, 3, 1, 2, 3, + 1, 2, 3, 1, 2, 3, 1, 2, + 3, 1, 2, 3, 1, 2, 1, 2, + 1, 1, 1, 1, 1, 1, 2, 3, + 4, 1, 2, 3, 4, 1, 2, 3, + 1, 2, 3, 1, 2, 1, 2, 1, + 2, 1, 2, 1, 2, 1, 2, 1, + 2, 1, 2, 1, 2, 1, 2, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, +}; + +static const signed char table0_run[132] = +{ + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 2, 2, 2, 2, 2, + 2, 2, 2, 3, 3, 3, 3, 3, + 3, 3, 4, 4, 4, 4, 4, 5, + 5, 5, 5, 6, 6, 6, 6, 7, + 7, 7, 8, 8, 8, 9, 9, 9, + 10, 10, 10, 11, 11, 11, 12, 12, + 12, 13, 13, 13, 14, 14, 15, 15, + 16, 17, 18, 19, 20, 0, 0, 0, + 0, 1, 1, 1, 1, 2, 2, 2, + 3, 3, 3, 4, 4, 5, 5, 6, + 6, 7, 7, 8, 8, 9, 9, 10, + 10, 11, 11, 12, 12, 13, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, + 23, 24, 25, 26, +}; + +/* vlc table 1, for intra chroma and P macro blocks */ + +static const WORD table1_vlc[149][2] = +{ + { 0x4, 3 }, { 0x14, 5 }, { 0x17, 7 }, { 0x7f, 8 }, + { 0x154, 9 }, { 0x1f2, 10 }, { 0xbf, 11 }, { 0x65, 12 }, + { 0xaaa, 12 }, { 0x630, 13 }, { 0x1597, 13 },{ 0x3b7, 14 }, + { 0x2b22, 14 },{ 0xbe6, 15 }, { 0xb, 4 }, { 0x37, 7 }, + { 0x62, 9 }, { 0x7, 11 }, { 0x166, 12 }, { 0xce, 13 }, + { 0x1590, 13 },{ 0x5f6, 14 }, { 0xbe7, 15 }, { 0x7, 5 }, + { 0x6d, 8 }, { 0x3, 11 }, { 0x31f, 12 }, { 0x5f2, 14 }, + { 0x2, 6 }, { 0x61, 9 }, { 0x55, 12 }, { 0x1df, 14 }, + { 0x1a, 6 }, { 0x1e, 10 }, { 0xac9, 12 }, { 0x2b23, 14 }, + { 0x1e, 6 }, { 0x1f, 10 }, { 0xac3, 12 }, { 0x2b2b, 14 }, + { 0x6, 7 }, { 0x4, 11 }, { 0x2f8, 13 }, { 0x19, 7 }, + { 0x6, 11 }, { 0x63d, 13 }, { 0x57, 7 }, { 0x182, 11 }, + { 0x2aa2, 14 },{ 0x4, 8 }, { 0x180, 11 }, { 0x59c, 14 }, + { 0x7d, 8 }, { 0x164, 12 }, { 0x76d, 15 }, { 0x2, 9 }, + { 0x18d, 11 }, { 0x1581, 13 },{ 0xad, 8 }, { 0x60, 12 }, + { 0xc67, 14 }, { 0x1c, 9 }, { 0xee, 13 }, { 0x3, 9 }, + { 0x2cf, 13 }, { 0xd9, 9 }, { 0x1580, 13 },{ 0x2, 11 }, + { 0x183, 11 }, { 0x57, 12 }, { 0x61, 12 }, { 0x31, 11 }, + { 0x66, 12 }, { 0x631, 13 }, { 0x632, 13 }, { 0xac, 13 }, + { 0x31d, 12 }, { 0x76, 12 }, { 0x3a, 11 }, { 0x165, 12 }, + { 0xc66, 14 }, { 0x3, 2 }, { 0x54, 7 }, { 0x2ab, 10 }, + { 0x16, 13 }, { 0x5f7, 14 }, { 0x5, 4 }, { 0xf8, 9 }, + { 0xaa9, 12 }, { 0x5f, 15 }, { 0x4, 4 }, { 0x1c, 10 }, + { 0x1550, 13 },{ 0x4, 5 }, { 0x77, 11 }, { 0x76c, 15 }, + { 0xe, 5 }, { 0xa, 12 }, { 0xc, 5 }, { 0x562, 11 }, + { 0x4, 6 }, { 0x31c, 12 }, { 0x6, 6 }, { 0xc8, 13 }, + { 0xd, 6 }, { 0x1da, 13 }, { 0x7, 6 }, { 0xc9, 13 }, + { 0x1, 7 }, { 0x2e, 14 }, { 0x14, 7 }, { 0x1596, 13 }, + { 0xa, 7 }, { 0xac2, 12 }, { 0x16, 7 }, { 0x15b, 14 }, + { 0x15, 7 }, { 0x15a, 14 }, { 0xf, 8 }, { 0x5e, 15 }, + { 0x7e, 8 }, { 0xab, 8 }, { 0x2d, 9 }, { 0xd8, 9 }, + { 0xb, 9 }, { 0x14, 10 }, { 0x2b3, 10 }, { 0x1f3, 10 }, + { 0x3a, 10 }, { 0x0, 10 }, { 0x58, 10 }, { 0x2e, 9 }, + { 0x5e, 10 }, { 0x563, 11 }, { 0xec, 12 }, { 0x54, 12 }, + { 0xac1, 12 }, { 0x1556, 13 },{ 0x2fa, 13 }, { 0x181, 11 }, + { 0x1557, 13 },{ 0x59d, 14 }, { 0x2aa3, 14 },{ 0x2b2a, 14 }, + { 0x1de, 14 }, { 0x63c, 13 }, { 0xcf, 13 }, { 0x1594, 13 }, + { 0xd, 9 }, +}; + +static const signed char table1_level[148] = +{ + 1, 2, 3, 4, 5, 6, 7, 8, + 9, 10, 11, 12, 13, 14, 1, 2, + 3, 4, 5, 6, 7, 8, 9, 1, + 2, 3, 4, 5, 1, 2, 3, 4, + 1, 2, 3, 4, 1, 2, 3, 4, + 1, 2, 3, 1, 2, 3, 1, 2, + 3, 1, 2, 3, 1, 2, 3, 1, + 2, 3, 1, 2, 3, 1, 2, 1, + 2, 1, 2, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 2, 3, 4, 5, 1, 2, + 3, 4, 1, 2, 3, 1, 2, 3, + 1, 2, 1, 2, 1, 2, 1, 2, + 1, 2, 1, 2, 1, 2, 1, 2, + 1, 2, 1, 2, 1, 2, 1, 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, +}; + +static const signed char table1_run[148] = +{ + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 2, + 2, 2, 2, 2, 3, 3, 3, 3, + 4, 4, 4, 4, 5, 5, 5, 5, + 6, 6, 6, 7, 7, 7, 8, 8, + 8, 9, 9, 9, 10, 10, 10, 11, + 11, 11, 12, 12, 12, 13, 13, 14, + 14, 15, 15, 16, 17, 18, 19, 20, + 21, 22, 23, 24, 25, 26, 27, 28, + 29, 0, 0, 0, 0, 0, 1, 1, + 1, 1, 2, 2, 2, 3, 3, 3, + 4, 4, 5, 5, 6, 6, 7, 7, + 8, 8, 9, 9, 10, 10, 11, 11, + 12, 12, 13, 13, 14, 14, 15, 15, + 16, 17, 18, 19, 20, 21, 22, 23, + 24, 25, 26, 27, 28, 29, 30, 31, + 32, 33, 34, 35, 36, 37, 38, 39, + 40, 41, 42, 43, +}; + +/* third vlc table */ + +static const WORD table2_vlc[186][2] = +{ + { 0x1, 2 }, { 0x5, 3 }, { 0xd, 4 }, { 0x12, 5 }, + { 0xe, 6 }, { 0x15, 7 }, { 0x13, 8 }, { 0x3f, 8 }, + { 0x4b, 9 }, { 0x11f, 9 }, { 0xb8, 10 }, { 0x3e3, 10 }, + { 0x172, 11 }, { 0x24d, 12 }, { 0x3da, 12 }, { 0x2dd, 13 }, + { 0x1f55, 13 },{ 0x5b9, 14 }, { 0x3eae, 14 },{ 0x0, 4 }, + { 0x10, 5 }, { 0x8, 7 }, { 0x20, 8 }, { 0x29, 9 }, + { 0x1f4, 9 }, { 0x233, 10 }, { 0x1e0, 11 }, { 0x12a, 12 }, + { 0x3dd, 12 }, { 0x50a, 13 }, { 0x1f29, 13 },{ 0xa42, 14 }, + { 0x1272, 15 },{ 0x1737, 15 },{ 0x3, 5 }, { 0x11, 7 }, + { 0xc4, 8 }, { 0x4b, 10 }, { 0xb4, 11 }, { 0x7d4, 11 }, + { 0x345, 12 }, { 0x2d7, 13 }, { 0x7bf, 13 }, { 0x938, 14 }, + { 0xbbb, 14 }, { 0x95e, 15 }, { 0x13, 5 }, { 0x78, 7 }, + { 0x69, 9 }, { 0x232, 10 }, { 0x461, 11 }, { 0x3ec, 12 }, + { 0x520, 13 }, { 0x1f2a, 13 },{ 0x3e50, 14 },{ 0x3e51, 14 }, + { 0x1486, 15 },{ 0xc, 6 }, { 0x24, 9 }, { 0x94, 11 }, + { 0x8c0, 12 }, { 0xf09, 14 }, { 0x1ef0, 15 },{ 0x3d, 6 }, + { 0x53, 9 }, { 0x1a0, 11 }, { 0x2d6, 13 }, { 0xf08, 14 }, + { 0x13, 7 }, { 0x7c, 9 }, { 0x7c1, 11 }, { 0x4ac, 14 }, + { 0x1b, 7 }, { 0xa0, 10 }, { 0x344, 12 }, { 0xf79, 14 }, + { 0x79, 7 }, { 0x3e1, 10 }, { 0x2d4, 13 }, { 0x2306, 14 }, + { 0x21, 8 }, { 0x23c, 10 }, { 0xfae, 12 }, { 0x23de, 14 }, + { 0x35, 8 }, { 0x175, 11 }, { 0x7b3, 13 }, { 0xc5, 8 }, + { 0x174, 11 }, { 0x785, 13 }, { 0x48, 9 }, { 0x1a3, 11 }, + { 0x49e, 13 }, { 0x2c, 9 }, { 0xfa, 10 }, { 0x7d6, 11 }, + { 0x92, 10 }, { 0x5cc, 13 }, { 0x1ef1, 15 },{ 0xa3, 10 }, + { 0x3ed, 12 }, { 0x93e, 14 }, { 0x1e2, 11 }, { 0x1273, 15 }, + { 0x7c4, 11 }, { 0x1487, 15 },{ 0x291, 12 }, { 0x293, 12 }, + { 0xf8a, 12 }, { 0x509, 13 }, { 0x508, 13 }, { 0x78d, 13 }, + { 0x7be, 13 }, { 0x78c, 13 }, { 0x4ae, 14 }, { 0xbba, 14 }, + { 0x2307, 14 },{ 0xb9a, 14 }, { 0x1736, 15 },{ 0xe, 4 }, + { 0x45, 7 }, { 0x1f3, 9 }, { 0x47a, 11 }, { 0x5dc, 13 }, + { 0x23df, 14 },{ 0x19, 5 }, { 0x28, 9 }, { 0x176, 11 }, + { 0x49d, 13 }, { 0x23dd, 14 },{ 0x30, 6 }, { 0xa2, 10 }, + { 0x2ef, 12 }, { 0x5b8, 14 }, { 0x3f, 6 }, { 0xa5, 10 }, + { 0x3db, 12 }, { 0x93f, 14 }, { 0x44, 7 }, { 0x7cb, 11 }, + { 0x95f, 15 }, { 0x63, 7 }, { 0x3c3, 12 }, { 0x15, 8 }, + { 0x8f6, 12 }, { 0x17, 8 }, { 0x498, 13 }, { 0x2c, 8 }, + { 0x7b2, 13 }, { 0x2f, 8 }, { 0x1f54, 13 },{ 0x8d, 8 }, + { 0x7bd, 13 }, { 0x8e, 8 }, { 0x1182, 13 },{ 0xfb, 8 }, + { 0x50b, 13 }, { 0x2d, 8 }, { 0x7c0, 11 }, { 0x79, 9 }, + { 0x1f5f, 13 },{ 0x7a, 9 }, { 0x1f56, 13 },{ 0x231, 10 }, + { 0x3e4, 10 }, { 0x1a1, 11 }, { 0x143, 11 }, { 0x1f7, 11 }, + { 0x16f, 12 }, { 0x292, 12 }, { 0x2e7, 12 }, { 0x16c, 12 }, + { 0x16d, 12 }, { 0x3dc, 12 }, { 0xf8b, 12 }, { 0x499, 13 }, + { 0x3d8, 12 }, { 0x78e, 13 }, { 0x2d5, 13 }, { 0x1f5e, 13 }, + { 0x1f2b, 13 },{ 0x78f, 13 }, { 0x4ad, 14 }, { 0x3eaf, 14 }, + { 0x23dc, 14 },{ 0x4a, 9 }, +}; + +static const signed char table2_level[185] = +{ + 1, 2, 3, 4, 5, 6, 7, 8, + 9, 10, 11, 12, 13, 14, 15, 16, + 17, 18, 19, 1, 2, 3, 4, 5, + 6, 7, 8, 9, 10, 11, 12, 13, + 14, 15, 1, 2, 3, 4, 5, 6, + 7, 8, 9, 10, 11, 12, 1, 2, + 3, 4, 5, 6, 7, 8, 9, 10, + 11, 1, 2, 3, 4, 5, 6, 1, + 2, 3, 4, 5, 1, 2, 3, 4, + 1, 2, 3, 4, 1, 2, 3, 4, + 1, 2, 3, 4, 1, 2, 3, 1, + 2, 3, 1, 2, 3, 1, 2, 3, + 1, 2, 3, 1, 2, 3, 1, 2, + 1, 2, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 2, 3, 4, 5, 6, 1, 2, 3, + 4, 5, 1, 2, 3, 4, 1, 2, + 3, 4, 1, 2, 3, 1, 2, 1, + 2, 1, 2, 1, 2, 1, 2, 1, + 2, 1, 2, 1, 2, 1, 2, 1, + 2, 1, 2, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, +}; + +static const signed char table2_run[185] = +{ + 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, 1, 1, 1, 1, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, + 3, 4, 4, 4, 4, 4, 4, 5, + 5, 5, 5, 5, 6, 6, 6, 6, + 7, 7, 7, 7, 8, 8, 8, 8, + 9, 9, 9, 9, 10, 10, 10, 11, + 11, 11, 12, 12, 12, 13, 13, 13, + 14, 14, 14, 15, 15, 15, 16, 16, + 17, 17, 18, 19, 20, 21, 22, 23, + 24, 25, 26, 27, 28, 29, 30, 0, + 0, 0, 0, 0, 0, 1, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, + 3, 3, 4, 4, 4, 5, 5, 6, + 6, 7, 7, 8, 8, 9, 9, 10, + 10, 11, 11, 12, 12, 13, 13, 14, + 14, 15, 15, 16, 17, 18, 19, 20, + 21, 22, 23, 24, 25, 26, 27, 28, + 29, 30, 31, 32, 33, 34, 35, 36, + 37, +}; + +/* second non intra vlc table */ +static const WORD table4_vlc[169][2] = +{ + { 0x0, 3 }, { 0x3, 4 }, { 0xb, 5 }, { 0x14, 6 }, + { 0x3f, 6 }, { 0x5d, 7 }, { 0xa2, 8 }, { 0xac, 9 }, + { 0x16e, 9 }, { 0x20a, 10 }, { 0x2e2, 10 }, { 0x432, 11 }, + { 0x5c9, 11 }, { 0x827, 12 }, { 0xb54, 12 }, { 0x4e6, 13 }, + { 0x105f, 13 },{ 0x172a, 13 },{ 0x20b2, 14 },{ 0x2d4e, 14 }, + { 0x39f0, 14 },{ 0x4175, 15 },{ 0x5a9e, 15 },{ 0x4, 4 }, + { 0x1e, 5 }, { 0x42, 7 }, { 0xb6, 8 }, { 0x173, 9 }, + { 0x395, 10 }, { 0x72e, 11 }, { 0xb94, 12 }, { 0x16a4, 13 }, + { 0x20b3, 14 },{ 0x2e45, 14 },{ 0x5, 5 }, { 0x40, 7 }, + { 0x49, 9 }, { 0x28f, 10 }, { 0x5cb, 11 }, { 0x48a, 13 }, + { 0x9dd, 14 }, { 0x73e2, 15 },{ 0x18, 5 }, { 0x25, 8 }, + { 0x8a, 10 }, { 0x51b, 11 }, { 0xe5f, 12 }, { 0x9c9, 14 }, + { 0x139c, 15 },{ 0x29, 6 }, { 0x4f, 9 }, { 0x412, 11 }, + { 0x48d, 13 }, { 0x2e41, 14 },{ 0x38, 6 }, { 0x10e, 9 }, + { 0x5a8, 11 }, { 0x105c, 13 },{ 0x39f2, 14 },{ 0x58, 7 }, + { 0x21f, 10 }, { 0xe7e, 12 }, { 0x39ff, 14 },{ 0x23, 8 }, + { 0x2e3, 10 }, { 0x4e5, 13 }, { 0x2e40, 14 },{ 0xa1, 8 }, + { 0x5be, 11 }, { 0x9c8, 14 }, { 0x83, 8 }, { 0x13a, 11 }, + { 0x1721, 13 },{ 0x44, 9 }, { 0x276, 12 }, { 0x39f6, 14 }, + { 0x8b, 10 }, { 0x4ef, 13 }, { 0x5a9b, 15 },{ 0x208, 10 }, + { 0x1cfe, 13 },{ 0x399, 10 }, { 0x1cb4, 13 },{ 0x39e, 10 }, + { 0x39f3, 14 },{ 0x5ab, 11 }, { 0x73e3, 15 },{ 0x737, 11 }, + { 0x5a9f, 15 },{ 0x82d, 12 }, { 0xe69, 12 }, { 0xe68, 12 }, + { 0x433, 11 }, { 0xb7b, 12 }, { 0x2df8, 14 },{ 0x2e56, 14 }, + { 0x2e57, 14 },{ 0x39f7, 14 },{ 0x51a5, 15 },{ 0x3, 3 }, + { 0x2a, 6 }, { 0xe4, 8 }, { 0x28e, 10 }, { 0x735, 11 }, + { 0x1058, 13 },{ 0x1cfa, 13 },{ 0x2df9, 14 },{ 0x4174, 15 }, + { 0x9, 4 }, { 0x54, 8 }, { 0x398, 10 }, { 0x48b, 13 }, + { 0x139d, 15 },{ 0xd, 4 }, { 0xad, 9 }, { 0x826, 12 }, + { 0x2d4c, 14 },{ 0x11, 5 }, { 0x16b, 9 }, { 0xb7f, 12 }, + { 0x51a4, 15 },{ 0x19, 5 }, { 0x21b, 10 }, { 0x16fd, 13 }, + { 0x1d, 5 }, { 0x394, 10 }, { 0x28d3, 14 },{ 0x2b, 6 }, + { 0x5bc, 11 }, { 0x5a9a, 15 },{ 0x2f, 6 }, { 0x247, 12 }, + { 0x10, 7 }, { 0xa35, 12 }, { 0x3e, 6 }, { 0xb7a, 12 }, + { 0x59, 7 }, { 0x105e, 13 },{ 0x26, 8 }, { 0x9cf, 14 }, + { 0x55, 8 }, { 0x1cb5, 13 },{ 0x57, 8 }, { 0xe5b, 12 }, + { 0xa0, 8 }, { 0x1468, 13 },{ 0x170, 9 }, { 0x90, 10 }, + { 0x1ce, 9 }, { 0x21a, 10 }, { 0x218, 10 }, { 0x168, 9 }, + { 0x21e, 10 }, { 0x244, 12 }, { 0x736, 11 }, { 0x138, 11 }, + { 0x519, 11 }, { 0xe5e, 12 }, { 0x72c, 11 }, { 0xb55, 12 }, + { 0x9dc, 14 }, { 0x20bb, 14 },{ 0x48c, 13 }, { 0x1723, 13 }, + { 0x2e44, 14 },{ 0x16a5, 13 },{ 0x518, 11 }, { 0x39fe, 14 }, + { 0x169, 9 }, +}; + +static const signed char table4_level[168] = +{ + 1, 2, 3, 4, 5, 6, 7, 8, + 9, 10, 11, 12, 13, 14, 15, 16, + 17, 18, 19, 20, 21, 22, 23, 1, + 2, 3, 4, 5, 6, 7, 8, 9, + 10, 11, 1, 2, 3, 4, 5, 6, + 7, 8, 1, 2, 3, 4, 5, 6, + 7, 1, 2, 3, 4, 5, 1, 2, + 3, 4, 5, 1, 2, 3, 4, 1, + 2, 3, 4, 1, 2, 3, 1, 2, + 3, 1, 2, 3, 1, 2, 3, 1, + 2, 1, 2, 1, 2, 1, 2, 1, + 2, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 2, 3, 4, 5, + 6, 7, 8, 9, 1, 2, 3, 4, + 5, 1, 2, 3, 4, 1, 2, 3, + 4, 1, 2, 3, 1, 2, 3, 1, + 2, 3, 1, 2, 1, 2, 1, 2, + 1, 2, 1, 2, 1, 2, 1, 2, + 1, 2, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, +}; + +static const signed char table4_run[168] = +{ + 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, 1, 1, + 1, 1, 2, 2, 2, 2, 2, 2, + 2, 2, 3, 3, 3, 3, 3, 3, + 3, 4, 4, 4, 4, 4, 5, 5, + 5, 5, 5, 6, 6, 6, 6, 7, + 7, 7, 7, 8, 8, 8, 9, 9, + 9, 10, 10, 10, 11, 11, 11, 12, + 12, 13, 13, 14, 14, 15, 15, 16, + 16, 17, 18, 19, 20, 21, 22, 23, + 24, 25, 26, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 1, 1, 1, 1, + 1, 2, 2, 2, 2, 3, 3, 3, + 3, 4, 4, 4, 5, 5, 5, 6, + 6, 6, 7, 7, 8, 8, 9, 9, + 10, 10, 11, 11, 12, 12, 13, 13, + 14, 14, 15, 16, 17, 18, 19, 20, + 21, 22, 23, 24, 25, 26, 27, 28, + 29, 30, 31, 32, 33, 34, 35, 36, +}; + +static const WORD inter_vlc[103][2] = +{ + { 0x2, 2 }, { 0xf, 4 }, { 0x15, 6 }, { 0x17, 7 }, + { 0x1f, 8 }, { 0x25, 9 }, { 0x24, 9 }, { 0x21, 10 }, + { 0x20, 10 }, { 0x7, 11 }, { 0x6, 11 }, { 0x20, 11 }, + { 0x6, 3 }, { 0x14, 6 }, { 0x1e, 8 }, { 0xf, 10 }, + { 0x21, 11 }, { 0x50, 12 }, { 0xe, 4 }, { 0x1d, 8 }, + { 0xe, 10 }, { 0x51, 12 }, { 0xd, 5 }, { 0x23, 9 }, + { 0xd, 10 }, { 0xc, 5 }, { 0x22, 9 }, { 0x52, 12 }, + { 0xb, 5 }, { 0xc, 10 }, { 0x53, 12 }, { 0x13, 6 }, + { 0xb, 10 }, { 0x54, 12 }, { 0x12, 6 }, { 0xa, 10 }, + { 0x11, 6 }, { 0x9, 10 }, { 0x10, 6 }, { 0x8, 10 }, + { 0x16, 7 }, { 0x55, 12 }, { 0x15, 7 }, { 0x14, 7 }, + { 0x1c, 8 }, { 0x1b, 8 }, { 0x21, 9 }, { 0x20, 9 }, + { 0x1f, 9 }, { 0x1e, 9 }, { 0x1d, 9 }, { 0x1c, 9 }, + { 0x1b, 9 }, { 0x1a, 9 }, { 0x22, 11 }, { 0x23, 11 }, + { 0x56, 12 }, { 0x57, 12 }, { 0x7, 4 }, { 0x19, 9 }, + { 0x5, 11 }, { 0xf, 6 }, { 0x4, 11 }, { 0xe, 6 }, + { 0xd, 6 }, { 0xc, 6 }, { 0x13, 7 }, { 0x12, 7 }, + { 0x11, 7 }, { 0x10, 7 }, { 0x1a, 8 }, { 0x19, 8 }, + { 0x18, 8 }, { 0x17, 8 }, { 0x16, 8 }, { 0x15, 8 }, + { 0x14, 8 }, { 0x13, 8 }, { 0x18, 9 }, { 0x17, 9 }, + { 0x16, 9 }, { 0x15, 9 }, { 0x14, 9 }, { 0x13, 9 }, + { 0x12, 9 }, { 0x11, 9 }, { 0x7, 10 }, { 0x6, 10 }, + { 0x5, 10 }, { 0x4, 10 }, { 0x24, 11 }, { 0x25, 11 }, + { 0x26, 11 }, { 0x27, 11 }, { 0x58, 12 }, { 0x59, 12 }, + { 0x5a, 12 }, { 0x5b, 12 }, { 0x5c, 12 }, { 0x5d, 12 }, + { 0x5e, 12 }, { 0x5f, 12 }, { 0x3, 7 }, +}; + +static const signed char inter_level[102] = +{ + 1, 2, 3, 4, 5, 6, 7, 8, + 9, 10, 11, 12, 1, 2, 3, 4, + 5, 6, 1, 2, 3, 4, 1, 2, + 3, 1, 2, 3, 1, 2, 3, 1, + 2, 3, 1, 2, 1, 2, 1, 2, + 1, 2, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 2, 3, 1, 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, 1, 1, 1, + 1, 1, 1, 1, 1, 1, +}; + +static const signed char inter_run[102] = +{ + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 1, 1, 1, 1, + 1, 1, 2, 2, 2, 2, 3, 3, + 3, 4, 4, 4, 5, 5, 5, 6, + 6, 6, 7, 7, 8, 8, 9, 9, + 10, 10, 11, 12, 13, 14, 15, 16, + 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 0, 0, 0, 1, 1, 2, + 3, 4, 5, 6, 7, 8, 9, 10, + 11, 12, 13, 14, 15, 16, 17, 18, + 19, 20, 21, 22, 23, 24, 25, 26, + 27, 28, 29, 30, 31, 32, 33, 34, + 35, 36, 37, 38, 39, 40, +}; + +static const WORD intra_vlc[103][2] = +{ + { 0x2, 2 }, { 0x6, 3 }, { 0xf, 4 }, { 0xd, 5 }, + { 0xc, 5 }, { 0x15, 6 }, { 0x13, 6 }, { 0x12, 6 }, + { 0x17, 7 }, { 0x1f, 8 }, { 0x1e, 8 }, { 0x1d, 8 }, + { 0x25, 9 }, { 0x24, 9 }, { 0x23, 9 }, { 0x21, 9 }, + { 0x21, 10 }, { 0x20, 10 }, { 0xf, 10 }, { 0xe, 10 }, + { 0x7, 11 }, { 0x6, 11 }, { 0x20, 11 }, { 0x21, 11 }, + { 0x50, 12 }, { 0x51, 12 }, { 0x52, 12 }, { 0xe, 4 }, + { 0x14, 6 }, { 0x16, 7 }, { 0x1c, 8 }, { 0x20, 9 }, + { 0x1f, 9 }, { 0xd, 10 }, { 0x22, 11 }, { 0x53, 12 }, + { 0x55, 12 }, { 0xb, 5 }, { 0x15, 7 }, { 0x1e, 9 }, + { 0xc, 10 }, { 0x56, 12 }, { 0x11, 6 }, { 0x1b, 8 }, + { 0x1d, 9 }, { 0xb, 10 }, { 0x10, 6 }, { 0x22, 9 }, + { 0xa, 10 }, { 0xd, 6 }, { 0x1c, 9 }, { 0x8, 10 }, + { 0x12, 7 }, { 0x1b, 9 }, { 0x54, 12 }, { 0x14, 7 }, + { 0x1a, 9 }, { 0x57, 12 }, { 0x19, 8 }, { 0x9, 10 }, + { 0x18, 8 }, { 0x23, 11 }, { 0x17, 8 }, { 0x19, 9 }, + { 0x18, 9 }, { 0x7, 10 }, { 0x58, 12 }, { 0x7, 4 }, + { 0xc, 6 }, { 0x16, 8 }, { 0x17, 9 }, { 0x6, 10 }, + { 0x5, 11 }, { 0x4, 11 }, { 0x59, 12 }, { 0xf, 6 }, + { 0x16, 9 }, { 0x5, 10 }, { 0xe, 6 }, { 0x4, 10 }, + { 0x11, 7 }, { 0x24, 11 }, { 0x10, 7 }, { 0x25, 11 }, + { 0x13, 7 }, { 0x5a, 12 }, { 0x15, 8 }, { 0x5b, 12 }, + { 0x14, 8 }, { 0x13, 8 }, { 0x1a, 8 }, { 0x15, 9 }, + { 0x14, 9 }, { 0x13, 9 }, { 0x12, 9 }, { 0x11, 9 }, + { 0x26, 11 }, { 0x27, 11 }, { 0x5c, 12 }, { 0x5d, 12 }, + { 0x5e, 12 }, { 0x5f, 12 }, { 0x3, 7 }, +}; + +static const signed char intra_level[102] = +{ + 1, 2, 3, 4, 5, 6, 7, 8, + 9, 10, 11, 12, 13, 14, 15, 16, + 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27, 1, 2, 3, 4, 5, + 6, 7, 8, 9, 10, 1, 2, 3, + 4, 5, 1, 2, 3, 4, 1, 2, + 3, 1, 2, 3, 1, 2, 3, 1, + 2, 3, 1, 2, 1, 2, 1, 1, + 1, 1, 1, 1, 2, 3, 4, 5, + 6, 7, 8, 1, 2, 3, 1, 2, + 1, 2, 1, 2, 1, 2, 1, 2, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, +}; + +static const signed char intra_run[102] = +{ + 0, 0, 0, 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, 1, 1, 1, 2, 2, 2, + 2, 2, 3, 3, 3, 3, 4, 4, + 4, 5, 5, 5, 6, 6, 6, 7, + 7, 7, 8, 8, 9, 9, 10, 11, + 12, 13, 14, 0, 0, 0, 0, 0, + 0, 0, 0, 1, 1, 1, 2, 2, + 3, 3, 4, 4, 5, 5, 6, 6, + 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, +}; + +static DIVX_RL_TABLE rl_table[6] = +{ + /* intra luminance tables */ + { + 132, + 85, + table0_vlc, + table0_run, + table0_level, + }, + { + 185, + 119, + table2_vlc, + table2_run, + table2_level, + }, + { + 102, + 67, + intra_vlc, + intra_run, + intra_level, + }, + /* intra chrominance / non intra tables */ + { + 148, + 81, + table1_vlc, + table1_run, + table1_level, + }, + { + 168, + 99, + table4_vlc, + table4_run, + table4_level, + }, + { + 102, + 58, + inter_vlc, + inter_run, + inter_level, + }, +}; + +////////////////////////////////////////////////////// + +static const WORD table_mb_intra[64][2] = +{ + { 0x1, 1 },{ 0x17, 6 },{ 0x9, 5 },{ 0x5, 5 }, + { 0x6, 5 },{ 0x47, 9 },{ 0x20, 7 },{ 0x10, 7 }, + { 0x2, 5 },{ 0x7c, 9 },{ 0x3a, 7 },{ 0x1d, 7 }, + { 0x2, 6 },{ 0xec, 9 },{ 0x77, 8 },{ 0x0, 8 }, + { 0x3, 5 },{ 0xb7, 9 },{ 0x2c, 7 },{ 0x13, 7 }, + { 0x1, 6 },{ 0x168, 10 },{ 0x46, 8 },{ 0x3f, 8 }, + { 0x1e, 6 },{ 0x712, 13 },{ 0xb5, 9 },{ 0x42, 8 }, + { 0x22, 7 },{ 0x1c5, 11 },{ 0x11e, 10 },{ 0x87, 9 }, + { 0x6, 4 },{ 0x3, 9 },{ 0x1e, 7 },{ 0x1c, 6 }, + { 0x12, 7 },{ 0x388, 12 },{ 0x44, 9 },{ 0x70, 9 }, + { 0x1f, 6 },{ 0x23e, 11 },{ 0x39, 8 },{ 0x8e, 9 }, + { 0x1, 7 },{ 0x1c6, 11 },{ 0xb6, 9 },{ 0x45, 9 }, + { 0x14, 6 },{ 0x23f, 11 },{ 0x7d, 9 },{ 0x18, 9 }, + { 0x7, 7 },{ 0x1c7, 11 },{ 0x86, 9 },{ 0x19, 9 }, + { 0x15, 6 },{ 0x1db, 10 },{ 0x2, 9 },{ 0x46, 9 }, + { 0xd, 8 },{ 0x713, 13 },{ 0x1da, 10 },{ 0x169, 10 }, +}; + +static const DWORD table_mb_non_intra[128][2] = +{ + { 0x40, 7 },{ 0x13c9, 13 },{ 0x9fd, 12 },{ 0x1fc, 15 }, + { 0x9fc, 12 },{ 0xa83, 18 },{ 0x12d34, 17 },{ 0x83bc, 16 }, + { 0x83a, 12 },{ 0x7f8, 17 },{ 0x3fd, 16 },{ 0x3ff, 16 }, + { 0x79, 13 },{ 0xa82, 18 },{ 0x969d, 16 },{ 0x2a4, 16 }, + { 0x978, 12 },{ 0x543, 17 },{ 0x41df, 15 },{ 0x7f9, 17 }, + { 0x12f3, 13 },{ 0x25a6b, 18 },{ 0x25ef9, 18 },{ 0x3fa, 16 }, + { 0x20ee, 14 },{ 0x969ab, 20 },{ 0x969c, 16 },{ 0x25ef8, 18 }, + { 0x12d2, 13 },{ 0xa85, 18 },{ 0x969e, 16 },{ 0x4bc8, 15 }, + { 0x3d, 12 },{ 0x12f7f, 17 },{ 0x2a2, 16 },{ 0x969f, 16 }, + { 0x25ee, 14 },{ 0x12d355, 21 },{ 0x12f7d, 17 },{ 0x12f7e, 17 }, + { 0x9e5, 12 },{ 0xa81, 18 },{ 0x4b4d4, 19 },{ 0x83bd, 16 }, + { 0x78, 13 },{ 0x969b, 16 },{ 0x3fe, 16 },{ 0x2a5, 16 }, + { 0x7e, 13 },{ 0xa80, 18 },{ 0x2a3, 16 },{ 0x3fb, 16 }, + { 0x1076, 13 },{ 0xa84, 18 },{ 0x153, 15 },{ 0x4bc9, 15 }, + { 0x55, 13 },{ 0x12d354, 21 },{ 0x4bde, 15 },{ 0x25e5, 14 }, + { 0x25b, 10 },{ 0x4b4c, 15 },{ 0x96b, 12 },{ 0x96a, 12 }, + { 0x1, 2 },{ 0x0, 7 },{ 0x26, 6 },{ 0x12b, 9 }, + { 0x7, 3 },{ 0x20f, 10 },{ 0x4, 9 },{ 0x28, 12 }, + { 0x6, 3 },{ 0x20a, 10 },{ 0x128, 9 },{ 0x2b, 12 }, + { 0x11, 5 },{ 0x1b, 11 },{ 0x13a, 9 },{ 0x4ff, 11 }, + { 0x3, 4 },{ 0x277, 10 },{ 0x106, 9 },{ 0x839, 12 }, + { 0xb, 4 },{ 0x27b, 10 },{ 0x12c, 9 },{ 0x4bf, 11 }, + { 0x9, 6 },{ 0x35, 12 },{ 0x27e, 10 },{ 0x13c8, 13 }, + { 0x1, 6 },{ 0x4aa, 11 },{ 0x208, 10 },{ 0x29, 12 }, + { 0x1, 4 },{ 0x254, 10 },{ 0x12e, 9 },{ 0x838, 12 }, + { 0x24, 6 },{ 0x4f3, 11 },{ 0x276, 10 },{ 0x12f6, 13 }, + { 0x1, 5 },{ 0x27a, 10 },{ 0x13e, 9 },{ 0x3e, 12 }, + { 0x8, 6 },{ 0x413, 11 },{ 0xc, 10 },{ 0x4be, 11 }, + { 0x14, 5 },{ 0x412, 11 },{ 0x253, 10 },{ 0x97a, 12 }, + { 0x21, 6 },{ 0x4ab, 11 },{ 0x20b, 10 },{ 0x34, 12 }, + { 0x15, 5 },{ 0x278, 10 },{ 0x252, 10 },{ 0x968, 12 }, + { 0x5, 5 },{ 0xb, 10 },{ 0x9c, 8 },{ 0xe, 10 }, +}; + +static const WORD table0_mv_code[1100] = +{ + 0x0001, 0x0003, 0x0005, 0x0007, 0x0003, 0x0008, 0x000c, 0x0001, + 0x0002, 0x001b, 0x0006, 0x000b, 0x0015, 0x0002, 0x000e, 0x000f, + 0x0014, 0x0020, 0x0022, 0x0025, 0x0027, 0x0029, 0x002d, 0x004b, + 0x004d, 0x0003, 0x0022, 0x0023, 0x0025, 0x0027, 0x0042, 0x0048, + 0x0049, 0x0050, 0x005c, 0x0091, 0x009f, 0x000e, 0x0043, 0x004c, + 0x0054, 0x0056, 0x008c, 0x0098, 0x009a, 0x009b, 0x00b1, 0x00b2, + 0x0120, 0x0121, 0x0126, 0x0133, 0x0139, 0x01a1, 0x01a4, 0x01a5, + 0x01a6, 0x01a7, 0x01ae, 0x01af, 0x000b, 0x0019, 0x0085, 0x0090, + 0x009b, 0x00aa, 0x00af, 0x010c, 0x010e, 0x011c, 0x011e, 0x0133, + 0x0144, 0x0160, 0x0174, 0x0175, 0x0177, 0x0178, 0x0249, 0x024b, + 0x0252, 0x0261, 0x0265, 0x0270, 0x0352, 0x0353, 0x0355, 0x0359, + 0x0010, 0x0011, 0x0013, 0x0034, 0x0035, 0x0036, 0x0037, 0x003d, + 0x003e, 0x0109, 0x0126, 0x0156, 0x021a, 0x021e, 0x023a, 0x023e, + 0x028e, 0x028f, 0x02cf, 0x0491, 0x0494, 0x049f, 0x04a0, 0x04a3, + 0x04a6, 0x04a7, 0x04ad, 0x04ae, 0x04c0, 0x04c4, 0x04c6, 0x04c8, + 0x04c9, 0x04f5, 0x04f6, 0x04f7, 0x0680, 0x0682, 0x0683, 0x0688, + 0x0689, 0x068d, 0x068e, 0x068f, 0x06a2, 0x06a3, 0x06a9, 0x06b0, + 0x06b1, 0x06b4, 0x06b5, 0x0024, 0x0060, 0x0063, 0x0078, 0x0079, + 0x0211, 0x0244, 0x0245, 0x0247, 0x0248, 0x0249, 0x024a, 0x024b, + 0x026b, 0x02af, 0x02b8, 0x02bb, 0x0436, 0x0476, 0x0477, 0x047e, + 0x04c8, 0x04c9, 0x04ca, 0x0514, 0x0586, 0x0587, 0x0598, 0x059d, + 0x05d9, 0x05da, 0x0920, 0x0921, 0x093b, 0x093c, 0x093d, 0x0942, + 0x0943, 0x0944, 0x0945, 0x0959, 0x095e, 0x095f, 0x0982, 0x0983, + 0x098e, 0x098f, 0x09c4, 0x09e7, 0x09e8, 0x09e9, 0x0d02, 0x0d17, + 0x0d18, 0x0d19, 0x0d41, 0x0d42, 0x0d43, 0x0d50, 0x0d5f, 0x0d6d, + 0x0d6e, 0x0d6f, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, + 0x00c2, 0x00c3, 0x00c4, 0x00c5, 0x041e, 0x041f, 0x0420, 0x0421, + 0x048c, 0x048d, 0x04d3, 0x04d4, 0x04d5, 0x055c, 0x055d, 0x0572, + 0x0573, 0x0574, 0x0575, 0x08de, 0x08df, 0x08fe, 0x08ff, 0x0996, + 0x0a36, 0x0a37, 0x0b08, 0x0b09, 0x0b0a, 0x0b0b, 0x0b32, 0x0b33, + 0x0b34, 0x0b35, 0x0b36, 0x0b37, 0x0b38, 0x0b39, 0x0bb0, 0x0bf7, + 0x0bf8, 0x0bf9, 0x0bfa, 0x0bfb, 0x0bfc, 0x0bfd, 0x0bfe, 0x0bff, + 0x1254, 0x1255, 0x1256, 0x1257, 0x1270, 0x1271, 0x1272, 0x1273, + 0x1274, 0x1275, 0x12ab, 0x12ac, 0x12ad, 0x12ae, 0x12af, 0x12b0, + 0x12b1, 0x1315, 0x1316, 0x1317, 0x13bf, 0x13c0, 0x13c1, 0x13c2, + 0x13c3, 0x13c4, 0x13c5, 0x13c6, 0x13c7, 0x13c8, 0x13c9, 0x13ca, + 0x13cb, 0x13cc, 0x13cd, 0x1a06, 0x1a07, 0x1a28, 0x1a29, 0x1a2a, + 0x1a2b, 0x1a2c, 0x1a2d, 0x1a80, 0x1abb, 0x1abc, 0x1abd, 0x1ad8, + 0x1ad9, 0x0094, 0x0095, 0x0096, 0x0097, 0x00a0, 0x00a1, 0x00a2, + 0x00a3, 0x0831, 0x0832, 0x0833, 0x0834, 0x0835, 0x0836, 0x0837, + 0x0838, 0x0839, 0x083a, 0x083b, 0x0939, 0x093a, 0x093b, 0x093c, + 0x093d, 0x093e, 0x093f, 0x09a0, 0x09a1, 0x09a2, 0x09a3, 0x09a4, + 0x09a5, 0x11ac, 0x11ad, 0x11ae, 0x11af, 0x11b0, 0x11b1, 0x11b2, + 0x11b3, 0x11b4, 0x11b5, 0x11b6, 0x11b7, 0x11b8, 0x11b9, 0x11ba, + 0x11bb, 0x132f, 0x1454, 0x1455, 0x1456, 0x1457, 0x1458, 0x1459, + 0x145a, 0x145b, 0x145c, 0x145d, 0x145e, 0x145f, 0x1460, 0x1461, + 0x1462, 0x1463, 0x1464, 0x1465, 0x1466, 0x1467, 0x1468, 0x1469, + 0x146a, 0x146b, 0x17de, 0x17df, 0x17e0, 0x17e1, 0x17e2, 0x17e3, + 0x17e4, 0x17e5, 0x17e6, 0x17e7, 0x17e8, 0x17e9, 0x17ea, 0x17eb, + 0x17ec, 0x17ed, 0x2540, 0x2541, 0x2542, 0x2543, 0x2544, 0x2545, + 0x2546, 0x2547, 0x2548, 0x2549, 0x254a, 0x254b, 0x254c, 0x254d, + 0x254e, 0x254f, 0x2550, 0x2551, 0x2552, 0x2553, 0x2554, 0x2555, + 0x2628, 0x2766, 0x2767, 0x2768, 0x2769, 0x276a, 0x276b, 0x276c, + 0x276d, 0x276e, 0x276f, 0x2770, 0x2771, 0x2772, 0x2773, 0x2774, + 0x2775, 0x2776, 0x2777, 0x2778, 0x2779, 0x277a, 0x277b, 0x277c, + 0x277d, 0x3503, 0x3544, 0x3545, 0x3546, 0x3547, 0x3560, 0x3561, + 0x3562, 0x3563, 0x3564, 0x3565, 0x3566, 0x3567, 0x3568, 0x3569, + 0x356a, 0x356b, 0x356c, 0x356d, 0x356e, 0x356f, 0x3570, 0x3571, + 0x3572, 0x3573, 0x3574, 0x3575, 0x03f0, 0x103d, 0x103e, 0x103f, + 0x1040, 0x1041, 0x1042, 0x1043, 0x1044, 0x1045, 0x1046, 0x1047, + 0x1048, 0x1049, 0x104a, 0x104b, 0x104c, 0x104d, 0x104e, 0x104f, + 0x1050, 0x1051, 0x1052, 0x1053, 0x1054, 0x1055, 0x1056, 0x1057, + 0x1058, 0x1059, 0x105a, 0x105b, 0x105c, 0x105d, 0x105e, 0x105f, + 0x1060, 0x1061, 0x1270, 0x1271, 0x21b8, 0x21b9, 0x21ba, 0x21bb, + 0x21bc, 0x21bd, 0x21be, 0x21bf, 0x21f0, 0x21f1, 0x21f2, 0x21f3, + 0x21f4, 0x21f5, 0x21f6, 0x21f7, 0x21f8, 0x21f9, 0x21fa, 0x21fb, + 0x21fc, 0x21fd, 0x21fe, 0x21ff, 0x2340, 0x2341, 0x2342, 0x2343, + 0x2344, 0x2345, 0x2346, 0x2347, 0x2348, 0x2349, 0x234a, 0x234b, + 0x234c, 0x234d, 0x234e, 0x234f, 0x2350, 0x2351, 0x2352, 0x2353, + 0x2354, 0x2355, 0x2356, 0x2357, 0x265c, 0x2f88, 0x2f89, 0x2f8a, + 0x2f8b, 0x2f8c, 0x2f8d, 0x2f8e, 0x2f8f, 0x2f90, 0x2f91, 0x2f92, + 0x2f93, 0x2f94, 0x2f95, 0x2f96, 0x2f97, 0x2f98, 0x2f99, 0x2f9a, + 0x2f9b, 0x2f9c, 0x2f9d, 0x2f9e, 0x2f9f, 0x2fa0, 0x2fa1, 0x2fa2, + 0x2fa3, 0x2fa4, 0x2fa5, 0x2fa6, 0x2fa7, 0x2fa8, 0x2fa9, 0x2faa, + 0x2fab, 0x2fac, 0x2fad, 0x2fae, 0x2faf, 0x2fb0, 0x2fb1, 0x2fb2, + 0x2fb3, 0x2fb4, 0x2fb5, 0x2fb6, 0x2fb7, 0x2fb8, 0x2fb9, 0x2fba, + 0x2fbb, 0x4c52, 0x4c53, 0x4e28, 0x4e29, 0x4e2a, 0x4e2b, 0x4e2c, + 0x4e2d, 0x4e2e, 0x4e2f, 0x4e30, 0x4e31, 0x4e32, 0x4e33, 0x4e34, + 0x4e35, 0x4e36, 0x4e37, 0x4e38, 0x4e39, 0x4e3a, 0x4e3b, 0x4e3c, + 0x4e3d, 0x4e3e, 0x4e3f, 0x4e80, 0x4e81, 0x4e82, 0x4e83, 0x4e84, + 0x4e85, 0x4e86, 0x4e87, 0x4e88, 0x4e89, 0x4e8a, 0x4e8b, 0x4e8c, + 0x4e8d, 0x4e8e, 0x4e8f, 0x4e90, 0x4e91, 0x4e92, 0x4e93, 0x4e94, + 0x4e95, 0x4e96, 0x4e97, 0x4e98, 0x4e99, 0x4e9a, 0x4e9b, 0x4e9c, + 0x4e9d, 0x4e9e, 0x4e9f, 0x4ea0, 0x4ea1, 0x4ea2, 0x4ea3, 0x4ea4, + 0x4ea5, 0x4ea6, 0x4ea7, 0x4ea8, 0x4ea9, 0x4eaa, 0x4eab, 0x4eac, + 0x4ead, 0x4eae, 0x4eaf, 0x4eb0, 0x4eb1, 0x4eb2, 0x4eb3, 0x4eb4, + 0x4eb5, 0x4eb6, 0x4eb7, 0x4eb8, 0x4eb9, 0x4eba, 0x4ebb, 0x4ebc, + 0x4ebd, 0x4ebe, 0x4ebf, 0x4ec0, 0x4ec1, 0x4ec2, 0x4ec3, 0x4ec4, + 0x4ec5, 0x4ec6, 0x4ec7, 0x4ec8, 0x4ec9, 0x4eca, 0x4ecb, 0x6a04, + 0x6a05, 0x07e2, 0x07e3, 0x07e4, 0x07e5, 0x07e6, 0x07e7, 0x07e8, + 0x07e9, 0x07ea, 0x07eb, 0x07ec, 0x07ed, 0x07ee, 0x07ef, 0x07f0, + 0x07f1, 0x07f2, 0x07f3, 0x07f4, 0x07f5, 0x07f6, 0x07f7, 0x07f8, + 0x07f9, 0x07fa, 0x07fb, 0x07fc, 0x07fd, 0x07fe, 0x07ff, 0x2000, + 0x2001, 0x2002, 0x2003, 0x2004, 0x2005, 0x2006, 0x2007, 0x2008, + 0x2009, 0x200a, 0x200b, 0x200c, 0x200d, 0x200e, 0x200f, 0x2010, + 0x2011, 0x2012, 0x2013, 0x2014, 0x2015, 0x2016, 0x2017, 0x2018, + 0x2019, 0x201a, 0x201b, 0x201c, 0x201d, 0x201e, 0x201f, 0x2020, + 0x2021, 0x2022, 0x2023, 0x2024, 0x2025, 0x2026, 0x2027, 0x2028, + 0x2029, 0x202a, 0x202b, 0x202c, 0x202d, 0x202e, 0x202f, 0x2030, + 0x2031, 0x2032, 0x2033, 0x2034, 0x2035, 0x2036, 0x2037, 0x2038, + 0x2039, 0x203a, 0x203b, 0x203c, 0x203d, 0x203e, 0x203f, 0x2040, + 0x2041, 0x2042, 0x2043, 0x2044, 0x2045, 0x2046, 0x2047, 0x2048, + 0x2049, 0x204a, 0x204b, 0x204c, 0x204d, 0x204e, 0x204f, 0x2050, + 0x2051, 0x2052, 0x2053, 0x2054, 0x2055, 0x2056, 0x2057, 0x2058, + 0x2059, 0x205a, 0x205b, 0x205c, 0x205d, 0x205e, 0x205f, 0x2060, + 0x2061, 0x2062, 0x2063, 0x2064, 0x2065, 0x2066, 0x2067, 0x2068, + 0x2069, 0x206a, 0x206b, 0x206c, 0x206d, 0x206e, 0x206f, 0x2070, + 0x2071, 0x2072, 0x2073, 0x2074, 0x2075, 0x2076, 0x2077, 0x2078, + 0x2079, 0x4cba, 0x4cbb, 0x5d88, 0x5d89, 0x5d8a, 0x5d8b, 0x5d8c, + 0x5d8d, 0x5d8e, 0x5d8f, 0x5db0, 0x5db1, 0x5db2, 0x5db3, 0x5db4, + 0x5db5, 0x5db6, 0x5db7, 0x5db8, 0x5db9, 0x5dba, 0x5dbb, 0x5dbc, + 0x5dbd, 0x5dbe, 0x5dbf, 0x5e40, 0x5e41, 0x5e42, 0x5e43, 0x5e44, + 0x5e45, 0x5e46, 0x5e47, 0x5e48, 0x5e49, 0x5e4a, 0x5e4b, 0x5e4c, + 0x5e4d, 0x5e4e, 0x5e4f, 0x5e50, 0x5e51, 0x5e52, 0x5e53, 0x5e54, + 0x5e55, 0x5e56, 0x5e57, 0x5e58, 0x5e59, 0x5e5a, 0x5e5b, 0x5e5c, + 0x5e5d, 0x5e5e, 0x5e5f, 0x5e60, 0x5e61, 0x5e62, 0x5e63, 0x5e64, + 0x5e65, 0x5e66, 0x5e67, 0x5e68, 0x5e69, 0x5e6a, 0x5e6b, 0x5e6c, + 0x5e6d, 0x5e6e, 0x5e6f, 0x5e70, 0x5e71, 0x5e72, 0x5e73, 0x5e74, + 0x5e75, 0x5e76, 0x5e77, 0x5e78, 0x5e79, 0x5e7a, 0x5e7b, 0x5e7c, + 0x5e7d, 0x5e7e, 0x5e7f, 0x5e80, 0x5e81, 0x5e82, 0x5e83, 0x5e84, + 0x5e85, 0x5e86, 0x5e87, 0x5e88, 0x5e89, 0x5e8a, 0x5e8b, 0x5e8c, + 0x5e8d, 0x5e8e, 0x5e8f, 0x5e90, 0x5e91, 0x5e92, 0x5e93, 0x5e94, + 0x5e95, 0x5e96, 0x5e97, 0x5e98, 0x5e99, 0x5e9a, 0x5e9b, 0x5e9c, + 0x5e9d, 0x5e9e, 0x5e9f, 0x5ea0, 0x5ea1, 0x5ea2, 0x5ea3, 0x5ea4, + 0x5ea5, 0x5ea6, 0x5ea7, 0x5ea8, 0x5ea9, 0x5eaa, 0x5eab, 0x5eac, + 0x5ead, 0x5eae, 0x5eaf, 0x5eb0, 0x5eb1, 0x5eb2, 0x5eb3, 0x5eb4, + 0x5eb5, 0x5eb6, 0x5eb7, 0x5eb8, 0x5eb9, 0x5eba, 0x5ebb, 0x5ebc, + 0x5ebd, 0x5ebe, 0x5ebf, 0x5ec0, 0x5ec1, 0x5ec2, 0x5ec3, 0x5ec4, + 0x5ec5, 0x5ec6, 0x5ec7, 0x5ec8, 0x5ec9, 0x5eca, 0x5ecb, 0x5ecc, + 0x5ecd, 0x5ece, 0x5ecf, 0x5ed0, 0x5ed1, 0x5ed2, 0x5ed3, 0x5ed4, + 0x5ed5, 0x5ed6, 0x5ed7, 0x5ed8, 0x5ed9, 0x5eda, 0x5edb, 0x5edc, + 0x5edd, 0x5ede, 0x5edf, 0x5ee0, 0x5ee1, 0x5ee2, 0x5ee3, 0x5ee4, + 0x5ee5, 0x5ee6, 0x5ee7, 0x5ee8, 0x5ee9, 0x5eea, 0x5eeb, 0x5eec, + 0x5eed, 0x5eee, 0x5eef, 0x5ef0, 0x5ef1, 0x5ef2, 0x5ef3, 0x5ef4, + 0x5ef5, 0x5ef6, 0x5ef7, 0x5ef8, 0x5ef9, 0x5efa, 0x5efb, 0x5efc, + 0x5efd, 0x5efe, 0x5eff, 0x5f00, 0x5f01, 0x5f02, 0x5f03, 0x5f04, + 0x5f05, 0x5f06, 0x5f07, 0x5f08, 0x5f09, 0x5f0a, 0x5f0b, 0x5f0c, + 0x5f0d, 0x5f0e, 0x5f0f, 0x0000, +}; + +static const BYTE table0_mv_bits[1100] = +{ + 1, 4, 4, 4, 5, 5, 5, 6, + 6, 6, 7, 7, 7, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, + 8, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 11, 11, 11, 11, + 11, 11, 11, 11, 11, 11, 11, 11, + 11, 11, 11, 11, 11, 11, 11, 11, + 11, 11, 11, 11, 11, 11, 11, 11, + 12, 12, 12, 12, 12, 12, 12, 12, + 12, 12, 12, 12, 12, 12, 12, 12, + 12, 12, 12, 12, 12, 12, 12, 12, + 12, 12, 12, 12, 12, 12, 12, 12, + 12, 12, 12, 12, 12, 12, 12, 12, + 12, 12, 12, 12, 12, 12, 12, 12, + 12, 12, 12, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, + 14, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, + 16, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 17, 17, 17, 17, 17, + 17, 17, 17, 8, +}; + +static const BYTE table0_mvx[1099] = +{ + 32, 32, 31, 32, 33, 31, 33, 31, + 33, 32, 34, 32, 30, 32, 31, 34, + 35, 32, 34, 33, 29, 33, 30, 30, + 31, 31, 35, 29, 33, 35, 33, 34, + 31, 29, 30, 34, 30, 36, 28, 32, + 34, 37, 30, 27, 32, 25, 39, 32, + 34, 32, 35, 35, 35, 31, 35, 29, + 32, 29, 30, 29, 37, 27, 36, 38, + 37, 33, 32, 31, 29, 31, 28, 36, + 33, 30, 34, 33, 33, 28, 27, 25, + 31, 26, 39, 32, 32, 31, 33, 39, + 31, 38, 28, 36, 21, 23, 43, 36, + 34, 41, 30, 25, 28, 31, 30, 34, + 38, 35, 61, 34, 28, 30, 37, 37, + 35, 27, 36, 3, 59, 38, 37, 32, + 31, 29, 26, 33, 37, 33, 27, 27, + 35, 34, 34, 40, 42, 33, 32, 29, + 4, 5, 28, 24, 25, 35, 39, 38, + 32, 23, 27, 32, 30, 35, 26, 34, + 60, 36, 29, 22, 26, 41, 7, 30, + 38, 30, 36, 29, 30, 41, 26, 25, + 32, 34, 24, 39, 1, 25, 39, 32, + 28, 29, 32, 38, 26, 36, 28, 63, + 28, 39, 23, 21, 26, 35, 31, 35, + 57, 31, 29, 29, 28, 30, 27, 35, + 2, 38, 40, 34, 37, 29, 38, 43, + 26, 32, 33, 42, 24, 40, 28, 32, + 32, 32, 36, 32, 43, 25, 21, 31, + 30, 31, 41, 29, 33, 37, 26, 37, + 27, 59, 23, 33, 35, 31, 31, 37, + 38, 39, 32, 23, 32, 27, 37, 36, + 31, 40, 25, 27, 38, 31, 36, 28, + 31, 36, 25, 45, 3, 34, 38, 39, + 40, 38, 30, 32, 19, 24, 25, 26, + 45, 20, 24, 33, 33, 31, 41, 34, + 39, 47, 40, 58, 59, 41, 33, 3, + 17, 61, 42, 30, 26, 29, 36, 61, + 33, 37, 62, 28, 25, 38, 25, 38, + 17, 23, 34, 33, 21, 33, 49, 27, + 32, 23, 27, 22, 24, 22, 39, 43, + 27, 37, 6, 42, 47, 26, 30, 31, + 41, 39, 33, 22, 45, 36, 32, 45, + 19, 22, 30, 5, 5, 17, 29, 22, + 31, 31, 43, 37, 27, 32, 32, 32, + 33, 34, 43, 35, 29, 26, 22, 32, + 19, 32, 25, 31, 41, 49, 28, 34, + 28, 39, 34, 19, 37, 38, 29, 21, + 36, 42, 24, 48, 16, 28, 49, 22, + 34, 31, 38, 39, 44, 11, 35, 30, + 33, 33, 23, 28, 33, 46, 15, 13, + 24, 41, 24, 34, 34, 30, 26, 24, + 14, 60, 21, 29, 39, 23, 35, 37, + 63, 45, 33, 34, 47, 41, 22, 42, + 35, 35, 23, 32, 35, 43, 32, 7, + 31, 41, 20, 31, 16, 13, 63, 25, + 30, 32, 35, 30, 30, 31, 42, 47, + 39, 38, 40, 40, 51, 55, 56, 18, + 21, 39, 39, 33, 17, 41, 23, 24, + 43, 25, 31, 20, 19, 45, 1, 34, + 31, 22, 35, 15, 46, 46, 35, 31, + 28, 29, 29, 23, 41, 27, 14, 53, + 53, 27, 24, 32, 57, 32, 17, 42, + 37, 29, 33, 1, 25, 32, 32, 63, + 26, 40, 44, 36, 31, 39, 20, 20, + 44, 23, 33, 34, 35, 33, 33, 28, + 41, 23, 41, 41, 29, 25, 26, 49, + 29, 24, 37, 49, 50, 51, 51, 26, + 39, 25, 26, 15, 39, 18, 42, 17, + 4, 31, 32, 32, 60, 1, 42, 32, + 0, 12, 19, 35, 21, 41, 17, 26, + 20, 45, 46, 32, 37, 22, 47, 29, + 31, 27, 29, 30, 21, 33, 35, 18, + 25, 33, 50, 51, 42, 2, 15, 51, + 53, 33, 25, 29, 55, 37, 38, 33, + 38, 59, 38, 33, 39, 13, 32, 40, + 61, 61, 32, 9, 44, 3, 31, 29, + 25, 31, 27, 23, 9, 25, 9, 29, + 20, 30, 30, 42, 18, 28, 25, 28, + 28, 21, 29, 43, 29, 43, 26, 44, + 44, 21, 38, 21, 24, 45, 45, 35, + 39, 22, 35, 36, 34, 34, 45, 34, + 29, 31, 46, 25, 46, 16, 17, 31, + 20, 32, 47, 47, 47, 32, 49, 49, + 49, 31, 1, 27, 28, 39, 39, 21, + 36, 23, 51, 2, 40, 51, 32, 53, + 24, 30, 24, 30, 21, 40, 57, 57, + 31, 41, 58, 32, 12, 4, 32, 34, + 59, 31, 32, 13, 9, 35, 26, 35, + 37, 61, 37, 63, 26, 29, 41, 38, + 23, 20, 41, 26, 41, 42, 42, 42, + 26, 26, 26, 26, 1, 26, 37, 37, + 37, 23, 34, 42, 27, 43, 34, 27, + 31, 24, 33, 16, 3, 31, 24, 33, + 24, 4, 44, 44, 11, 44, 31, 13, + 13, 44, 45, 13, 25, 22, 38, 26, + 38, 38, 39, 32, 30, 39, 30, 22, + 32, 26, 30, 47, 47, 47, 19, 47, + 30, 31, 35, 8, 23, 47, 47, 27, + 35, 47, 31, 48, 35, 19, 36, 49, + 49, 33, 31, 39, 27, 39, 49, 49, + 50, 50, 50, 39, 31, 51, 51, 39, + 28, 33, 33, 21, 40, 31, 52, 53, + 40, 53, 9, 33, 31, 53, 54, 54, + 54, 55, 55, 34, 15, 56, 25, 56, + 21, 21, 40, 40, 25, 40, 58, 36, + 5, 41, 41, 12, 60, 41, 41, 37, + 22, 61, 18, 29, 29, 30, 61, 30, + 61, 62, 62, 30, 30, 63, 18, 13, + 30, 23, 19, 20, 20, 41, 13, 2, + 5, 5, 1, 5, 32, 6, 32, 35, + 20, 35, 27, 35, 35, 36, 36, 13, + 36, 41, 41, 41, 3, 30, 42, 27, + 20, 30, 27, 28, 30, 21, 33, 33, + 14, 24, 30, 42, 24, 33, 25, 42, + 43, 14, 43, 43, 14, 43, 7, 36, + 37, 37, 37, 37, 7, 14, 25, 43, + 43, 44, 15, 37, 7, 7, 3, 1, + 8, 15, 15, 8, 44, 44, 44, 45, + 45, 45, 45, 8, 8, 45, 21, 45, + 28, 28, 28, 21, 28, 28, 22, 37, + 46, 46, 37, 8, 29, 37, 29, 22, + 46, 37, 22, 29, 47, 47, 38, 38, + 16, 38, 38, 33, 38, 22, 47, 47, + 29, 25, 16, 0, 48, 1, 34, 48, + 48, 34, 25, 26, 26, 49, 49, 26, + 1, 49, 4, 26, 4, 49, 1, 9, + 49, 49, 49, 10, 49, 17, 38, 17, + 17, 50, 38, 50, 50, 22, 38, 51, + 38, 38, 51, 39, 39, 18, 22, 39, + 51, 22, 52, 52, 52, 39, 53, 53, + 10, 23, 18, 29, 10, 53, 29, 54, + 11, 54, 11, 11, 55, 1, 18, 55, + 55, 55, 55, 55, 55, 29, 34, 18, + 29, 56, 56, 34, 57, 34, 34, 29, + 29, 57, 57, 35, 35, 35, 35, 35, + 39, 35, 59, 59, 18, 59, 39, 30, + 18, 40, 60, 60, 61, 30, 18, 61, + 61, 19, 19, +}; + +static const BYTE table0_mvy[1099] = +{ + 32, 31, 32, 33, 32, 31, 31, 33, + 33, 34, 32, 30, 32, 35, 34, 31, + 32, 29, 33, 30, 32, 34, 33, 31, + 30, 35, 31, 31, 29, 33, 35, 30, + 29, 33, 34, 34, 30, 32, 32, 36, + 29, 32, 35, 32, 28, 32, 32, 27, + 35, 37, 34, 29, 30, 36, 35, 34, + 25, 30, 29, 35, 33, 31, 31, 32, + 31, 28, 39, 28, 29, 37, 31, 33, + 27, 36, 28, 36, 37, 33, 33, 31, + 27, 32, 31, 38, 26, 25, 25, 33, + 39, 31, 34, 30, 32, 32, 32, 34, + 36, 32, 28, 33, 30, 38, 37, 27, + 33, 28, 32, 37, 35, 38, 29, 34, + 27, 29, 29, 32, 32, 34, 35, 3, + 26, 36, 31, 38, 30, 26, 35, 34, + 37, 26, 25, 32, 32, 39, 23, 37, + 32, 32, 29, 32, 29, 36, 29, 30, + 41, 31, 30, 21, 39, 25, 34, 38, + 32, 35, 39, 32, 33, 33, 32, 27, + 29, 25, 28, 27, 26, 31, 30, 35, + 24, 24, 31, 34, 32, 30, 35, 40, + 28, 38, 5, 35, 29, 36, 36, 32, + 38, 30, 33, 31, 35, 26, 23, 38, + 32, 41, 28, 25, 37, 40, 37, 39, + 32, 36, 33, 39, 25, 26, 28, 31, + 28, 42, 23, 31, 33, 31, 39, 1, + 59, 22, 27, 4, 33, 34, 33, 24, + 41, 3, 35, 41, 41, 28, 36, 36, + 28, 33, 35, 21, 23, 21, 22, 37, + 27, 27, 43, 29, 60, 39, 27, 25, + 59, 34, 27, 27, 26, 40, 37, 27, + 61, 26, 39, 33, 31, 22, 37, 25, + 30, 25, 24, 61, 31, 34, 25, 38, + 32, 32, 30, 3, 61, 43, 29, 23, + 28, 32, 28, 32, 31, 34, 5, 33, + 32, 33, 33, 42, 37, 23, 38, 31, + 40, 26, 32, 26, 37, 38, 36, 24, + 29, 30, 20, 22, 29, 24, 32, 41, + 2, 34, 25, 33, 29, 31, 39, 35, + 36, 24, 32, 30, 33, 27, 44, 60, + 30, 36, 19, 34, 31, 24, 16, 35, + 32, 38, 21, 33, 31, 31, 21, 35, + 5, 17, 29, 38, 38, 18, 58, 19, + 43, 41, 30, 41, 43, 39, 29, 7, + 29, 17, 28, 19, 28, 31, 25, 19, + 40, 26, 21, 33, 39, 23, 40, 30, + 39, 34, 35, 32, 32, 24, 33, 30, + 40, 47, 39, 37, 32, 33, 24, 23, + 45, 47, 27, 23, 42, 32, 32, 33, + 36, 37, 37, 17, 18, 22, 40, 38, + 32, 31, 35, 24, 17, 25, 17, 23, + 33, 34, 51, 42, 31, 36, 36, 29, + 21, 22, 37, 44, 43, 25, 47, 33, + 45, 27, 31, 58, 31, 32, 31, 38, + 43, 20, 47, 45, 54, 1, 26, 34, + 38, 14, 22, 24, 33, 34, 32, 32, + 37, 21, 23, 49, 35, 23, 28, 39, + 39, 23, 55, 33, 30, 30, 63, 16, + 42, 28, 13, 33, 33, 35, 19, 46, + 43, 17, 19, 36, 39, 24, 31, 32, + 33, 26, 28, 62, 33, 63, 33, 39, + 19, 49, 17, 31, 43, 13, 15, 29, + 25, 35, 33, 23, 49, 41, 28, 29, + 34, 38, 7, 61, 11, 50, 13, 41, + 19, 47, 25, 26, 15, 42, 41, 29, + 45, 27, 17, 35, 32, 29, 32, 24, + 13, 26, 26, 31, 24, 33, 28, 30, + 31, 11, 45, 46, 33, 33, 35, 57, + 32, 32, 35, 45, 34, 11, 37, 42, + 39, 37, 31, 49, 21, 27, 29, 47, + 53, 40, 51, 16, 26, 1, 40, 30, + 41, 44, 34, 25, 27, 31, 35, 35, + 31, 15, 49, 1, 35, 40, 5, 58, + 21, 29, 22, 59, 45, 31, 9, 26, + 9, 29, 11, 32, 30, 3, 13, 20, + 18, 20, 11, 3, 29, 40, 31, 53, + 30, 17, 20, 37, 31, 42, 47, 47, + 54, 38, 9, 34, 13, 37, 21, 25, + 27, 43, 42, 45, 40, 25, 27, 46, + 22, 25, 53, 20, 2, 14, 39, 15, + 22, 44, 34, 21, 38, 33, 27, 48, + 34, 52, 35, 47, 49, 54, 2, 13, + 23, 52, 29, 45, 22, 49, 54, 21, + 40, 42, 31, 30, 29, 34, 0, 25, + 23, 51, 24, 59, 28, 38, 29, 31, + 2, 13, 31, 8, 31, 33, 12, 45, + 41, 7, 14, 30, 25, 18, 43, 20, + 43, 35, 44, 1, 49, 42, 42, 18, + 41, 38, 41, 44, 53, 11, 20, 25, + 45, 46, 47, 48, 39, 52, 46, 49, + 63, 55, 44, 38, 13, 13, 57, 22, + 51, 16, 12, 28, 35, 57, 25, 20, + 26, 28, 28, 29, 32, 31, 62, 34, + 35, 35, 19, 49, 48, 39, 40, 18, + 43, 46, 11, 6, 48, 19, 49, 41, + 10, 23, 58, 17, 21, 23, 34, 30, + 60, 0, 44, 34, 26, 37, 46, 43, + 49, 59, 4, 34, 59, 37, 22, 25, + 28, 46, 6, 40, 59, 42, 36, 61, + 28, 30, 31, 43, 10, 22, 23, 47, + 20, 52, 55, 36, 25, 16, 1, 11, + 27, 29, 5, 63, 18, 41, 31, 34, + 38, 1, 5, 13, 28, 31, 17, 38, + 39, 41, 36, 37, 22, 39, 33, 43, + 43, 15, 17, 49, 30, 21, 22, 20, + 10, 17, 25, 54, 57, 3, 34, 8, + 36, 25, 31, 14, 15, 19, 29, 25, + 18, 39, 53, 22, 27, 20, 29, 33, + 41, 42, 35, 62, 50, 29, 53, 50, + 35, 55, 42, 61, 63, 4, 7, 42, + 21, 46, 47, 49, 27, 46, 17, 55, + 41, 50, 63, 4, 56, 18, 8, 10, + 18, 51, 63, 36, 55, 18, 5, 55, + 9, 29, 17, 21, 30, 27, 1, 59, + 7, 11, 12, 15, 5, 42, 24, 41, + 43, 7, 27, 22, 25, 31, 30, 37, + 22, 39, 53, 29, 36, 37, 48, 0, + 5, 13, 17, 31, 32, 26, 46, 28, + 44, 45, 46, 53, 49, 51, 3, 41, + 3, 22, 42, 33, 5, 45, 7, 22, + 40, 53, 24, 14, 25, 27, 10, 12, + 34, 16, 17, 53, 20, 26, 39, 45, + 18, 45, 35, 33, 31, 49, 4, 39, + 42, 11, 51, 5, 13, 26, 27, 17, + 52, 30, 0, 22, 12, 34, 62, 36, + 38, 41, 47, 30, 63, 38, 41, 43, + 59, 33, 45, 37, 38, 40, 47, 24, + 48, 49, 30, 1, 10, 22, 49, 15, + 39, 59, 31, 32, 33, 18, 13, 15, + 31, 21, 27, 44, 42, 39, 46, 17, + 26, 32, 30, 31, 0, 30, 34, 9, + 12, 13, 25, 31, 32, 55, 43, 35, + 61, 33, 35, 46, 25, 47, 48, 62, + 63, 38, 61, 1, 2, 5, 7, 9, + 46, 10, 34, 35, 36, 55, 51, 7, + 40, 23, 34, 37, 5, 13, 42, 18, + 25, 27, 28, +}; + +/* motion vector table 1 */ +static const WORD table1_mv_code[1100] = +{ + 0x0000, 0x0007, 0x0009, 0x000f, 0x000a, 0x0011, 0x001a, 0x001c, + 0x0011, 0x0031, 0x0025, 0x002d, 0x002f, 0x006f, 0x0075, 0x0041, + 0x004c, 0x004e, 0x005c, 0x0060, 0x0062, 0x0066, 0x0068, 0x0069, + 0x006b, 0x00a6, 0x00c1, 0x00cb, 0x00cc, 0x00ce, 0x00da, 0x00e8, + 0x00ee, 0x0087, 0x0090, 0x009e, 0x009f, 0x00ba, 0x00ca, 0x00d8, + 0x00db, 0x00df, 0x0104, 0x0109, 0x010c, 0x0143, 0x0145, 0x014a, + 0x0156, 0x015c, 0x01b3, 0x01d3, 0x01da, 0x0103, 0x0109, 0x010b, + 0x0122, 0x0127, 0x0134, 0x0161, 0x0164, 0x0176, 0x0184, 0x018d, + 0x018e, 0x018f, 0x0190, 0x0193, 0x0196, 0x019d, 0x019e, 0x019f, + 0x01a9, 0x01b2, 0x01b4, 0x01ba, 0x01bb, 0x01bc, 0x0201, 0x0202, + 0x0205, 0x0207, 0x020d, 0x0210, 0x0211, 0x0215, 0x021b, 0x021f, + 0x0281, 0x0285, 0x0290, 0x029c, 0x029d, 0x02a2, 0x02a7, 0x02a8, + 0x02aa, 0x02b0, 0x02b1, 0x02b4, 0x02bc, 0x02bf, 0x0320, 0x0326, + 0x0327, 0x0329, 0x032a, 0x0336, 0x0360, 0x0362, 0x0363, 0x0372, + 0x03b2, 0x03bc, 0x03bd, 0x0203, 0x0205, 0x021a, 0x0249, 0x024a, + 0x024c, 0x02c7, 0x02ca, 0x02ce, 0x02ef, 0x030d, 0x0322, 0x0325, + 0x0338, 0x0373, 0x037a, 0x0409, 0x0415, 0x0416, 0x0418, 0x0428, + 0x042d, 0x042f, 0x0434, 0x0508, 0x0509, 0x0510, 0x0511, 0x051c, + 0x051e, 0x0524, 0x0541, 0x0543, 0x0546, 0x0547, 0x054d, 0x0557, + 0x055f, 0x056a, 0x056c, 0x056d, 0x056f, 0x0576, 0x0577, 0x057a, + 0x057b, 0x057c, 0x057d, 0x0600, 0x0601, 0x0603, 0x0614, 0x0616, + 0x0617, 0x061c, 0x061f, 0x0642, 0x0648, 0x0649, 0x064a, 0x064b, + 0x0657, 0x0668, 0x0669, 0x066b, 0x066e, 0x067f, 0x06c2, 0x06c8, + 0x06cb, 0x06de, 0x06df, 0x06e2, 0x06e3, 0x06ef, 0x0748, 0x074b, + 0x076e, 0x076f, 0x077c, 0x0409, 0x0423, 0x0428, 0x0429, 0x042a, + 0x042b, 0x0432, 0x0433, 0x0496, 0x049a, 0x04d5, 0x04db, 0x0581, + 0x0582, 0x058b, 0x058c, 0x058d, 0x0598, 0x0599, 0x059a, 0x059e, + 0x05dd, 0x0619, 0x0632, 0x0633, 0x0648, 0x0672, 0x06a1, 0x06a2, + 0x06a3, 0x06af, 0x06e2, 0x06e3, 0x06e4, 0x0800, 0x0801, 0x0802, + 0x0803, 0x081a, 0x081b, 0x0829, 0x082f, 0x0832, 0x083e, 0x083f, + 0x0852, 0x0853, 0x0858, 0x086b, 0x0877, 0x0878, 0x0879, 0x087a, + 0x087b, 0x0a00, 0x0a01, 0x0a0d, 0x0a0e, 0x0a0f, 0x0a24, 0x0a37, + 0x0a3a, 0x0a3b, 0x0a3e, 0x0a46, 0x0a47, 0x0a4a, 0x0a4b, 0x0a5f, + 0x0a79, 0x0a7a, 0x0a7b, 0x0a80, 0x0a81, 0x0a84, 0x0a85, 0x0a99, + 0x0aa5, 0x0aa6, 0x0ab8, 0x0aba, 0x0abb, 0x0abc, 0x0abd, 0x0ac8, + 0x0ace, 0x0acf, 0x0ad7, 0x0adc, 0x0aeb, 0x0c04, 0x0c25, 0x0c26, + 0x0c27, 0x0c2a, 0x0c2b, 0x0c3a, 0x0c3b, 0x0c3c, 0x0c3d, 0x0ca0, + 0x0cad, 0x0cd4, 0x0cd5, 0x0cfc, 0x0cfd, 0x0d86, 0x0d92, 0x0d93, + 0x0d94, 0x0d95, 0x0db0, 0x0db8, 0x0db9, 0x0dba, 0x0dbb, 0x0dc0, + 0x0dc2, 0x0dc3, 0x0dda, 0x0ddb, 0x0ddc, 0x0ddd, 0x0e92, 0x0e93, + 0x0e94, 0x0e95, 0x0ec7, 0x0ecc, 0x0ece, 0x0ecf, 0x0ed8, 0x0ed9, + 0x0eda, 0x0edb, 0x0808, 0x0809, 0x080a, 0x0810, 0x0811, 0x0844, + 0x0845, 0x0861, 0x0862, 0x0863, 0x086c, 0x0922, 0x0923, 0x092e, + 0x092f, 0x0936, 0x0937, 0x09b1, 0x09b2, 0x09b3, 0x09b4, 0x09b5, + 0x09b8, 0x09b9, 0x09ba, 0x09bb, 0x09bc, 0x09bd, 0x09be, 0x09bf, + 0x0b00, 0x0b15, 0x0b2c, 0x0b2d, 0x0b2e, 0x0b2f, 0x0b36, 0x0bb9, + 0x0c28, 0x0c2a, 0x0c2b, 0x0c2c, 0x0c2d, 0x0c2e, 0x0c2f, 0x0c30, + 0x0c31, 0x0c38, 0x0c60, 0x0c61, 0x0c62, 0x0c63, 0x0c8d, 0x0c8e, + 0x0c8f, 0x0c92, 0x0cbe, 0x0cbf, 0x0ce6, 0x0ce7, 0x0d40, 0x0d41, + 0x0d57, 0x0d58, 0x0d59, 0x0d5a, 0x0d5b, 0x0d5c, 0x0d5d, 0x0d98, + 0x0d99, 0x0d9a, 0x0d9b, 0x0d9c, 0x0d9d, 0x0dad, 0x0dae, 0x0daf, + 0x0dc0, 0x0dc1, 0x0dc2, 0x0dc3, 0x0dca, 0x0dcb, 0x0dec, 0x0ded, + 0x0dee, 0x0def, 0x1018, 0x1022, 0x1023, 0x1030, 0x1031, 0x1032, + 0x1033, 0x1050, 0x1051, 0x105c, 0x1074, 0x1075, 0x1076, 0x1077, + 0x1078, 0x1079, 0x107a, 0x107b, 0x10b2, 0x10b3, 0x10b8, 0x10b9, + 0x10ba, 0x10bb, 0x10d4, 0x10ea, 0x10eb, 0x10ec, 0x10ed, 0x1404, + 0x1405, 0x1406, 0x1407, 0x1410, 0x1411, 0x1412, 0x1413, 0x1414, + 0x1415, 0x1416, 0x1417, 0x1418, 0x1419, 0x1466, 0x1467, 0x1468, + 0x1469, 0x146a, 0x146b, 0x146c, 0x146d, 0x147e, 0x147f, 0x1488, + 0x1489, 0x148a, 0x148b, 0x14b6, 0x14b7, 0x14b8, 0x14b9, 0x14ba, + 0x14bb, 0x14bc, 0x14bd, 0x14f0, 0x14f1, 0x14f8, 0x14f9, 0x14fa, + 0x14fb, 0x14fc, 0x14fd, 0x14fe, 0x14ff, 0x152a, 0x152b, 0x152c, + 0x152d, 0x152e, 0x152f, 0x1530, 0x1531, 0x1548, 0x1549, 0x154e, + 0x154f, 0x1558, 0x1559, 0x155a, 0x155b, 0x1572, 0x159a, 0x159b, + 0x15ac, 0x15ba, 0x15bb, 0x15d0, 0x15d1, 0x15d2, 0x15d3, 0x15d4, + 0x15d5, 0x181d, 0x181e, 0x181f, 0x1840, 0x1841, 0x1842, 0x1843, + 0x1844, 0x1845, 0x1846, 0x1847, 0x1848, 0x1849, 0x1861, 0x1862, + 0x1863, 0x1864, 0x1865, 0x1866, 0x1867, 0x1868, 0x1869, 0x186a, + 0x186b, 0x186c, 0x186d, 0x186e, 0x191b, 0x191c, 0x191d, 0x191e, + 0x191f, 0x1942, 0x1943, 0x1944, 0x1945, 0x1946, 0x1947, 0x1958, + 0x1959, 0x19ed, 0x19ee, 0x19ef, 0x19f0, 0x19f1, 0x19f2, 0x19f3, + 0x19f4, 0x19f5, 0x19f6, 0x19f7, 0x1b0e, 0x1b0f, 0x1b62, 0x1b63, + 0x1b64, 0x1b65, 0x1b66, 0x1b67, 0x1b68, 0x1b69, 0x1b6a, 0x1b6b, + 0x1b6c, 0x1b6d, 0x1b6e, 0x1b6f, 0x1b82, 0x1ba8, 0x1ba9, 0x1baa, + 0x1bab, 0x1bac, 0x1bad, 0x1bae, 0x1baf, 0x1bb0, 0x1bb1, 0x1bb2, + 0x1bb3, 0x1d80, 0x1d81, 0x1d82, 0x1d83, 0x1d84, 0x1d85, 0x1d86, + 0x1d87, 0x1d88, 0x1d89, 0x1d8a, 0x1d8b, 0x1d8c, 0x1d8d, 0x1007, + 0x1008, 0x1009, 0x100a, 0x100b, 0x100c, 0x100d, 0x100e, 0x100f, + 0x1016, 0x1080, 0x1081, 0x1082, 0x1083, 0x1084, 0x1085, 0x1086, + 0x1087, 0x10c0, 0x123a, 0x123b, 0x123c, 0x123d, 0x123e, 0x123f, + 0x1240, 0x1241, 0x1242, 0x1243, 0x1350, 0x1352, 0x1353, 0x1358, + 0x1359, 0x135a, 0x135b, 0x135c, 0x135d, 0x135e, 0x135f, 0x1360, + 0x1361, 0x1602, 0x1603, 0x160c, 0x160d, 0x160e, 0x160f, 0x1620, + 0x1621, 0x1622, 0x1623, 0x1624, 0x1625, 0x1626, 0x1627, 0x1628, + 0x1629, 0x166e, 0x166f, 0x167c, 0x167d, 0x167e, 0x167f, 0x1770, + 0x1771, 0x1852, 0x1853, 0x1872, 0x1873, 0x1874, 0x1875, 0x1876, + 0x1877, 0x1878, 0x1879, 0x187a, 0x187b, 0x187c, 0x187d, 0x187e, + 0x187f, 0x1918, 0x1919, 0x1926, 0x1927, 0x1970, 0x1971, 0x1972, + 0x1973, 0x1974, 0x1975, 0x1976, 0x1977, 0x1978, 0x1979, 0x197a, + 0x197b, 0x1aa0, 0x1aa1, 0x1aa2, 0x1aa3, 0x1aa4, 0x1aa5, 0x1aa6, + 0x1aa7, 0x1aa8, 0x1aa9, 0x1aaa, 0x1aab, 0x1aac, 0x1aad, 0x1b3c, + 0x1b3d, 0x1b3e, 0x1b3f, 0x1b50, 0x1b51, 0x1b52, 0x1b53, 0x1b54, + 0x1b55, 0x1b56, 0x1b57, 0x1b58, 0x1b59, 0x2032, 0x2033, 0x2034, + 0x2035, 0x2036, 0x2037, 0x2038, 0x2039, 0x203a, 0x203b, 0x203c, + 0x203d, 0x203e, 0x203f, 0x2040, 0x2041, 0x2042, 0x2043, 0x20ba, + 0x20bb, 0x20cc, 0x20cd, 0x20ce, 0x20cf, 0x20e0, 0x20e1, 0x20e2, + 0x20e3, 0x20e4, 0x20e5, 0x20e6, 0x20e7, 0x21aa, 0x21ab, 0x21c0, + 0x21c1, 0x21c2, 0x21c3, 0x21c4, 0x21c5, 0x21c6, 0x21c7, 0x21c8, + 0x21c9, 0x21ca, 0x21cb, 0x21cc, 0x21cd, 0x21ce, 0x21cf, 0x21d0, + 0x21d1, 0x21d2, 0x21d3, 0x2894, 0x2895, 0x2896, 0x2897, 0x2898, + 0x2899, 0x289a, 0x289b, 0x289c, 0x289d, 0x289e, 0x289f, 0x28c0, + 0x28c1, 0x28c2, 0x28c3, 0x28c4, 0x28c5, 0x28c6, 0x28c7, 0x28c8, + 0x28c9, 0x28ca, 0x28cb, 0x2930, 0x2931, 0x2932, 0x2933, 0x2934, + 0x2935, 0x2936, 0x2937, 0x2938, 0x2939, 0x293a, 0x293b, 0x293c, + 0x293d, 0x293e, 0x293f, 0x2960, 0x2961, 0x2962, 0x2963, 0x2964, + 0x2965, 0x2966, 0x2967, 0x2968, 0x2969, 0x296a, 0x296b, 0x2a40, + 0x2a41, 0x2a42, 0x2a43, 0x2a44, 0x2a45, 0x2a46, 0x2a47, 0x2a48, + 0x2a49, 0x2a4a, 0x2a4b, 0x2a4c, 0x2a4d, 0x2a4e, 0x2a4f, 0x2a50, + 0x2a51, 0x2a52, 0x2a53, 0x2ae6, 0x2ae7, 0x2b24, 0x2b25, 0x2b26, + 0x2b27, 0x2b28, 0x2b29, 0x2b2a, 0x2b2b, 0x2b2c, 0x2b2d, 0x2b2e, + 0x2b2f, 0x2b30, 0x2b31, 0x2b32, 0x2b33, 0x2b5a, 0x2b5b, 0x3014, + 0x3015, 0x3016, 0x3017, 0x3020, 0x3021, 0x3022, 0x3023, 0x3024, + 0x3025, 0x3026, 0x3027, 0x3028, 0x3029, 0x302a, 0x302b, 0x302c, + 0x302d, 0x302e, 0x302f, 0x3030, 0x3031, 0x3032, 0x3033, 0x3034, + 0x3035, 0x3036, 0x3037, 0x3038, 0x3039, 0x30c0, 0x30c1, 0x30de, + 0x30df, 0x3218, 0x3219, 0x321a, 0x321b, 0x321c, 0x321d, 0x321e, + 0x321f, 0x3220, 0x3221, 0x3222, 0x3223, 0x3224, 0x3225, 0x3226, + 0x3227, 0x3228, 0x3229, 0x322a, 0x322b, 0x322c, 0x322d, 0x322e, + 0x322f, 0x3230, 0x3231, 0x3232, 0x3233, 0x3234, 0x3235, 0x3378, + 0x3379, 0x337a, 0x337b, 0x337c, 0x337d, 0x337e, 0x337f, 0x33c0, + 0x33c1, 0x33c2, 0x33c3, 0x33c4, 0x33c5, 0x33c6, 0x33c7, 0x33c8, + 0x33c9, 0x33ca, 0x33cb, 0x33cc, 0x33cd, 0x33ce, 0x33cf, 0x33d0, + 0x33d1, 0x33d2, 0x33d3, 0x33d4, 0x33d5, 0x33d6, 0x33d7, 0x33d8, + 0x33d9, 0x3706, 0x3707, 0x3730, 0x3731, 0x3732, 0x3733, 0x3734, + 0x3735, 0x3736, 0x3737, 0x3738, 0x3739, 0x373a, 0x373b, 0x373c, + 0x373d, 0x373e, 0x373f, 0x3740, 0x3741, 0x3742, 0x3743, 0x3744, + 0x3745, 0x3746, 0x3747, 0x3748, 0x3749, 0x374a, 0x374b, 0x374c, + 0x374d, 0x374e, 0x374f, 0x3b34, 0x3b35, 0x3b36, 0x3b37, 0x3be8, + 0x3be9, 0x3bea, 0x3beb, 0x3bec, 0x3bed, 0x3bee, 0x3bef, 0x3bf0, + 0x3bf1, 0x3bf2, 0x3bf3, 0x3bf4, 0x3bf5, 0x3bf6, 0x3bf7, 0x3bf8, + 0x3bf9, 0x3bfa, 0x3bfb, 0x3bfc, 0x3bfd, 0x3bfe, 0x3bff, 0x2000, + 0x2001, 0x2002, 0x2003, 0x2004, 0x2005, 0x2006, 0x2007, 0x2008, + 0x2009, 0x200a, 0x200b, 0x200c, 0x200d, 0x202e, 0x202f, 0x2182, + 0x2183, 0x21b4, 0x21b5, 0x21b6, 0x21b7, 0x21b8, 0x21b9, 0x21ba, + 0x21bb, 0x21bc, 0x21bd, 0x21be, 0x21bf, 0x2460, 0x2461, 0x2462, + 0x2463, 0x2464, 0x2465, 0x2466, 0x2467, 0x2468, 0x2469, 0x246a, + 0x246b, 0x246c, 0x246d, 0x246e, 0x246f, 0x2470, 0x2471, 0x2472, + 0x2473, 0x26a2, 0x26a3, 0x000b, +}; + +static const BYTE table1_mv_bits[1100] = +{ + 2, 4, 4, 4, 5, 5, 5, 5, + 6, 6, 7, 7, 7, 7, 7, 8, + 8, 8, 8, 8, 8, 8, 8, 8, + 8, 8, 8, 8, 8, 8, 8, 8, + 8, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 9, 9, 9, + 9, 9, 9, 9, 9, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 10, 10, 10, 10, 10, + 10, 10, 10, 11, 11, 11, 11, 11, + 11, 11, 11, 11, 11, 11, 11, 11, + 11, 11, 11, 11, 11, 11, 11, 11, + 11, 11, 11, 11, 11, 11, 11, 11, + 11, 11, 11, 11, 11, 11, 11, 11, + 11, 11, 11, 11, 11, 11, 11, 11, + 11, 11, 11, 11, 11, 11, 11, 11, + 11, 11, 11, 11, 11, 11, 11, 11, + 11, 11, 11, 11, 11, 11, 11, 11, + 11, 11, 11, 11, 11, 11, 11, 11, + 11, 11, 11, 12, 12, 12, 12, 12, + 12, 12, 12, 12, 12, 12, 12, 12, + 12, 12, 12, 12, 12, 12, 12, 12, + 12, 12, 12, 12, 12, 12, 12, 12, + 12, 12, 12, 12, 12, 12, 12, 12, + 12, 12, 12, 12, 12, 12, 12, 12, + 12, 12, 12, 12, 12, 12, 12, 12, + 12, 12, 12, 12, 12, 12, 12, 12, + 12, 12, 12, 12, 12, 12, 12, 12, + 12, 12, 12, 12, 12, 12, 12, 12, + 12, 12, 12, 12, 12, 12, 12, 12, + 12, 12, 12, 12, 12, 12, 12, 12, + 12, 12, 12, 12, 12, 12, 12, 12, + 12, 12, 12, 12, 12, 12, 12, 12, + 12, 12, 12, 12, 12, 12, 12, 12, + 12, 12, 12, 12, 12, 12, 12, 12, + 12, 12, 12, 12, 12, 12, 12, 12, + 12, 12, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 13, + 13, 13, 13, 13, 13, 13, 13, 14, + 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 14, + 14, 14, 14, 14, 14, 14, 14, 15, + 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 4, +}; + +static const BYTE table1_mvx[1099] = +{ + 32, 31, 32, 31, 33, 32, 33, 33, + 31, 34, 30, 32, 32, 34, 35, 32, + 34, 33, 29, 30, 30, 32, 31, 31, + 33, 35, 35, 33, 31, 29, 29, 33, + 34, 30, 31, 28, 36, 30, 34, 32, + 32, 37, 32, 32, 25, 27, 39, 32, + 32, 32, 38, 35, 36, 32, 37, 61, + 26, 32, 34, 35, 3, 35, 27, 28, + 29, 34, 28, 37, 31, 36, 32, 27, + 31, 30, 29, 39, 33, 29, 33, 35, + 25, 25, 29, 33, 31, 31, 31, 33, + 32, 30, 32, 32, 41, 39, 33, 36, + 32, 28, 34, 36, 38, 24, 60, 31, + 23, 28, 32, 33, 59, 32, 40, 30, + 5, 34, 32, 38, 32, 30, 43, 4, + 32, 32, 42, 31, 31, 32, 26, 38, + 26, 22, 21, 37, 61, 63, 37, 31, + 32, 33, 2, 1, 23, 33, 41, 27, + 35, 30, 38, 23, 33, 3, 28, 34, + 34, 27, 41, 29, 39, 35, 36, 29, + 32, 27, 30, 32, 24, 61, 37, 26, + 59, 25, 35, 27, 36, 37, 30, 31, + 34, 40, 3, 28, 34, 39, 32, 31, + 32, 30, 24, 28, 35, 36, 26, 32, + 31, 33, 29, 33, 39, 25, 30, 24, + 35, 59, 29, 34, 25, 30, 21, 35, + 43, 40, 32, 29, 5, 28, 31, 62, + 33, 33, 25, 31, 21, 31, 43, 31, + 34, 33, 20, 40, 39, 31, 31, 57, + 38, 32, 42, 33, 32, 31, 32, 29, + 30, 44, 5, 31, 22, 34, 36, 17, + 38, 58, 38, 35, 32, 60, 35, 24, + 32, 38, 16, 45, 42, 32, 31, 29, + 4, 30, 17, 40, 46, 48, 63, 32, + 42, 19, 41, 22, 28, 36, 45, 33, + 33, 32, 29, 7, 41, 42, 18, 33, + 33, 32, 22, 37, 1, 26, 22, 23, + 49, 28, 26, 27, 32, 33, 27, 23, + 28, 36, 15, 6, 34, 27, 31, 26, + 23, 2, 33, 32, 34, 41, 28, 32, + 41, 0, 36, 38, 34, 31, 47, 32, + 17, 31, 39, 33, 37, 51, 30, 47, + 32, 50, 32, 19, 63, 30, 25, 27, + 33, 62, 24, 31, 27, 30, 37, 31, + 45, 32, 39, 20, 46, 47, 35, 19, + 34, 1, 49, 21, 21, 14, 51, 26, + 23, 31, 36, 35, 58, 29, 29, 21, + 20, 42, 13, 28, 12, 40, 31, 33, + 39, 60, 32, 44, 33, 31, 28, 37, + 29, 32, 30, 49, 43, 28, 39, 25, + 32, 48, 2, 15, 20, 25, 31, 28, + 21, 24, 25, 15, 31, 17, 37, 43, + 18, 32, 33, 24, 33, 36, 13, 33, + 31, 39, 11, 31, 33, 32, 39, 37, + 32, 32, 29, 17, 44, 46, 36, 35, + 26, 37, 58, 32, 34, 38, 8, 38, + 38, 22, 29, 25, 16, 35, 32, 35, + 33, 43, 18, 46, 38, 50, 33, 18, + 53, 60, 13, 32, 36, 33, 51, 36, + 43, 45, 27, 42, 29, 24, 30, 25, + 31, 52, 31, 35, 38, 9, 22, 34, + 4, 17, 28, 55, 42, 25, 17, 20, + 47, 34, 33, 16, 40, 25, 16, 30, + 53, 29, 10, 11, 14, 26, 33, 4, + 35, 44, 26, 16, 31, 26, 34, 38, + 29, 31, 30, 24, 22, 61, 32, 9, + 45, 34, 31, 19, 9, 31, 46, 31, + 35, 54, 29, 57, 30, 50, 3, 31, + 63, 34, 47, 41, 51, 18, 31, 14, + 37, 38, 31, 24, 32, 31, 50, 33, + 31, 54, 27, 9, 33, 23, 19, 32, + 29, 29, 33, 28, 47, 49, 30, 47, + 33, 27, 25, 54, 44, 45, 50, 58, + 51, 48, 33, 59, 33, 34, 57, 13, + 26, 33, 13, 48, 30, 11, 7, 56, + 34, 55, 26, 0, 26, 35, 1, 51, + 33, 53, 31, 45, 12, 29, 29, 51, + 31, 48, 2, 6, 34, 30, 28, 33, + 60, 40, 27, 46, 31, 9, 35, 29, + 31, 39, 55, 46, 19, 37, 62, 34, + 30, 16, 19, 49, 41, 41, 39, 37, + 14, 5, 13, 35, 55, 30, 40, 40, + 42, 8, 20, 25, 45, 35, 33, 36, + 54, 38, 27, 37, 62, 40, 15, 59, + 49, 31, 29, 34, 34, 39, 24, 29, + 25, 29, 21, 29, 10, 61, 33, 49, + 35, 34, 3, 38, 39, 29, 7, 41, + 1, 35, 4, 23, 15, 23, 11, 37, + 28, 35, 30, 30, 24, 1, 43, 56, + 8, 34, 42, 24, 45, 30, 20, 23, + 8, 38, 22, 33, 17, 52, 34, 22, + 53, 43, 44, 1, 27, 31, 41, 43, + 41, 30, 31, 36, 30, 5, 55, 31, + 33, 30, 40, 23, 15, 29, 34, 34, + 59, 34, 30, 11, 13, 38, 5, 0, + 30, 42, 5, 30, 29, 34, 10, 44, + 30, 63, 35, 12, 3, 26, 15, 17, + 25, 34, 43, 39, 34, 56, 29, 23, + 30, 12, 30, 10, 35, 9, 24, 58, + 10, 12, 54, 33, 37, 20, 41, 35, + 29, 18, 61, 30, 40, 24, 39, 53, + 62, 26, 29, 33, 34, 53, 49, 21, + 27, 11, 63, 20, 26, 23, 7, 13, + 6, 47, 29, 30, 9, 51, 22, 34, + 21, 25, 33, 56, 57, 30, 38, 51, + 51, 38, 63, 28, 40, 35, 33, 18, + 33, 33, 24, 58, 58, 34, 49, 29, + 43, 4, 1, 4, 42, 35, 35, 30, + 17, 5, 56, 61, 25, 37, 36, 55, + 28, 35, 29, 50, 48, 52, 2, 42, + 34, 40, 46, 46, 43, 35, 29, 48, + 20, 29, 31, 41, 7, 30, 35, 19, + 14, 21, 8, 39, 39, 40, 46, 55, + 34, 6, 30, 34, 37, 25, 37, 33, + 22, 44, 52, 17, 35, 29, 36, 35, + 40, 37, 28, 30, 50, 14, 28, 55, + 6, 23, 19, 14, 30, 3, 30, 28, + 28, 61, 61, 47, 45, 48, 40, 40, + 34, 34, 25, 30, 29, 35, 4, 26, + 53, 50, 26, 41, 27, 59, 27, 38, + 39, 3, 50, 43, 47, 23, 33, 55, + 35, 21, 23, 35, 61, 33, 46, 52, + 35, 34, 24, 30, 43, 16, 37, 21, + 2, 24, 45, 34, 30, 55, 55, 1, + 29, 29, 26, 28, 25, 31, 36, 22, + 17, 30, 52, 2, 44, 44, 57, 26, + 62, 41, 39, 57, 26, 46, 49, 11, + 16, 19, 5, 59, 38, 39, 58, 38, + 25, 49, 50, 22, 28, 59, 9, 59, + 7, 28, 55, 17, 4, 35, 50, 21, + 29, 44, 47, 18, 24, 19, 25, 42, + 35, 3, 51, 35, 16, 35, 30, 63, + 57, 39, 39, 25, 35, 38, 9, 16, + 36, 45, 31, 60, 14, 34, 42, 24, + 0, 37, 18, 61, 57, 37, 28, 53, + 20, 46, 14, 47, 38, 38, 38, 9, + 34, 39, 43, 17, 39, 59, 5, 27, + 0, 12, 27, +}; + +static const BYTE table1_mvy[1099] = +{ + 32, 32, 31, 31, 32, 33, 31, 33, + 33, 32, 32, 30, 34, 31, 32, 29, + 33, 30, 32, 33, 31, 35, 34, 30, + 34, 31, 33, 29, 29, 31, 33, 35, + 30, 30, 35, 32, 32, 34, 34, 28, + 25, 32, 36, 27, 32, 32, 32, 37, + 39, 3, 32, 30, 31, 26, 31, 32, + 32, 38, 29, 29, 32, 34, 31, 31, + 34, 35, 33, 33, 28, 33, 1, 33, + 27, 29, 30, 31, 28, 29, 37, 35, + 31, 33, 35, 27, 36, 37, 25, 25, + 61, 35, 4, 5, 32, 33, 36, 30, + 23, 30, 28, 34, 31, 32, 32, 39, + 32, 34, 21, 39, 32, 59, 32, 28, + 32, 36, 60, 33, 24, 36, 32, 32, + 41, 2, 32, 38, 26, 22, 33, 30, + 31, 32, 32, 30, 31, 32, 29, 3, + 40, 38, 32, 32, 33, 26, 31, 34, + 28, 38, 34, 31, 3, 31, 35, 38, + 27, 35, 33, 28, 29, 27, 29, 27, + 43, 29, 37, 63, 31, 33, 34, 30, + 31, 30, 37, 30, 35, 35, 26, 41, + 37, 31, 33, 28, 26, 30, 42, 24, + 7, 27, 33, 29, 36, 28, 34, 57, + 23, 41, 36, 23, 35, 34, 25, 30, + 25, 33, 25, 25, 29, 24, 33, 39, + 33, 33, 0, 37, 31, 36, 21, 32, + 61, 24, 35, 61, 31, 5, 31, 59, + 39, 21, 32, 30, 34, 22, 40, 32, + 29, 16, 31, 5, 62, 2, 20, 39, + 39, 32, 33, 1, 31, 24, 36, 32, + 36, 32, 28, 26, 6, 31, 38, 34, + 58, 35, 32, 33, 33, 17, 43, 26, + 31, 40, 31, 34, 32, 32, 31, 19, + 30, 32, 29, 33, 38, 38, 32, 59, + 40, 18, 38, 32, 35, 34, 32, 17, + 1, 15, 30, 28, 31, 28, 34, 29, + 32, 27, 35, 27, 49, 22, 37, 34, + 37, 26, 32, 32, 22, 28, 45, 29, + 30, 31, 43, 46, 41, 30, 26, 13, + 34, 32, 27, 38, 42, 42, 33, 47, + 33, 60, 27, 42, 25, 32, 22, 32, + 48, 32, 45, 33, 33, 41, 27, 25, + 19, 31, 35, 19, 36, 42, 27, 17, + 31, 44, 28, 33, 33, 31, 23, 31, + 40, 33, 31, 34, 30, 32, 33, 36, + 35, 47, 37, 41, 31, 23, 41, 29, + 30, 35, 32, 25, 32, 28, 58, 2, + 37, 33, 14, 33, 49, 20, 39, 36, + 21, 9, 23, 33, 35, 24, 39, 37, + 11, 33, 30, 31, 31, 28, 51, 40, + 35, 29, 25, 33, 46, 35, 37, 30, + 30, 8, 63, 28, 15, 40, 33, 45, + 49, 25, 32, 4, 47, 51, 36, 39, + 53, 10, 24, 29, 30, 31, 25, 40, + 38, 38, 33, 56, 23, 27, 32, 37, + 26, 29, 43, 36, 33, 24, 55, 43, + 9, 29, 34, 34, 24, 33, 18, 33, + 33, 30, 31, 50, 24, 60, 30, 39, + 34, 30, 39, 28, 22, 38, 2, 26, + 63, 32, 57, 21, 39, 33, 28, 18, + 30, 34, 22, 33, 29, 41, 30, 34, + 35, 21, 13, 34, 35, 39, 30, 46, + 32, 42, 32, 31, 33, 26, 11, 33, + 22, 31, 25, 31, 53, 27, 43, 25, + 40, 50, 21, 36, 38, 30, 12, 31, + 34, 20, 15, 29, 32, 62, 30, 13, + 17, 32, 19, 31, 20, 31, 30, 7, + 1, 17, 34, 37, 31, 31, 44, 34, + 26, 40, 16, 37, 52, 48, 30, 20, + 18, 33, 38, 29, 7, 25, 30, 54, + 45, 47, 46, 41, 29, 29, 16, 30, + 14, 26, 38, 34, 34, 29, 34, 30, + 29, 30, 57, 30, 4, 46, 33, 29, + 39, 44, 30, 31, 50, 33, 31, 32, + 19, 32, 40, 31, 37, 47, 1, 35, + 16, 31, 0, 35, 33, 1, 17, 34, + 9, 34, 33, 31, 49, 43, 42, 51, + 34, 29, 23, 29, 14, 30, 45, 49, + 11, 24, 31, 28, 35, 41, 30, 44, + 18, 29, 34, 35, 36, 25, 26, 21, + 31, 30, 34, 19, 34, 44, 36, 38, + 25, 31, 28, 23, 37, 3, 55, 41, + 30, 22, 41, 24, 33, 26, 35, 35, + 30, 55, 51, 47, 48, 38, 24, 15, + 21, 50, 25, 46, 30, 29, 10, 34, + 42, 45, 29, 42, 22, 3, 33, 27, + 34, 1, 34, 28, 34, 36, 35, 23, + 23, 13, 58, 3, 26, 63, 25, 31, + 34, 61, 38, 39, 25, 61, 29, 37, + 30, 41, 26, 48, 28, 33, 50, 35, + 30, 37, 29, 29, 40, 6, 39, 28, + 28, 19, 8, 22, 45, 34, 35, 10, + 58, 17, 37, 39, 30, 18, 54, 14, + 29, 16, 59, 30, 35, 23, 35, 30, + 47, 36, 29, 55, 20, 12, 31, 35, + 14, 29, 18, 34, 34, 24, 29, 26, + 22, 2, 27, 23, 8, 30, 55, 38, + 60, 31, 4, 34, 49, 34, 27, 34, + 33, 30, 31, 54, 42, 35, 38, 46, + 44, 26, 27, 9, 39, 25, 21, 29, + 28, 42, 13, 0, 5, 34, 37, 28, + 24, 29, 63, 26, 22, 27, 29, 25, + 33, 25, 61, 0, 35, 25, 36, 15, + 27, 40, 53, 33, 3, 10, 16, 37, + 38, 18, 30, 46, 27, 9, 6, 29, + 62, 8, 42, 28, 29, 3, 25, 16, + 26, 29, 35, 28, 27, 51, 61, 48, + 37, 9, 34, 7, 49, 45, 20, 29, + 21, 5, 5, 29, 28, 34, 29, 24, + 10, 24, 35, 36, 38, 55, 11, 36, + 38, 53, 54, 26, 30, 49, 20, 27, + 30, 39, 33, 41, 49, 22, 38, 38, + 4, 30, 8, 9, 3, 24, 22, 50, + 37, 36, 31, 27, 2, 9, 42, 63, + 25, 19, 44, 1, 28, 28, 48, 30, + 34, 41, 41, 38, 12, 27, 15, 0, + 16, 34, 35, 38, 28, 29, 40, 42, + 51, 52, 45, 54, 59, 59, 42, 44, + 37, 26, 46, 24, 15, 39, 22, 46, + 19, 35, 38, 17, 37, 23, 52, 55, + 50, 37, 26, 11, 37, 12, 24, 30, + 16, 13, 22, 13, 36, 35, 40, 41, + 34, 41, 26, 53, 51, 5, 21, 30, + 2, 63, 41, 20, 1, 56, 21, 24, + 25, 5, 28, 35, 26, 28, 30, 18, + 29, 23, 40, 34, 20, 42, 39, 34, + 28, 61, 38, 27, 62, 9, 36, 17, + 9, 49, 24, 25, 54, 34, 39, 37, + 3, 1, 25, 38, 38, 44, 35, 36, + 12, 60, 36, 38, 40, 25, 43, 39, + 53, 28, 39, 57, 46, 10, 52, 27, + 35, 42, 45, 59, 15, 60, 38, 24, + 23, 39, 12, 29, 24, 0, 20, 16, + 28, 43, 35, 28, 1, 49, 4, 21, + 42, 39, 29, 3, 44, 21, 53, 55, + 11, 5, 3, 39, 53, 28, 25, 19, + 34, 28, 21, +}; + + +const BYTE mvtab[33][2] = +{ + {1,1}, {1,2}, {1,3}, {1,4}, {3,6}, {5,7}, {4,7}, {3,7}, + {11,9}, {10,9}, {9,9}, {17,10}, {16,10}, {15,10}, {14,10}, {13,10}, + {12,10}, {11,10}, {10,10}, {9,10}, {8,10}, {7,10}, {6,10}, {5,10}, + {4,10}, {7,11}, {6,11}, {5,11}, {4,11}, {3,11}, {2,11}, {3,12}, + {2,12} +}; + +static DIVX_MV_TABLE mv_tables[2] = +{ + { + 1099, + table0_mv_code, + table0_mv_bits, + table0_mvx, + table0_mvy, + }, + { + 1099, + table1_mv_code, + table1_mv_bits, + table1_mvx, + table1_mvy, + } +}; + + +static const DWORD table0_dc_lum[120][2] = +{ + { 0x1, 1 },{ 0x1, 2 },{ 0x1, 4 },{ 0x1, 5 }, + { 0x5, 5 },{ 0x7, 5 },{ 0x8, 6 },{ 0xc, 6 }, + { 0x0, 7 },{ 0x2, 7 },{ 0x12, 7 },{ 0x1a, 7 }, + { 0x3, 8 },{ 0x7, 8 },{ 0x27, 8 },{ 0x37, 8 }, + { 0x5, 9 },{ 0x4c, 9 },{ 0x6c, 9 },{ 0x6d, 9 }, + { 0x8, 10 },{ 0x19, 10 },{ 0x9b, 10 },{ 0x1b, 10 }, + { 0x9a, 10 },{ 0x13, 11 },{ 0x34, 11 },{ 0x35, 11 }, + { 0x61, 12 },{ 0x48, 13 },{ 0xc4, 13 },{ 0x4a, 13 }, + { 0xc6, 13 },{ 0xc7, 13 },{ 0x92, 14 },{ 0x18b, 14 }, + { 0x93, 14 },{ 0x183, 14 },{ 0x182, 14 },{ 0x96, 14 }, + { 0x97, 14 },{ 0x180, 14 },{ 0x314, 15 },{ 0x315, 15 }, + { 0x605, 16 },{ 0x604, 16 },{ 0x606, 16 },{ 0xc0e, 17 }, + { 0x303cd, 23 },{ 0x303c9, 23 },{ 0x303c8, 23 },{ 0x303ca, 23 }, + { 0x303cb, 23 },{ 0x303cc, 23 },{ 0x303ce, 23 },{ 0x303cf, 23 }, + { 0x303d0, 23 },{ 0x303d1, 23 },{ 0x303d2, 23 },{ 0x303d3, 23 }, + { 0x303d4, 23 },{ 0x303d5, 23 },{ 0x303d6, 23 },{ 0x303d7, 23 }, + { 0x303d8, 23 },{ 0x303d9, 23 },{ 0x303da, 23 },{ 0x303db, 23 }, + { 0x303dc, 23 },{ 0x303dd, 23 },{ 0x303de, 23 },{ 0x303df, 23 }, + { 0x303e0, 23 },{ 0x303e1, 23 },{ 0x303e2, 23 },{ 0x303e3, 23 }, + { 0x303e4, 23 },{ 0x303e5, 23 },{ 0x303e6, 23 },{ 0x303e7, 23 }, + { 0x303e8, 23 },{ 0x303e9, 23 },{ 0x303ea, 23 },{ 0x303eb, 23 }, + { 0x303ec, 23 },{ 0x303ed, 23 },{ 0x303ee, 23 },{ 0x303ef, 23 }, + { 0x303f0, 23 },{ 0x303f1, 23 },{ 0x303f2, 23 },{ 0x303f3, 23 }, + { 0x303f4, 23 },{ 0x303f5, 23 },{ 0x303f6, 23 },{ 0x303f7, 23 }, + { 0x303f8, 23 },{ 0x303f9, 23 },{ 0x303fa, 23 },{ 0x303fb, 23 }, + { 0x303fc, 23 },{ 0x303fd, 23 },{ 0x303fe, 23 },{ 0x303ff, 23 }, + { 0x60780, 24 },{ 0x60781, 24 },{ 0x60782, 24 },{ 0x60783, 24 }, + { 0x60784, 24 },{ 0x60785, 24 },{ 0x60786, 24 },{ 0x60787, 24 }, + { 0x60788, 24 },{ 0x60789, 24 },{ 0x6078a, 24 },{ 0x6078b, 24 }, + { 0x6078c, 24 },{ 0x6078d, 24 },{ 0x6078e, 24 },{ 0x6078f, 24 }, +}; + +static const DWORD table0_dc_chroma[120][2] = +{ + { 0x0, 2 },{ 0x1, 2 },{ 0x5, 3 },{ 0x9, 4 }, + { 0xd, 4 },{ 0x11, 5 },{ 0x1d, 5 },{ 0x1f, 5 }, + { 0x21, 6 },{ 0x31, 6 },{ 0x38, 6 },{ 0x33, 6 }, + { 0x39, 6 },{ 0x3d, 6 },{ 0x61, 7 },{ 0x79, 7 }, + { 0x80, 8 },{ 0xc8, 8 },{ 0xca, 8 },{ 0xf0, 8 }, + { 0x81, 8 },{ 0xc0, 8 },{ 0xc9, 8 },{ 0x107, 9 }, + { 0x106, 9 },{ 0x196, 9 },{ 0x183, 9 },{ 0x1e3, 9 }, + { 0x1e2, 9 },{ 0x20a, 10 },{ 0x20b, 10 },{ 0x609, 11 }, + { 0x412, 11 },{ 0x413, 11 },{ 0x60b, 11 },{ 0x411, 11 }, + { 0x60a, 11 },{ 0x65f, 11 },{ 0x410, 11 },{ 0x65d, 11 }, + { 0x65e, 11 },{ 0xcb8, 12 },{ 0xc10, 12 },{ 0xcb9, 12 }, + { 0x1823, 13 },{ 0x3045, 14 },{ 0x6089, 15 },{ 0xc110, 16 }, + { 0x304448, 22 },{ 0x304449, 22 },{ 0x30444a, 22 },{ 0x30444b, 22 }, + { 0x30444c, 22 },{ 0x30444d, 22 },{ 0x30444e, 22 },{ 0x30444f, 22 }, + { 0x304450, 22 },{ 0x304451, 22 },{ 0x304452, 22 },{ 0x304453, 22 }, + { 0x304454, 22 },{ 0x304455, 22 },{ 0x304456, 22 },{ 0x304457, 22 }, + { 0x304458, 22 },{ 0x304459, 22 },{ 0x30445a, 22 },{ 0x30445b, 22 }, + { 0x30445c, 22 },{ 0x30445d, 22 },{ 0x30445e, 22 },{ 0x30445f, 22 }, + { 0x304460, 22 },{ 0x304461, 22 },{ 0x304462, 22 },{ 0x304463, 22 }, + { 0x304464, 22 },{ 0x304465, 22 },{ 0x304466, 22 },{ 0x304467, 22 }, + { 0x304468, 22 },{ 0x304469, 22 },{ 0x30446a, 22 },{ 0x30446b, 22 }, + { 0x30446c, 22 },{ 0x30446d, 22 },{ 0x30446e, 22 },{ 0x30446f, 22 }, + { 0x304470, 22 },{ 0x304471, 22 },{ 0x304472, 22 },{ 0x304473, 22 }, + { 0x304474, 22 },{ 0x304475, 22 },{ 0x304476, 22 },{ 0x304477, 22 }, + { 0x304478, 22 },{ 0x304479, 22 },{ 0x30447a, 22 },{ 0x30447b, 22 }, + { 0x30447c, 22 },{ 0x30447d, 22 },{ 0x30447e, 22 },{ 0x30447f, 22 }, + { 0x608880, 23 },{ 0x608881, 23 },{ 0x608882, 23 },{ 0x608883, 23 }, + { 0x608884, 23 },{ 0x608885, 23 },{ 0x608886, 23 },{ 0x608887, 23 }, + { 0x608888, 23 },{ 0x608889, 23 },{ 0x60888a, 23 },{ 0x60888b, 23 }, + { 0x60888c, 23 },{ 0x60888d, 23 },{ 0x60888e, 23 },{ 0x60888f, 23 }, +}; + +/* dc table 1 */ + +static const DWORD table1_dc_lum[120][2] = +{ + { 0x2, 2 },{ 0x3, 2 },{ 0x3, 3 },{ 0x2, 4 }, + { 0x5, 4 },{ 0x1, 5 },{ 0x3, 5 },{ 0x8, 5 }, + { 0x0, 6 },{ 0x5, 6 },{ 0xd, 6 },{ 0xf, 6 }, + { 0x13, 6 },{ 0x8, 7 },{ 0x18, 7 },{ 0x1c, 7 }, + { 0x24, 7 },{ 0x4, 8 },{ 0x6, 8 },{ 0x12, 8 }, + { 0x32, 8 },{ 0x3b, 8 },{ 0x4a, 8 },{ 0x4b, 8 }, + { 0xb, 9 },{ 0x26, 9 },{ 0x27, 9 },{ 0x66, 9 }, + { 0x74, 9 },{ 0x75, 9 },{ 0x14, 10 },{ 0x1c, 10 }, + { 0x1f, 10 },{ 0x1d, 10 },{ 0x2b, 11 },{ 0x3d, 11 }, + { 0x19d, 11 },{ 0x19f, 11 },{ 0x54, 12 },{ 0x339, 12 }, + { 0x338, 12 },{ 0x33d, 12 },{ 0xab, 13 },{ 0xf1, 13 }, + { 0x678, 13 },{ 0xf2, 13 },{ 0x1e0, 14 },{ 0x1e1, 14 }, + { 0x154, 14 },{ 0xcf2, 14 },{ 0x3cc, 15 },{ 0x2ab, 15 }, + { 0x19e7, 15 },{ 0x3ce, 15 },{ 0x19e6, 15 },{ 0x554, 16 }, + { 0x79f, 16 },{ 0x555, 16 },{ 0xf3d, 17 },{ 0xf37, 17 }, + { 0xf3c, 17 },{ 0xf35, 17 },{ 0x1e6d, 18 },{ 0x1e68, 18 }, + { 0x3cd8, 19 },{ 0x3cd3, 19 },{ 0x3cd9, 19 },{ 0x79a4, 20 }, + { 0xf34ba, 25 },{ 0xf34b4, 25 },{ 0xf34b5, 25 },{ 0xf34b6, 25 }, + { 0xf34b7, 25 },{ 0xf34b8, 25 },{ 0xf34b9, 25 },{ 0xf34bb, 25 }, + { 0xf34bc, 25 },{ 0xf34bd, 25 },{ 0xf34be, 25 },{ 0xf34bf, 25 }, + { 0x1e6940, 26 },{ 0x1e6941, 26 },{ 0x1e6942, 26 },{ 0x1e6943, 26 }, + { 0x1e6944, 26 },{ 0x1e6945, 26 },{ 0x1e6946, 26 },{ 0x1e6947, 26 }, + { 0x1e6948, 26 },{ 0x1e6949, 26 },{ 0x1e694a, 26 },{ 0x1e694b, 26 }, + { 0x1e694c, 26 },{ 0x1e694d, 26 },{ 0x1e694e, 26 },{ 0x1e694f, 26 }, + { 0x1e6950, 26 },{ 0x1e6951, 26 },{ 0x1e6952, 26 },{ 0x1e6953, 26 }, + { 0x1e6954, 26 },{ 0x1e6955, 26 },{ 0x1e6956, 26 },{ 0x1e6957, 26 }, + { 0x1e6958, 26 },{ 0x1e6959, 26 },{ 0x1e695a, 26 },{ 0x1e695b, 26 }, + { 0x1e695c, 26 },{ 0x1e695d, 26 },{ 0x1e695e, 26 },{ 0x1e695f, 26 }, + { 0x1e6960, 26 },{ 0x1e6961, 26 },{ 0x1e6962, 26 },{ 0x1e6963, 26 }, + { 0x1e6964, 26 },{ 0x1e6965, 26 },{ 0x1e6966, 26 },{ 0x1e6967, 26 }, +}; + +static const DWORD table1_dc_chroma[120][2] = +{ + { 0x0, 2 },{ 0x1, 2 },{ 0x4, 3 },{ 0x7, 3 }, + { 0xb, 4 },{ 0xd, 4 },{ 0x15, 5 },{ 0x28, 6 }, + { 0x30, 6 },{ 0x32, 6 },{ 0x52, 7 },{ 0x62, 7 }, + { 0x66, 7 },{ 0xa6, 8 },{ 0xc6, 8 },{ 0xcf, 8 }, + { 0x14f, 9 },{ 0x18e, 9 },{ 0x19c, 9 },{ 0x29d, 10 }, + { 0x33a, 10 },{ 0x538, 11 },{ 0x63c, 11 },{ 0x63e, 11 }, + { 0x63f, 11 },{ 0x676, 11 },{ 0xa73, 12 },{ 0xc7a, 12 }, + { 0xcef, 12 },{ 0x14e5, 13 },{ 0x19dd, 13 },{ 0x29c8, 14 }, + { 0x29c9, 14 },{ 0x63dd, 15 },{ 0x33b8, 14 },{ 0x33b9, 14 }, + { 0xc7b6, 16 },{ 0x63d8, 15 },{ 0x63df, 15 },{ 0xc7b3, 16 }, + { 0xc7b4, 16 },{ 0xc7b5, 16 },{ 0x63de, 15 },{ 0xc7b7, 16 }, + { 0xc7b8, 16 },{ 0xc7b9, 16 },{ 0x18f65, 17 },{ 0x31ec8, 18 }, + { 0xc7b248, 24 },{ 0xc7b249, 24 },{ 0xc7b24a, 24 },{ 0xc7b24b, 24 }, + { 0xc7b24c, 24 },{ 0xc7b24d, 24 },{ 0xc7b24e, 24 },{ 0xc7b24f, 24 }, + { 0xc7b250, 24 },{ 0xc7b251, 24 },{ 0xc7b252, 24 },{ 0xc7b253, 24 }, + { 0xc7b254, 24 },{ 0xc7b255, 24 },{ 0xc7b256, 24 },{ 0xc7b257, 24 }, + { 0xc7b258, 24 },{ 0xc7b259, 24 },{ 0xc7b25a, 24 },{ 0xc7b25b, 24 }, + { 0xc7b25c, 24 },{ 0xc7b25d, 24 },{ 0xc7b25e, 24 },{ 0xc7b25f, 24 }, + { 0xc7b260, 24 },{ 0xc7b261, 24 },{ 0xc7b262, 24 },{ 0xc7b263, 24 }, + { 0xc7b264, 24 },{ 0xc7b265, 24 },{ 0xc7b266, 24 },{ 0xc7b267, 24 }, + { 0xc7b268, 24 },{ 0xc7b269, 24 },{ 0xc7b26a, 24 },{ 0xc7b26b, 24 }, + { 0xc7b26c, 24 },{ 0xc7b26d, 24 },{ 0xc7b26e, 24 },{ 0xc7b26f, 24 }, + { 0xc7b270, 24 },{ 0xc7b271, 24 },{ 0xc7b272, 24 },{ 0xc7b273, 24 }, + { 0xc7b274, 24 },{ 0xc7b275, 24 },{ 0xc7b276, 24 },{ 0xc7b277, 24 }, + { 0xc7b278, 24 },{ 0xc7b279, 24 },{ 0xc7b27a, 24 },{ 0xc7b27b, 24 }, + { 0xc7b27c, 24 },{ 0xc7b27d, 24 },{ 0xc7b27e, 24 },{ 0xc7b27f, 24 }, + { 0x18f6480, 25 },{ 0x18f6481, 25 },{ 0x18f6482, 25 },{ 0x18f6483, 25 }, + { 0x18f6484, 25 },{ 0x18f6485, 25 },{ 0x18f6486, 25 },{ 0x18f6487, 25 }, + { 0x18f6488, 25 },{ 0x18f6489, 25 },{ 0x18f648a, 25 },{ 0x18f648b, 25 }, + { 0x18f648c, 25 },{ 0x18f648d, 25 },{ 0x18f648e, 25 },{ 0x18f648f, 25 }, +}; + +static const BYTE divx_log2_tab[256] = +{ + 0,0,1,1,2,2,2,2,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4, + 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5, + 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, + 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7 +}; + +static const BYTE zigzag_direct[64] = +{ + 0, 1, 8, 16, 9, 2, 3, 10, + 17, 24, 32, 25, 18, 11, 4, 5, + 12, 19, 26, 33, 40, 48, 41, 34, + 27, 20, 13, 6, 7, 14, 21, 28, + 35, 42, 49, 56, 57, 50, 43, 36, + 29, 22, 15, 23, 30, 37, 44, 51, + 58, 59, 52, 45, 38, 31, 39, 46, + 53, 60, 61, 54, 47, 55, 62, 63 +}; + +static const BYTE alternate_horizontal_scan[64] = +{ + 0, 1, 2, 3, 8, 9, 16, 17, + 10, 11, 4, 5, 6, 7, 15, 14, + 13, 12, 19, 18, 24, 25, 32, 33, + 26, 27, 20, 21, 22, 23, 28, 29, + 30, 31, 34, 35, 40, 41, 48, 49, + 42, 43, 36, 37, 38, 39, 44, 45, + 46, 47, 50, 51, 56, 57, 58, 59, + 52, 53, 54, 55, 60, 61, 62, 63, +}; + +static const BYTE alternate_vertical_scan[64] = +{ + 0, 8, 16, 24, 1, 9, 2, 10, + 17, 25, 32, 40, 48, 56, 57, 49, + 41, 33, 26, 18, 3, 11, 4, 12, + 19, 27, 34, 42, 50, 58, 35, 43, + 51, 59, 20, 28, 5, 13, 6, 14, + 21, 29, 36, 44, 52, 60, 37, 45, + 53, 61, 22, 30, 7, 15, 23, 31, + 38, 46, 54, 62, 39, 47, 55, 63, +}; + +// strange +#if 0 +static const BYTE strange_scan[64] = +{ + /* 0 */ 0, 8, 16, , , , , , /* 7 */ + /* 8 */ 1, , , , , , , , /* 15 */ + /* 16 */ , 17, 48, 27, , , , , /* 23 */ + /* 24 */ , 40, , 19, , , , , /* 31 */ + /* 32 */ , , , , , , , , /* 39 */ + /* 40 */ , 12, , , , , , , /* 47 */ + /* 48 */ 18, , , , , , , , /* 55 */ + /* 56 */ , , , , , , , , /* 63 */ +}; +#endif + +/* dc encoding for mpeg4 */ +static const BYTE DCtab_lum[13][2] = +{ + {3,3}, {3,2}, {2,2}, {2,3}, {1,3}, {1,4}, {1,5}, {1,6}, {1,7}, + {1,8}, {1,9}, {1,10}, {1,11}, +}; + +static const BYTE DCtab_chrom[13][2] = +{ + {3,2}, {2,2}, {1,2}, {1,3}, {1,4}, {1,5}, {1,6}, {1,7}, {1,8}, + {1,9}, {1,10}, {1,11}, {1,12}, +}; + +static const BYTE intra_MCBPC_code[9] = { 1, 1, 2, 3, 1, 1, 2, 3, 1 }; +static const BYTE intra_MCBPC_bits[9] = { 1, 3, 3, 3, 4, 6, 6, 6, 9 }; + +static const BYTE inter_MCBPC_code[28] = { + 1, 3, 2, 5, + 3, 4, 3, 3, + 3, 7, 6, 5, + 4, 4, 3, 2, + 2, 5, 4, 5, + 1, 0, 0, 0, /* Stuffing */ + 2, 12, 14, 15, +}; +static const BYTE inter_MCBPC_bits[28] = { + 1, 4, 4, 6, /* inter */ + 5, 8, 8, 7, /* intra */ + 3, 7, 7, 9, /* interQ */ + 6, 9, 9, 9, /* intraQ */ + 3, 7, 7, 8, /* inter4 */ + 9, 0, 0, 0, /* Stuffing */ + 11, 13, 13, 13,/* inter4Q*/ +}; + +static const BYTE cbpy_tab[16][2] = +{ + {3,4}, {5,5}, {4,5}, {9,4}, {3,5}, {7,4}, {2,6}, {11,4}, + {2,5}, {3,6}, {5,4}, {10,4}, {4,4}, {8,4}, {6,4}, {3,2} +}; + +static BYTE old_y_dc_scale_table[32] = +{ + 0, 8, 8, 8, 8,10,12,14,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39 +}; +static BYTE old_c_dc_scale_table[32] = +{ + 0, 8, 8, 8, 8, 9, 9,10,10,11,11,12,12,13,13,14,14,15,15,16,16,17,17,18,18,19,19,20,20,21,21,22 +}; + +static BYTE mpeg4_y_dc_scale_table[32] = +{ + 0, 8, 8, 8, 8,10,12,14,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,34,36,38,40,42,44,46 +}; +static BYTE mpeg4_c_dc_scale_table[32] = +{ + 0, 8, 8, 8, 8, 9, 9,10,10,11,11,12,12,13,13,14,14,15,15,16,16,17,17,18,18,19,20,21,22,23,24,25 +}; + +static BYTE default_chroma_qscale_table[32] = +{ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31 +}; + +#endif // of SP_DIVX_TABLES_H diff --git a/src/divx.c b/src/divx.c new file mode 100644 index 0000000..5b8dd10 --- /dev/null +++ b/src/divx.c @@ -0,0 +1,2612 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - DivX3->MPEG-4 transcoder source file. + * \file divx.cpp + * \author bombur + * \version 0.1 + * \date 1.04.2007 + * + * Portions of code taken from libavcodec. + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include + +#include +#include + +#define DIVX_INTERNAL +#include "bitstream.h" +#include "divx.h" +#include "divx-tables.h" + +//#define USE_SLOW_METHOD_LIMIT + +// UNCOMMENT ALL!!! +#define USE_SLOW_METHOD +//#define USE_AC_CORRECTION +//#define USE_ONLY_SLOW_METHOD +//#define DUMP + +//!!!!!!!!!!!!!!!!!!!! +//static int CNT = 0; + +#ifdef DUMP +#define DEBUG_MSG msg +//#define DEBUG_MSG printf +#endif + + +#define INLINE SP_INLINE +#define STATIC static +//#define INLINE +//#define STATIC + +////////////////////////////////////////////////////////////////// + +#ifdef USE_SLOW_METHOD_LIMIT +const int max_allowed_frame_size_for_correct_method = 3750*4; +#endif + +int frame_number; + +static int width, height; +static DWORD pict_type; + +static DWORD qscale, chroma_qscale; +static int enc_y_dc_scale, enc_c_dc_scale; +static BYTE *enc_y_dc_scale_table, *enc_c_dc_scale_table; +static int dec_y_dc_scale, dec_c_dc_scale; +static BYTE *dec_y_dc_scale_table, *dec_c_dc_scale_table; +static BYTE *chroma_qscale_table; + +#ifdef USE_AC_CORRECTION +static int *ac_val_base, *ac_val; +static int ac_size; +static int qmul, qadd; +static DWORD use_acpred; +#endif + +static int mb_width, mb_height, mb_num; +static int slice_height; + +static int use_skip_mb_code; +static int f_code; +static int rl_table_index; +static int rl_chroma_table_index; +static int dc_table_index; +static int mv_table_index; +static int no_rounding; +static BOOL flipflop_rounding; + +static int b8_stride, mb_stride; +static int block_index[6], (*block_index_tbl)[6]; +static BYTE *coded_block_base, *coded_block; +static signed short (*motion_val_base)[2], (*motion_val)[2]; + +static BOOL mb_intra; +static int mb_x, mb_y; +static BOOL mb_skip; +static int first_slice_line; +static int motion_x, motion_y; +static int ac_pred; +static int block_wrap[6]; +static signed short *enc_dc_val_base, *enc_dc_val[3]; +static signed short *dec_dc_val_base, *dec_dc_val[3]; +static signed short old_enc_dc_val, old_dec_dc_val; + +static DWORD block_flags[64], block_flag; +//static int out_block[64]; + +#ifdef DUMP +DWORD block_idx[64], block_run[64]; +#endif + +static DIVX_SCAN_TABLE inter_scantable, intra_scantable; +static DIVX_SCAN_TABLE intra_h_scantable, intra_v_scantable; + +static LONGLONG pts; +static int last_time_div, time_incr_res; + +static DIVX_VLC mb_intra_vlc; +static DIVX_VLC mb_non_intra_vlc; +static DIVX_VLC dc_lum_vlc[2]; +static DIVX_VLC dc_chroma_vlc[2]; +static BYTE uni_DCtab_lum_len[512]; +static BYTE uni_DCtab_chrom_len[512]; +static WORD uni_DCtab_lum_bits[512]; +static WORD uni_DCtab_chrom_bits[512]; + +static DIVX_BITS_LEN *bits_len_tab_cur[3], *bits_len[9]; +static DWORD *bits_len_tab2_cur[3], *bits_len2[9]; + +static DWORD *level_run_tab_cur[3], *level_run[6]; +static DWORD *level_run_tab2_cur[3], *level_run2[6]; + +static DWORD *uni_table[2]; + +/* +#include +void DUMP_FRAME(char *text, ...) +{ + if (frame_number == 772) + { + static FILE *fp = NULL; + if (fp == NULL) + fp = fopen("out.log", "wb"); + va_list args; + va_start(args, text); + vfprintf(fp, text, args); + va_end(args); + //fclose(fp); + } +} +*/ +//#include +//#define DUMP_FRAME gui_update();while(fip_read_button(TRUE) != FIP_KEY_ENTER);msg + + +//////////////////////////////////////////////////// + +STATIC void divx_init_scantable(DIVX_SCAN_TABLE *st, const BYTE *src_scantable) +{ + DWORD i; + int end; + st->scantable = src_scantable; + + for (i = 0; i < 64; i++) + { + st->permutated[i] = src_scantable[i]; + st->inv_permutated[src_scantable[i]] = (BYTE)i; + } + + end = -1; + for (i = 0; i < 64; i++) + { + int j = st->permutated[i]; + if (j > end) + end = j; + st->raster_end[i] = (BYTE)end; + } +} + +static int divx_alloc_vlc_table(DIVX_VLC *vlc, int size) +{ + int index; + index = vlc->table_size; + vlc->table_size += size; + if (vlc->table_size > vlc->table_allocated) + { + vlc->table_allocated += (1 << vlc->bits); + vlc->table = (signed short (*)[2])SPrealloc(vlc->table, + sizeof(signed short) * 2 * vlc->table_allocated); + if (!vlc->table) + return -1; + } + return index; +} + +STATIC INLINE DWORD divx_get_vlc_data(const void *table, int i, int wrap, int size) +{ + const BYTE *ptr = (const BYTE *)table + i * wrap; + switch(size) + { + case 1: + return *(const BYTE *)ptr; + case 2: + return *(const WORD *)ptr; + default: + return *(const DWORD *)ptr; + } +} + +static int divx_build_vlc_table(DIVX_VLC *vlc, int table_nb_bits, + int nb_codes, + const void *bits, int bits_wrap, int bits_size, + const void *codes, int codes_wrap, int codes_size, + DWORD code_prefix, int n_prefix) +{ + int i, j, k, n, table_size, table_index, nb, n1, index; + DWORD code; + signed short (*table)[2]; + + table_size = 1 << table_nb_bits; + table_index = divx_alloc_vlc_table(vlc, table_size); + if (table_index < 0) + return -1; + table = &vlc->table[table_index]; + + for (i = 0; i < table_size; i++) + { + table[i][1] = 0; //bits + table[i][0] = -1; //codes + } + + // first pass: map codes and compute auxillary table sizes + for (i = 0; i < nb_codes; i++) + { + n = divx_get_vlc_data(bits, i, bits_wrap, bits_size); + code = divx_get_vlc_data(codes, i, codes_wrap, codes_size); + // we accept tables with holes + if (n <= 0) + continue; + // if code matches the prefix, it is in the table + n -= n_prefix; + if (n > 0 && (code >> n) == code_prefix) + { + if (n <= table_nb_bits) + { + // no need to add another table + j = (code << (table_nb_bits - n)) & (table_size - 1); + nb = 1 << (table_nb_bits - n); + for (k = 0; k < nb; k++) + { + if (table[j][1] != 0) // bits + { + return -1; + } + table[j][1] = (signed short)n; //bits + table[j][0] = (signed short)i; //code + j++; + } + } else + { + n -= table_nb_bits; + j = (code >> n) & ((1 << table_nb_bits) - 1); + // compute table size + n1 = -table[j][1]; //bits + if (n > n1) + n1 = n; + table[j][1] = (signed short)-n1; //bits + } + } + } + + // second pass : fill auxiliary tables recursively + for (i = 0; i < table_size; i++) + { + n = table[i][1]; //bits + if (n < 0) + { + n = -n; + if (n > table_nb_bits) + { + n = table_nb_bits; + table[i][1] = (signed short)-n; //bits + } + index = divx_build_vlc_table(vlc, n, nb_codes, + bits, bits_wrap, bits_size, + codes, codes_wrap, codes_size, + (code_prefix << table_nb_bits) | i, + n_prefix + table_nb_bits); + if (index < 0) + return -1; + // note: realloc has been done, so reload tables + table = &vlc->table[table_index]; + table[i][0] = (signed short)index; //code + } + } + return table_index; +} + +static int divx_init_vlc(DIVX_VLC *vlc, int nb_bits, int nb_codes, + const void *bits, int bits_wrap, int bits_size, + const void *codes, int codes_wrap, int codes_size) +{ + vlc->bits = nb_bits; + vlc->table = NULL; + vlc->table_allocated = 0; + vlc->table_size = 0; + + if (divx_build_vlc_table(vlc, nb_bits, nb_codes, + bits, bits_wrap, bits_size, + codes, codes_wrap, codes_size, 0, 0) < 0) + { + SPSafeFree(vlc->table); + return -1; + } + return 0; +} + + +static int divx_init_rl(DIVX_RL_TABLE *rl) +{ + signed char max_level[DIVX_MAX_RUN + 1], max_run[DIVX_MAX_LEVEL + 1]; + BYTE index_run[DIVX_MAX_RUN + 1]; + int last, run, level, start, end, i; + + // compute max_level[], max_run[] and index_run[] + for (last = 0; last < 2; last++) + { + if (last == 0) + { + start = 0; + end = rl->last; + } else + { + start = rl->last; + end = rl->n; + } + + memset(max_level, 0, DIVX_MAX_RUN + 1); + memset(max_run, 0, DIVX_MAX_LEVEL + 1); + memset(index_run, rl->n, DIVX_MAX_RUN + 1); + for (i = start; i < end; i++) + { + run = rl->table_run[i]; + level = rl->table_level[i]; + if (index_run[run] == rl->n) + index_run[run] = (BYTE)i; + if (level > max_level[run]) + max_level[run] = (signed char)level; + if (run > max_run[level]) + max_run[level] = (signed char)run; + } + rl->max_level[last] = (signed char *)SPmalloc(DIVX_MAX_RUN + 1); + if (rl->max_level[last] == NULL) + return -1; + memcpy(rl->max_level[last], max_level, DIVX_MAX_RUN + 1); + + rl->max_run[last] = (signed char *)SPmalloc(DIVX_MAX_LEVEL + 1); + if (rl->max_run[last] == NULL) + return -1; + memcpy(rl->max_run[last], max_run, DIVX_MAX_LEVEL + 1); + + rl->index_run[last] = (BYTE *)SPmalloc(DIVX_MAX_RUN + 1); + if (rl->index_run[last] == NULL) + return -1; + memcpy(rl->index_run[last], index_run, DIVX_MAX_RUN + 1); + } + + return 0; +} + +static void divx_free_rl(DIVX_RL_TABLE *rl) +{ + SPSafeFree(rl->max_level[0]); + SPSafeFree(rl->max_level[1]); + SPSafeFree(rl->max_run[0]); + SPSafeFree(rl->max_run[1]); + SPSafeFree(rl->index_run[0]); + SPSafeFree(rl->index_run[1]); +} + +static int divx_build_ac_tables() +{ + DWORD i, k, data_mask[16]; + for (i = 0; i < 16; i++) + data_mask[i] = ((1 << i) - 1) << (16 - i); + for (i = 0; i < 9; i++) + { + DIVX_BITS_LEN *bits_len_tab = bits_len[i]; + DWORD *bits_len_tab2 = bits_len2[i]; + DWORD *uni_tbl = uni_table[i >= 6 ? 1 : 0]; + DWORD I = (i >= 6) ? i - 3 : i; + DWORD *level_run_tab = level_run[I]; + DWORD *level_run_tab2 = level_run2[I]; + + DWORD ext_idx = 0; + DWORD rt_idx = 4, j; + + int max_level[64*2], max_run[64*2]; + int num = rl_table[I].n, rl_last = rl_table[I].last; + + DWORD ext_next_k = 0, rt_next_k = 0; + DWORD ext_cur_j = 0; + BOOL ext_next_esc4 = FALSE; + int idx; + //const WORD (*vlc)[2] = rl_table[I].table_vlc; + + memset(max_level, 0, 64 * 2 * sizeof(int)); + memset(max_run, 0, 64 * 2 * sizeof(int)); + + for (idx = 0; idx < num; idx++) + { + // no need to add another table + int lev = rl_table[I].table_level[idx]; + int run = rl_table[I].table_run[idx]; + int last = (idx >= rl_last) ? 64 : 0; + + if (max_level[last + run] < lev) + max_level[last + run] = lev; + if (max_run[last + lev] < run) + max_run[last + lev] = run; +/* + DWORD nb = (15 - vlc[idx][1]); + DWORD j = vlc[idx][0] << (nb + 1); + for (int sign = 0; sign <= 1; sign++) + { + DWORD jj = j | (sign << nb); + for (int k = 1 << nb; k > 0; k--, jj++) + { + bits_len_tab[jj>>2].bits = (WORD)idx; + } + } +*/ + } + + for (j = 0; j < (1<<14); j++) + { +/* + short idx0 = bits_len_tab[j].bits; +*/ + for (k = 0; k < 4; k++) + { + DWORD data = (j << 2) | k; + + int idx, left = 16, len = 0; + BOOL found = FALSE, use_esc4 = FALSE, use_1 = FALSE; + DWORD total_num = 0, total_inlen = 0, total_outlen = 0, total_last = 0; + int first_level = 0; + DWORD first_len = 0, first_outlen = 0, first_run = 0, first_last = 0, first_data = 0; + DWORD out_data = 0, esc; + DWORD lrt; +/* + if (idx0 >= 0) + { + idx = idx0; + goto found; + } +*/ + do + { + found = FALSE; + len = 0; + for (idx = 0; idx < num; idx++) + { +/* +found: +*/ + const WORD *vlc = rl_table[I].table_vlc[idx]; + len = vlc[1]; + if (len + 1 > left) + continue; + if ((data & data_mask[len]) == (((DWORD)vlc[0]) << (16 - len))) + { + int level = rl_table[I].table_level[idx]; + int run = rl_table[I].table_run[idx]; + int last = (idx >= rl_table[I].last) ? 1 : 0; + register int index, outlen; + + len++; + + if (data & (1 << (16 - len))) + level = -level; + + index = last*128*64 + run*128 + level + 64; + outlen = uni_tbl[index] >> 24; + + left -= len; + data <<= len; + total_inlen += len; + + total_outlen += outlen; + out_data <<= outlen; + out_data |= uni_tbl[index] & 0xffffff; + + total_last = last; + + if (first_len == 0) + { + first_len = len; + first_outlen = outlen; + first_level = level; + first_run = run; + first_data = out_data; + first_last = last; + } + + total_num++; + + if (outlen > 22) + { + use_esc4 = TRUE; + break; + } + + if (total_num > 0 && (total_outlen > 15 || total_inlen > 14)) + { + use_1 = TRUE; + break; + } + + if (last) + break; + + + found = TRUE; + break; + } + } + } + while (found && total_outlen < 22); + + esc = 0; + if (!use_esc4 && total_num == 0) // search ESC codes + { + const WORD *vlc = rl_table[I].table_vlc[num]; + int len = vlc[1]; + if ((data & data_mask[len]) == (((DWORD)vlc[0]) << (16 - len))) + { + int esc_shift, esc123; + + len++; + esc_shift = 15 - len; + esc123 = (data >> esc_shift) & 3; + + if (esc123 == 0) + { + esc = 3; + len++; + } + else if (esc123 == 1) + { + esc = 2; + len++; + } + else + esc = 1; + first_len = len; + } + } + + // truncate too long seqs + if (use_1 || (total_num == 2 && total_outlen > 15 && first_outlen <= 15)) + { + total_outlen = first_outlen; + total_inlen = first_len; + total_num = 1; + total_last = first_last; + out_data = first_data; + } + + if (i < 6 && !esc) + { + DWORD add_level = max_level[first_last * 64 + (first_run & 63)]; + DWORD add_run = max_run[first_last * 64 + Abs(first_level)]; + + lrt = ((DWORD)first_level & 127) << 25; + lrt |= ((first_len - 1) & 0xf) << 18; + lrt |= (first_last & 1) << 17; + lrt |= (add_level & 31) << 12; + lrt |= (add_run & 63) << 6; + lrt |= (first_run & 63); + + if (first_len > 14) + { + if (k != rt_next_k) + rt_idx = (((rt_idx >> 2) + 1) << 2) | k; + + level_run_tab2[rt_idx] = lrt; + level_run_tab[j] = (rt_idx & ~3); + rt_idx++; + rt_next_k = (k + 1) & 3; + } + else + level_run_tab[j] = lrt; + } + + // esc1-4 + if (esc) + { + bits_len_tab[j].len = (BYTE)esc; + bits_len_tab[j].bits = (WORD)first_len; + level_run_tab[j] = 0; + } + // extended + else if (total_inlen > 14 || total_outlen > 15 || use_esc4) + { + // if one of four extended records has ESC4, then all others should too + + if (ext_cur_j == j) + { + if (!use_esc4 && ext_next_esc4) + use_esc4 = TRUE; + } + + if (k != ext_next_k) + ext_idx = (((ext_idx >> 2) + 1) << 2) | k; + bits_len_tab2[ext_idx] = ((DWORD)total_last << 31) | ((out_data & 0x3FFFFF) << 9) + | ((total_inlen - 1) << 5) | total_outlen; + bits_len_tab[j].len = use_esc4 ? (BYTE)4 : (BYTE)0; + bits_len_tab[j].bits = (WORD)(ext_idx & ~3); + ext_idx++; + ext_next_k = (k + 1) & 3; + + ext_next_esc4 = use_esc4; + ext_cur_j = j; + + } + // normal + else + { + bits_len_tab[j].len = (BYTE)(((total_outlen & 0xf) << 4) | (total_inlen & 0xf)); + bits_len_tab[j].bits = (WORD)(out_data | (total_last << 15)); + } + } + } + msg("DivX3: AC-init[%d]: ext_num=%d, rt_num=%d\n", i, ext_idx, rt_idx); + gui_update(); + } + + + return 0; +} + +static void divx_init_uni_dc_tab(void) +{ + int level, uni_code, uni_len; + + for (level = -256; level < 256; level++) + { + int size, v, l; + // find number of bits + size = 0; + v = Abs(level); + while (v) + { + v >>= 1; + size++; + } + + if (level < 0) + l = (-level) ^ ((1 << size) - 1); + else + l = level; + + // luminance + uni_code = DCtab_lum[size][0]; + uni_len = DCtab_lum[size][1]; + + if (size > 0) + { + uni_code <<= size; uni_code |= l; + uni_len += size; + if (size > 8) + { + uni_code <<= 1; uni_code |= 1; + uni_len++; + } + } + uni_DCtab_lum_bits[level + 256] = (WORD)uni_code; + uni_DCtab_lum_len [level + 256] = (BYTE)uni_len; + + // chrominance + uni_code = DCtab_chrom[size][0]; + uni_len = DCtab_chrom[size][1]; + + if (size > 0) + { + uni_code <<= size; uni_code |= l; + uni_len += size; + if (size > 8) + { + uni_code <<= 1; uni_code |= 1; + uni_len++; + } + } + uni_DCtab_chrom_bits[level + 256] = (WORD)uni_code; + uni_DCtab_chrom_len [level + 256] = (BYTE)uni_len; + } +} + +STATIC INLINE int divx_get_rl_index(const DIVX_RL_TABLE *rl, int last, int run, int level) +{ + DWORD index; + index = rl->index_run[last][run]; + if (index >= rl->n) + return rl->n; + if (level > rl->max_level[last][run]) + return rl->n; + return (int)index + level - 1; +} + +static void divx_init_uni_tabs() +{ + DWORD i; + for (i = 0; i < 2; i++) + { + DIVX_RL_TABLE *rl = &rl_table[i == 0 ? 2 : 5]; + DWORD *uni_tab = uni_table[i]; + int slevel, run, last; + + for (run = 0; run < 64; run++) + { + for (last = 0; last <= 1; last++) + { + const int index = ((last)*128*64 + (run)*128 + 64); + uni_tab[index] = 0; + } + } + + for (slevel = -64; slevel < 64; slevel++) + { + if (slevel == 0) + continue; + for (run = 0; run < 64; run++) + { + for (last = 0; last <= 1; last++) + { + const int index = ((last)*128*64 + (run)*128 + (slevel+64)); + int level = slevel < 0 ? -slevel : slevel; + int sign = slevel < 0 ? 1 : 0; + DWORD bits, len, code; + int level1, run1; + + DWORD out_len = 30; + DWORD out_bits = 0; + + // ESC0 + code = divx_get_rl_index(rl, last, run, level); + bits = rl->table_vlc[code][0]; + len = rl->table_vlc[code][1] + 1; + bits = bits * 2 + sign; + + if (code != rl->n && len < out_len) + { + out_bits = bits; out_len = len; + } + // ESC1 + bits = rl->table_vlc[rl->n][0]; + len = rl->table_vlc[rl->n][1] + 1; + bits = bits*2; + level1 = level - rl->max_level[last][run]; + if (level1 > 0) + { + code = divx_get_rl_index(rl, last, run, level1); + bits <<= rl->table_vlc[code][1]; + len += rl->table_vlc[code][1]; + bits += rl->table_vlc[code][0]; + bits = bits * 2 + sign; + len++; + + if (code != rl->n && len < out_len) + { + out_bits = bits; out_len = len; + } + } + // ESC2 + bits = rl->table_vlc[rl->n][0]; + len = rl->table_vlc[rl->n][1]; + bits = bits * 4 + 2; len+=2; + run1 = run - rl->max_run[last][level] - 1; + if (run1 >= 0) + { + code = divx_get_rl_index(rl, last, run1, level); + bits <<= rl->table_vlc[code][1]; + len += rl->table_vlc[code][1]; + bits += rl->table_vlc[code][0]; + bits = bits * 2 + sign; len++; + + if (code != rl->n && len < out_len) + { + out_bits = bits; out_len = len; + } + } + + uni_tab[index] = (out_len << 24) | (out_bits & 0xffffff); + } + } + } + } +} + +////////////////////////////////////////////////////////////// + +#define divx_get_vlc2(code, dec_v, dec_bitidx, dec_left, table) \ +{ \ + register DWORD index; \ + register int n; \ + \ + bitstream_show_bits(index, dec_v, dec_bitidx, 9); \ + code = table[index][0]; \ + n = table[index][1]; \ + \ + if (n < 0) \ + { \ + bitstream_skip_bits(0, dec_v, dec_bitidx, dec_left, 9); \ + bitstream_show_bits(index, dec_v, dec_bitidx, (-n)); \ + index += code; \ + code = table[index][0]; \ + n = table[index][1]; \ + } \ + bitstream_skip_bits(0, dec_v, dec_bitidx, dec_left, n); \ +} + +#define divx_get_vlc3(code, dec_v, dec_bitidx, dec_left, table) \ +{ \ + register DWORD index; \ + register int n; \ + \ + bitstream_show_bits(index, dec_v, dec_bitidx, 9); \ + code = table[index][0]; \ + n = table[index][1]; \ + \ + if (n < 0) \ + { \ + register int nb_bits = -n; \ + bitstream_skip_bits(0, dec_v, dec_bitidx, dec_left, 9); \ + \ + bitstream_show_bits(index, dec_v, dec_bitidx, nb_bits); \ + index += code; \ + code = table[index][0]; \ + n = table[index][1]; \ + \ + if (n < 0) \ + { \ + bitstream_skip_bits(0, dec_v, dec_bitidx, dec_left, nb_bits); \ + nb_bits = -n; \ + bitstream_show_bits(index, dec_v, dec_bitidx, nb_bits); \ + index += code; \ + code = table[index][0]; \ + n = table[index][1]; \ + } \ + } \ + bitstream_skip_bits(0, dec_v, dec_bitidx, dec_left, n); \ +} + +STATIC INLINE int divx_log2(unsigned int v) +{ + int n = 0; + if (v & 0xffff0000) + { + v >>= 16; + n += 16; + } + if (v & 0xff00) + { + v >>= 8; + n += 8; + } + n += divx_log2_tab[v]; + + return n; +} + +STATIC INLINE int divx_coded_block_pred(int n, BYTE **coded_block_ptr) +{ + int xy, wrap, pred, a, b, c; + + xy = block_index[n]; + wrap = b8_stride; + + a = coded_block[xy - 1 ]; + b = coded_block[xy - 1 - wrap]; + c = coded_block[xy - wrap]; + + if (b == c) + pred = a; + else + pred = c; + + *coded_block_ptr = &coded_block[xy]; + + return pred; +} + +STATIC INLINE int divx_pred_dc(int n, int level, int *dir_ptr, BOOL encoding) +{ + int a, b, c, wrap, pred, scale, ret; + WORD *dc_val; + + // find prediction + if (n < 4) + { + scale = encoding ? enc_y_dc_scale : dec_y_dc_scale; + } else + { + scale = encoding ? enc_c_dc_scale : dec_c_dc_scale; + } + + wrap = block_wrap[n]; + dc_val = (WORD *)((encoding ? enc_dc_val[0] : dec_dc_val[0]) + block_index[n]); + + /* B C + * A X + */ + a = dc_val[ - 1]; + b = dc_val[ - 1 - wrap]; + c = dc_val[ - wrap]; + + if (encoding) + { + if (Abs(a - b) < Abs(b - c)) + { + pred = c; + *dir_ptr = 1; // top + } else + { + pred = a; + *dir_ptr = 0; // left + } + + pred = -(pred + (scale >> 1)) / scale; + + ret = (level + pred); + level *= scale; + + old_enc_dc_val = dc_val[0]; + } else + { + if (scale == 8) + { + a = (a + (8 >> 1)) / 8; + b = (b + (8 >> 1)) / 8; + c = (c + (8 >> 1)) / 8; + } else + { + a = (a + (scale >> 1)) / scale; + b = (b + (scale >> 1)) / scale; + c = (c + (scale >> 1)) / scale; + } + + if (Abs(a - b) <= Abs(b - c)) + { + pred = c; + *dir_ptr = 1; // top + } else + { + pred = a; + *dir_ptr = 0; // left + } + + ret = (level + pred); + level = ret * scale; + + old_dec_dc_val = dc_val[0]; + } + + if (level & (~2047)) + { + if (level < 0) + level = 0; + } + + dc_val[0] = (WORD)level; + + return ret; +} + +#ifdef USE_AC_CORRECTION + +STATIC INLINE DWORD divx_pred_notcoded_ac(int block_i, int block[64], int dec_dc_pred_dir) +{ + int i = block_index[block_i] * 16; + int *ac = ac_val + i; + DWORD non_zero = 0; + + if (ac_pred) + { + if (dec_dc_pred_dir == 0) + { + int *a = ac - 16; + for(i = 1; i < 8; i++) + { +#ifdef DUMP +// DEBUG_MSG("block[%d] = %d\n", i<<3, *a); +#endif + + int level = *a++; + level = (level > 0) ? (level - qadd) / qmul : (level + qadd) / qmul; + block[i<<3] = level; + non_zero |= (DWORD)level; + } + } else + { + int *a = ac - 16 * block_wrap[block_i] + 8; + for(i = 1; i < 8; i++) + { +#ifdef DUMP +// DEBUG_MSG("block[%d] = %d\n", i, *a); +#endif + int level = *a++; + level = (level > 0) ? (level - qadd) / qmul : (level + qadd) / qmul; + block[i] = level; + non_zero |= (DWORD)level; + } + } + } + + int *a = ac, *a8 = a + 8; + for(i = 1; i < 8; i++) + { + int level = block[i<<3]; + level = (level > 0) ? level * qmul + qadd : level * qmul - qadd; + *a++ = level; + + level = block[i]; + level = (level > 0) ? level * qmul + qadd : level * qmul - qadd; + *a8++ = level; + } + + // non-zero if any coefs are non-zero + return non_zero != 0 ? 1 : 0; +} + +STATIC INLINE DWORD divx_pred_ac(int block_i, int *block, int dec_dc_pred_dir) +{ + int i = block_index[block_i] * 16; + int *ac = ac_val + i; + DWORD non_zero = 0; + + if (dec_dc_pred_dir == 0) + { + int *a = ac - 16; + for(i = 1; i < 8; i++) + { +#ifdef DUMP +// DEBUG_MSG("block[%d] = %d + %d\n", i<<3, block[i<<3], *a); +#endif + + int level = block[i<<3]; + level = (level > 0) ? level * qmul + qadd : level * qmul - qadd; + level += *a++; + level = (level > 0) ? (level - qadd) / qmul : (level + qadd) / qmul; + block[i<<3] = level; + non_zero |= (DWORD)level; + } + } else + { + int *a = ac - 16 * block_wrap[block_i] + 8; + for(i = 1; i < 8; i++) + { +#ifdef DUMP +// DEBUG_MSG("block[%d] = %d + %d\n", i, block[i], *a); +#endif + int level = block[i]; + level= (level > 0) ? level * qmul + qadd : level * qmul - qadd; + level += *a++; + level = (level > 0) ? (level - qadd) / qmul : (level + qadd) / qmul; + block[i] = level; + non_zero |= (DWORD)level; + } + } + + int *a = ac, *a8 = a + 8; + for(i = 1; i < 8; i++) + { + int level = block[i<<3]; + level = (level > 0) ? level * qmul + qadd : level * qmul - qadd; + *a++ = level; + + level = block[i]; + level = (level > 0) ? level * qmul + qadd : level * qmul - qadd; + *a8++ = level; + } + + // non-zero if any coefs are non-zero + return non_zero != 0 ? 1 : 0; +} + +STATIC INLINE void divx_pred_flaged_ac(int block_i, int *block, DWORD *block_f, DWORD block_flag, int dec_dc_pred_dir) +{ + int i = block_index[block_i] * 16; + int *ac = ac_val + i; + + int *a = ac, *a8 = a + 8; + for(i = 1; i < 8; i++) + { + if (block_f[i<<3] == block_flag) + { + int level = block[i<<3]; + level = (level > 0) ? level * qmul + qadd : level * qmul - qadd; + *a++ = level; + } + else + *a++ = 0; + if (block_f[i] == block_flag) + { + int level = block[i]; + level = (level > 0) ? level * qmul + qadd : level * qmul - qadd; + *a8++ = level; + } + else + *a8++ = 0; + } +} + +#endif + +#if 0 +STATIC INLINE int divx_put_rl_vlc(DWORD &enc_v, int &enc_bitidx, int &enc_len, int level, int last, int run, DWORD *bits_tab, BYTE *len_tab) +{ + register int index = last*128*64 + (run-1)*128 + level + 64; + if (index >= 0) + return bitstream_put_bits(len_tab[index], bits_tab[index], enc_v, enc_bitidx, enc_len); + return 0; +} +#endif + +#define divx_put_rl_vlc_esc3(enc_v, enc_bitidx, enc_len, level, last, run) \ +{ \ + DWORD d = ((3 << 23) + (3 << 21) + (1 << 13) + 1) + \ + (last << 20) + ((run - 1) << 14) + (((level) & 0xfff) << 1); \ + bitstream_put_bits((7+2+1+6+1+12+1), d, enc_v, enc_bitidx, enc_len); \ +} + +#define divx_encode_dc(enc_v, enc_bitidx, enc_len, level, n) \ +{ \ + level += 256; \ + if (n < 4) /* luminance */ \ + { \ + bitstream_put_bits(uni_DCtab_lum_len[level], uni_DCtab_lum_bits[level], enc_v, enc_bitidx, enc_len); \ + } else /* chrominance */ \ + { \ + bitstream_put_bits(uni_DCtab_chrom_len[level], uni_DCtab_chrom_bits[level], enc_v, enc_bitidx, enc_len); \ + } \ +} + +STATIC INLINE int divx_mid_pred(int a, int b, int c) +{ + if (a > b) + { + if (c > b) + b = (c > a) ? a : c; + } else + { + if (b > c) + b = (c > a) ? c : a; + } + return b; +} + +STATIC INLINE signed short *divx_pred_motion(int *px, int *py) +{ + int wrap; + signed short *A, *B, *C, (*mot_val)[2]; + + wrap = b8_stride; + mot_val = motion_val + block_index[0]; + + A = mot_val[ - 1]; + if (first_slice_line) + { + if (mb_x == 0) + { + *px = *py = 0; + } else + { + *px = A[0]; + *py = A[1]; + } + } else + { + B = mot_val[ - wrap]; + C = mot_val[2 - wrap]; + *px = divx_mid_pred(A[0], B[0], C[0]); + *py = divx_mid_pred(A[1], B[1], C[1]); + } + return *mot_val; +} + +#define divx_decode_motion(dec_v, dec_bitidx, dec_left, mx_ptr, my_ptr) \ +{ \ + DIVX_MV_TABLE *mv = &mv_tables[mv_table_index]; \ + \ + register int code; \ + int mx, my; \ + divx_get_vlc2(code, dec_v, dec_bitidx, dec_left, mv->vlc.table); \ + if (code < 0) \ + return -1; \ + if (code == mv->n) \ + { \ + register DWORD mxmy; \ + bitstream_get_bits(mxmy, 0, dec_v, dec_bitidx, dec_left, 12); \ + mx = mxmy >> 6; \ + my = mxmy & 63; \ + } else \ + { \ + mx = mv->table_mvx[code]; \ + my = mv->table_mvy[code]; \ + } \ + \ + mx += mx_ptr - 32; \ + my += my_ptr - 32; \ + /* \WARNING : they do not do exactly modulo encoding */ \ + if (mx <= -64) \ + mx += 64; \ + else if (mx >= 64) \ + mx -= 64; \ + \ + if (my <= -64) \ + my += 64; \ + else if (my >= 64) \ + my -= 64; \ + \ + mx_ptr = mx; \ + my_ptr = my; \ +} + +#define divx_encode_motion(enc_v, enc_bitidx, enc_len, val, pred, fc) \ +{ \ + register int range, l, bit_size, sign, code, bits; \ + \ + int local_val = val - pred; \ + if (local_val == 0) \ + { \ + code = 0; \ + /*DUMP_FRAME("MOTION ZERO code = %d\n", code);*/ \ + bitstream_put_bits(mvtab[code][1], mvtab[code][0], enc_v, enc_bitidx, enc_len); \ + } else \ + { \ + DWORD d; \ + bit_size = fc - 1; \ + range = 1 << bit_size; \ + /* modulo encoding (not divx3-like!) */ \ + l = range * 32; \ + local_val += l; \ + local_val &= 2*l-1; \ + local_val -= l; \ + sign = local_val >> 31; \ + local_val = (local_val^sign) - sign; \ + sign &= 1; \ + local_val--; \ + code = (local_val >> bit_size) + 1; \ + bits = local_val & (range - 1); \ + \ + /*DUMP_FRAME("MOTION code = %d sign=%d bits=%d bs=%d\n", code, sign, bits, bit_size);*/ \ + \ + d = ((mvtab[code][0] << 1) | sign); \ + bitstream_put_bits(mvtab[code][1] + 1, d, enc_v, enc_bitidx, enc_len); \ + if (bit_size > 0) \ + { \ + bitstream_put_bits(bit_size, bits, enc_v, enc_bitidx, enc_len); \ + } \ + } \ +} + +STATIC INLINE void divx_set_block_index() +{ + block_index[0] = block_index_tbl[mb_y][0]; + block_index[1] = block_index_tbl[mb_y][1]; + block_index[2] = block_index_tbl[mb_y][2]; + block_index[3] = block_index_tbl[mb_y][3]; + block_index[4] = block_index_tbl[mb_y][4]; + block_index[5] = block_index_tbl[mb_y][5]; +} + +STATIC INLINE void divx_update_block_index() +{ + block_index[0] += 2; + block_index[1] += 2; + block_index[2] += 2; + block_index[3] += 2; + block_index[4]++; + block_index[5]++; +} + +STATIC INLINE void divx_update_motion_val() +{ + register int xy = block_index[0]; + register int wrap = b8_stride; + + //DUMP_FRAME("--update_motion_val xy=%d = %d,%d\n", xy, motion_x, motion_y); + + register signed short smx = (signed short)motion_x; + register signed short smy = (signed short)motion_y; + motion_val[xy][0] = smx; + motion_val[xy][1] = smy; + motion_val[xy + 1][0] = smx; + motion_val[xy + 1][1] = smy; + motion_val[xy + wrap][0] = smx; + motion_val[xy + wrap][1] = smy; + motion_val[xy + 1 + wrap][0] = smx; + motion_val[xy + 1 + wrap][1] = smy; +} + +STATIC INLINE void divx_clean_intra_table_entries() +{ + register int wrap = b8_stride; + register int xy = block_index[0]; + register int xy1 = xy + 1, xywrap = xy + wrap, xy1wrap = xy + 1 + wrap; + + coded_block[xy] = coded_block[xy1] = coded_block[xywrap] = coded_block[xy1wrap] = 0; + + dec_dc_val[0][xy] = dec_dc_val[0][xy1] = dec_dc_val[0][xywrap] = dec_dc_val[0][xy1wrap] = 1024; + dec_dc_val[0][xy] = dec_dc_val[0][xy1] = dec_dc_val[0][xywrap] = dec_dc_val[0][xy1wrap] = 1024; + + enc_dc_val[0][xy] = enc_dc_val[0][xy1] = enc_dc_val[0][xywrap] = enc_dc_val[0][xy1wrap] = 1024; + enc_dc_val[0][xy] = enc_dc_val[0][xy1] = enc_dc_val[0][xywrap] = enc_dc_val[0][xy1wrap] = 1024; + + wrap = mb_stride; + xy = mb_x + mb_y * wrap; + dec_dc_val[1][xy] = dec_dc_val[2][xy] = 1024; + enc_dc_val[1][xy] = enc_dc_val[2][xy] = 1024; +} + +///////////////////////////////////////////////////////////////// + +#define divx_encode_mb_header(enc_v, enc_bitidx, enc_len, param_cbp, skip, ac_pred) \ +{ \ + DWORD cbpc = param_cbp & 3; \ + DWORD cbpy = param_cbp >> 2; \ + if (pict_type == DIVX_I_TYPE) \ + { \ + bitstream_put_bits(intra_MCBPC_bits[cbpc], intra_MCBPC_code[cbpc], enc_v, enc_bitidx, enc_len); \ + \ + if (mb_intra) \ + { \ + bitstream_put_bits(1, ac_pred, enc_v, enc_bitidx, enc_len); \ + } \ + else \ + cbpy ^= 0x0F; \ + /*DUMP_FRAME("cbpc=%d cbpy=%d\n", cbpc, cbpy);*/ \ + bitstream_put_bits(cbpy_tab[cbpy][1], cbpy_tab[cbpy][0], enc_v, enc_bitidx, enc_len); \ + } else \ + { \ + bitstream_put_bits(1, (skip ? 1 : 0), enc_v, enc_bitidx, enc_len); \ + if (!skip) \ + { \ + if (mb_intra) \ + cbpc |= 4; \ + bitstream_put_bits(inter_MCBPC_bits[cbpc], inter_MCBPC_code[cbpc], enc_v, enc_bitidx, enc_len); \ + \ + if (mb_intra) \ + { \ + bitstream_put_bits(1, ac_pred, enc_v, enc_bitidx, enc_len); \ + } \ + else \ + cbpy ^= 0x0F; \ + /*DUMP_FRAME("cbpc=%d cbpy=%d\n", cbpc, cbpy);*/ \ + bitstream_put_bits(cbpy_tab[cbpy][1], cbpy_tab[cbpy][0], enc_v, enc_bitidx, enc_len); \ + } \ + } \ +} + +#define divx_decode_picture_header(dec_v, dec_bitidx, dec_left) \ +{ \ + bitstream_get_bits(pict_type, 0, dec_v, dec_bitidx, dec_left, 2); \ + pict_type += 1; \ + if (pict_type != DIVX_I_TYPE && pict_type != DIVX_P_TYPE) \ + return -1; \ + bitstream_get_bits(qscale, 0, dec_v, dec_bitidx, dec_left, 5); \ + if (qscale == 0) \ + return -1; \ + chroma_qscale = chroma_qscale_table[qscale]; \ + enc_y_dc_scale = enc_y_dc_scale_table[qscale]; \ + enc_c_dc_scale = enc_c_dc_scale_table[chroma_qscale]; \ + dec_y_dc_scale = dec_y_dc_scale_table[qscale]; \ + dec_c_dc_scale = dec_c_dc_scale_table[chroma_qscale]; \ + \ + if (pict_type == DIVX_I_TYPE) \ + { \ + DWORD code; \ + bitstream_get_bits(code, 0, dec_v, dec_bitidx, dec_left, 5); \ + if (code < 0x17) \ + return -1; \ + \ + slice_height = mb_height / (code - 0x16); \ + \ + bitstream_get_bits012(rl_chroma_table_index, 0, dec_v, dec_bitidx, dec_left); \ + bitstream_get_bits012(rl_table_index, 0, dec_v, dec_bitidx, dec_left); \ + bitstream_get_bits1(dc_table_index, 0, dec_v, dec_bitidx, dec_left); \ + no_rounding = 1; \ + } else \ + { \ + bitstream_get_bits1(use_skip_mb_code, 0, dec_v, dec_bitidx, dec_left); \ + bitstream_get_bits012(rl_table_index, 0, dec_v, dec_bitidx, dec_left); \ + rl_chroma_table_index = rl_table_index; \ + bitstream_get_bits1(dc_table_index, 0, dec_v, dec_bitidx, dec_left); \ + bitstream_get_bits1(mv_table_index, 0, dec_v, dec_bitidx, dec_left); \ + \ + if(flipflop_rounding) \ + no_rounding ^= 1; \ + else \ + no_rounding = 0; \ + } \ +} + +#define divx_encode_vos_header(enc_v, enc_bitidx, enc_len) \ +{ \ + bitstream_put_bits(32, 0x000001B0, enc_v, enc_bitidx, enc_len); \ + bitstream_put_bits(8, 0xf5, enc_v, enc_bitidx, enc_len); /* profile: f5=??? 01=MPEG4@SL1 */ \ +} + +#define divx_encode_vol_header(enc_v, enc_bitidx, enc_len) \ +{ \ + const int vo_number = 0; \ + const int vol_number = 0; \ + const int vo_ver_id = 1; \ + const int vo_type = 1; \ + const int aspect_ratio_info = 1; \ + \ + bitstream_put_bits(32, 0x100 + vo_number, enc_v, enc_bitidx, enc_len); \ + bitstream_put_bits(32, 0x120 + vol_number, enc_v, enc_bitidx, enc_len); \ + \ + bitstream_put_bits(1, 0, enc_v, enc_bitidx, enc_len); \ + bitstream_put_bits(8, vo_type, enc_v, enc_bitidx, enc_len); \ + bitstream_put_bits(8, (1<<7)|(vo_ver_id<<3)|1, enc_v, enc_bitidx, enc_len); \ + \ + bitstream_put_bits(4, aspect_ratio_info, enc_v, enc_bitidx, enc_len); \ + bitstream_put_bits(8, 0xB1, enc_v, enc_bitidx, enc_len); /*10110001b*/ \ + bitstream_put_bits(16, time_incr_res, enc_v, enc_bitidx, enc_len); \ + bitstream_put_bits(3, 5, enc_v, enc_bitidx, enc_len); /* 101b */ \ + bitstream_put_bits(13, width, enc_v, enc_bitidx, enc_len); \ + bitstream_put_bits(1, 1, enc_v, enc_bitidx, enc_len); \ + bitstream_put_bits(13, height, enc_v, enc_bitidx, enc_len); \ + bitstream_put_bits(1, 1, enc_v, enc_bitidx, enc_len); \ + bitstream_put_bits(1, 0 /*progressive=1*/, enc_v, enc_bitidx, enc_len); \ + \ + bitstream_put_bits(8, 0x8C, enc_v, enc_bitidx, enc_len); /*10001100b*/ \ + \ + bitstream_put_bits(3, 3, enc_v, enc_bitidx, enc_len); \ +} + +#if 0 +static int divx_encode_gop_header(DWORD &enc_v, int &enc_bitidx, int &enc_len) +{ + bitstream_put_bits(32, 0x000001B3, enc_v, enc_bitidx, enc_len); + + LONGLONG time= pts; + const int AV_TIME_BASE = 1000000; + time= (time * time_incr_res + AV_TIME_BASE/2) / AV_TIME_BASE; + + int seconds = (int)(time / time_incr_res); + int minutes = seconds / 60; seconds %= 60; + int hours = minutes/60; minutes %= 60; hours%=24; + + bitstream_put_bits(5, hours, enc_v, enc_bitidx, enc_len); + bitstream_put_bits(6, minutes, enc_v, enc_bitidx, enc_len); + bitstream_put_bits(1, 1, enc_v, enc_bitidx, enc_len); + bitstream_put_bits(6, seconds, enc_v, enc_bitidx, enc_len); + + return bitstream_put_bits(6, 7, enc_v, enc_bitidx, enc_len); +} +#endif + + +#define divx_encode_picture_header(enc_v, enc_bitidx, enc_len) \ +{ \ + /* VOP header */ \ + int time_increment_bits; \ + int time_div = 0, time_mod = 0, time_incr; \ + \ + bitstream_put_bits(32, 0x000001b6, enc_v, enc_bitidx, enc_len); \ + bitstream_put_bits(2, pict_type - 1, enc_v, enc_bitidx, enc_len); \ + \ + time_increment_bits = time_incr_res != 0 ? divx_log2(time_incr_res - 1) + 1 : 4; \ + \ + if (time_incr_res > 0) \ + { \ + time_div = (int)(pts / time_incr_res); \ + time_mod = (int)(pts % time_incr_res); \ + } \ + time_incr = time_div - last_time_div; \ + last_time_div = time_div; \ + \ + while (time_incr--) \ + { \ + bitstream_put_bits(1, 1, enc_v, enc_bitidx, enc_len); \ + } \ + bitstream_put_bits(2, 1, enc_v, enc_bitidx, enc_len); \ + bitstream_put_bits(time_increment_bits, time_mod, enc_v, enc_bitidx, enc_len); \ + bitstream_put_bits(2, 3, enc_v, enc_bitidx, enc_len); \ + if (pict_type == DIVX_P_TYPE) \ + { \ + bitstream_put_bits(1, no_rounding, enc_v, enc_bitidx, enc_len); \ + } \ + \ + bitstream_put_bits(8, qscale & 31, enc_v, enc_bitidx, enc_len); \ + \ + if (pict_type != DIVX_I_TYPE) \ + { \ + bitstream_put_bits(3, f_code, enc_v, enc_bitidx, enc_len); \ + } \ +} + +///////////////////////////////////////////////////////////////// + +int divx_transcode_preinit() +{ + int i; + static const int bits_len2_size[9] = { 600, 800, 10, 1100, 600, 1700, 300, 500, 10 }; + static const int level_run2_size[6] = { 6, 130, 6, 100, 140, 6 }; + + uni_table[0] = (DWORD *)SPmalloc(128*64*2 * sizeof(DWORD)); + uni_table[1] = (DWORD *)SPmalloc(128*64*2 * sizeof(DWORD)); + if (uni_table[0] == NULL || uni_table[1] == NULL) + return -1; + + for (i = 0; i < 9; i++) + { + bits_len[i] = (DIVX_BITS_LEN *)SPmalloc(16484 * sizeof(DIVX_BITS_LEN)); + if (bits_len[i] == NULL) + return -1; + //memset(bits_len[i], 0xff, 16484 * sizeof(DIVX_BITS_LEN)); + bits_len2[i] = (DWORD *)SPmalloc(bits_len2_size[i] * sizeof(DWORD)); + if (bits_len2[i] == NULL) + return -1; + } + + for (i = 0; i < 6; i++) + { + level_run[i] = (DWORD *)SPmalloc(16484 * sizeof(DWORD)); + if (level_run[i] == NULL) + return -1; + level_run2[i] = (DWORD *)SPmalloc(level_run2_size[i] * sizeof(DWORD)); + if (level_run2[i] == NULL) + return -1; + } + + if (divx_init_rl(&rl_table[2]) < 0) + return -1; + if (divx_init_rl(&rl_table[5]) < 0) + return -1; + + return 0; +} + +int divx_transcode_predeinit() +{ + int i; + divx_free_rl(&rl_table[5]); + divx_free_rl(&rl_table[2]); + + SPSafeFree(uni_table[0]); + SPSafeFree(uni_table[1]); + + for (i = 0; i < 6; i++) + { + SPSafeFree(level_run[i]); + SPSafeFree(level_run2[i]); + } + for (i = 0; i < 9; i++) + { + SPSafeFree(bits_len[i]); + SPSafeFree(bits_len2[i]); + } + + return 0; +} + + +int divx_transcode_init(int w, int h, int t) +{ + int i, y_size, c_size, yc_size, b8_array_size; + width = w; + height = h; + mb_width = (width + 15) / 16; + mb_height = (height + 15) / 16; + mb_num = mb_width * mb_height; + no_rounding = 0; + flipflop_rounding = 1; + + b8_stride = mb_width * 2 + 1; + mb_stride = mb_width + 1; + y_size = b8_stride * (2 * mb_height + 1); + c_size = mb_stride * (mb_height + 1); + yc_size = y_size + 2 * c_size; + b8_array_size = b8_stride * mb_height * 2; + + enc_dc_val_base = (signed short *)SPcalloc(yc_size * sizeof(signed short)); + dec_dc_val_base = (signed short *)SPcalloc(yc_size * sizeof(signed short)); + coded_block_base = (BYTE *)SPcalloc(y_size); + motion_val_base = (signed short (*)[2])SPcalloc(2 * (b8_array_size + 2) * sizeof(signed short)); + block_index_tbl = (int (*)[6])SPmalloc(6 * mb_height * sizeof(int)); + + if (enc_dc_val_base == NULL || dec_dc_val_base == NULL || + coded_block_base == NULL || motion_val_base == NULL || block_index_tbl == NULL) + { + return -1; + } + +#ifdef USE_AC_CORRECTION + ac_size = yc_size * sizeof(int) * 16; + ac_val_base = (int *)SPcalloc(ac_size); + if (ac_val_base == NULL) + return -1; + ac_val = ac_val_base + (b8_stride + 1) * 16; +#endif + + block_wrap[0] = block_wrap[1] = block_wrap[2] = block_wrap[3] = b8_stride; + block_wrap[4] = block_wrap[5] = mb_stride; + + enc_dc_val[0] = enc_dc_val_base + b8_stride + 1; + enc_dc_val[1] = enc_dc_val_base + y_size + mb_stride + 1; + enc_dc_val[2] = enc_dc_val[1] + c_size; + dec_dc_val[0] = dec_dc_val_base + b8_stride + 1; + dec_dc_val[1] = dec_dc_val_base + y_size + mb_stride + 1; + dec_dc_val[2] = dec_dc_val[1] + c_size; + for (i = 0; i < yc_size; i++) + { + enc_dc_val_base[i] = 1024; + dec_dc_val_base[i] = 1024; + } + + for (i = 0; i < mb_height; i++) + { + block_index_tbl[i][0] = b8_stride * (i*2 ) - 2; + block_index_tbl[i][1] = b8_stride * (i*2 ) - 1; + block_index_tbl[i][2] = b8_stride * (i*2 + 1) - 2; + block_index_tbl[i][3] = b8_stride * (i*2 + 1) - 1; + block_index_tbl[i][4] = mb_stride * (i + 1) + b8_stride * mb_height*2 - 1; + block_index_tbl[i][5] = mb_stride * (i + mb_height + 2) + b8_stride * mb_height*2 - 1; + } + + coded_block = coded_block_base + b8_stride + 1; + + mb_x = mb_y = 0; + ac_pred = 0; + f_code = 2; + + dec_y_dc_scale_table = old_y_dc_scale_table; + dec_c_dc_scale_table = old_c_dc_scale_table; + enc_y_dc_scale_table = mpeg4_y_dc_scale_table; + enc_c_dc_scale_table = mpeg4_c_dc_scale_table; + chroma_qscale_table = default_chroma_qscale_table; + + if (divx_init_vlc(&mb_intra_vlc, 9, 64, &table_mb_intra[0][1], 4, 2, &table_mb_intra[0][0], 4, 2) < 0) + return -1; + + if (divx_init_vlc(&mb_non_intra_vlc, 9, 128, &table_mb_non_intra[0][1], 8, 4, &table_mb_non_intra[0][0], 8, 4) < 0) + return -1; + + for (i = 0; i < 2; i++) + { + DIVX_MV_TABLE *mv = &mv_tables[i]; + if (divx_init_vlc(&mv->vlc, 9, mv->n + 1, mv->table_mv_bits, 1, + 1, mv->table_mv_code, 2, 2) < 0) + return -1; + } + + if (divx_init_vlc(&dc_lum_vlc[0], 9, 120, &table0_dc_lum[0][1], 8, 4, &table0_dc_lum[0][0], 8, 4) < 0) + return -1; + if (divx_init_vlc(&dc_chroma_vlc[0], 9, 120, &table0_dc_chroma[0][1], 8, 4, &table0_dc_chroma[0][0], 8, 4) < 0) + return -1; + if (divx_init_vlc(&dc_lum_vlc[1], 9, 120, &table1_dc_lum[0][1], 8, 4, &table1_dc_lum[0][0], 8, 4) < 0) + return -1; + if (divx_init_vlc(&dc_chroma_vlc[1], 9, 120, &table1_dc_chroma[0][1], 8, 4, &table1_dc_chroma[0][0], 8, 4) < 0) + return -1; + + divx_init_scantable(&inter_scantable , zigzag_direct); + divx_init_scantable(&intra_scantable , zigzag_direct); + divx_init_scantable(&intra_h_scantable, alternate_horizontal_scan); + divx_init_scantable(&intra_v_scantable, alternate_vertical_scan); + + pts = 0; + time_incr_res = t; + last_time_div = 0; + + divx_init_uni_dc_tab(); + + divx_init_uni_tabs(); + divx_build_ac_tables(); + + // be careful, don't use uni_table! + SPSafeFree(uni_table[0]); + SPSafeFree(uni_table[1]); + + motion_val = motion_val_base + 2; + + block_flag = 1; + memset(block_flags, 0, sizeof(DWORD) * 64); + + frame_number = 0; + return 0; +} + +int divx_transcode_deinit() +{ + SPSafeFree(dc_lum_vlc[0].table); + SPSafeFree(dc_chroma_vlc[0].table); + SPSafeFree(dc_lum_vlc[1].table); + SPSafeFree(dc_chroma_vlc[1].table); + + SPSafeFree(mv_tables[0].vlc.table); + SPSafeFree(mv_tables[1].vlc.table); + + SPSafeFree(mb_intra_vlc.table); + SPSafeFree(mb_non_intra_vlc.table); + +#ifdef USE_AC_CORRECTION + SPSafeFree(ac_val_base); +#endif + + SPSafeFree(block_index_tbl); + SPSafeFree(motion_val_base); + SPSafeFree(coded_block_base); + SPSafeFree(dec_dc_val_base); + SPSafeFree(enc_dc_val_base); + + return 0; +} + +BOOL divx_is_key_frame() +{ + return pict_type == DIVX_I_TYPE; +} + +////////////////////////////////////////////////////// +#ifdef USE_AC_CORRECTION + +int divx_transcode_acpred_mb(DWORD &enc_v, int &ebitidx, int &enc_len, + DWORD &dec_v, int &bitidx, int &left, DWORD cbp) +{ + int blocks[6][64], *block; + int dc_levels[6]; + DWORD block_i; + DWORD block_coded[6]; + DWORD out_cbp = cbp & (~63); + + /// \TODO: move to the frame level + + for (block_i = 0; block_i < 6; block_i++) + { + int ac_i, run_diff; + int *dec_scan_table; + int dec_dc_pred_dir; + + DIVX_BITS_LEN *bits_len_tab; + DWORD *level_run_tab, *level_run_tab2; + DWORD *bits_len_tab2, *uni_tbl; + + int coded = (cbp >> (5 - block_i)) & 1; + int dc_level; + + register int level; + register DWORD run; + + run_diff = 0; + block = blocks[block_i]; + memset(block, 0, sizeof(int) * 64); + + // decode dc + if (block_i < 4) + divx_get_vlc3(level, dec_v, bitidx, left, dc_lum_vlc[dc_table_index].table); + else + divx_get_vlc3(level, dec_v, bitidx, left, dc_chroma_vlc[dc_table_index].table); + + if (level < 0) + return -1; + if (level == 119) + { + register DWORD ret; + bitstream_get_bits(level, 0, dec_v, bitidx, left, 8); + bitstream_get_bits1(ret, 0, dec_v, bitidx, left) + if (ret) + level = -level; + } + else if (level != 0) + { + register DWORD ret; + bitstream_get_bits1(ret, 0, dec_v, bitidx, left) + if (ret) + level = -level; + } + + dc_level = divx_pred_dc(block_i, level, &dec_dc_pred_dir, FALSE); + dc_levels[block_i] = dc_level; + dec_scan_table = (dec_dc_pred_dir == 0) ? + intra_v_scantable.permutated : intra_h_scantable.permutated; + +#ifdef DUMP +// DEBUG_MSG("-----------------\n"); +// DEBUG_MSG("{%d}: DC LEVEL=%d / INTRA=%d\n", block_i, dc_level, mb_intra); +#endif + + if (!coded) + { + DWORD coded = divx_pred_notcoded_ac(block_i, block, dec_dc_pred_dir); + out_cbp |= coded << (5 - block_i); + block_coded[block_i] = coded; + continue; + } + + ac_i = 0; +#ifdef DUMP + //DEBUG_MSG(" DIR: dec=%d, enc=%d\n", dec_dc_pred_dir, enc_dc_pred_dir); +#endif + + DWORD tbl_idx = block_i >> 2; + bits_len_tab = bits_len_tab_cur[tbl_idx]; + bits_len_tab2 = bits_len_tab2_cur[tbl_idx]; + uni_tbl = uni_table[0]; + level_run_tab = level_run_tab_cur[tbl_idx]; + level_run_tab2 = level_run_tab2_cur[tbl_idx]; + + while (ac_i < 64) + { + register DWORD idx, bits, len; + bitstream_show_bits(idx, dec_v, bitidx, 16); + + bits = level_run_tab[idx>>2]; + + if (bits == 0) // esc123 + { + bits = bits_len_tab[idx>>2].bits; + len = bits_len_tab[idx>>2].len; + bitstream_skip_bits(0, dec_v, bitidx, left, bits); + + if (len == 3) // esc3 + { + bitstream_get_bits(bits, 0, dec_v, bitidx, left, (1+6+8)); + level = (bits & 0x80) ? ((bits & 0xff) | 0xffffff00) : (bits & 0x7f); + run = ((bits >> 8) & 63) + 1; + if (bits & 0x4000) + break; + } + else // esc1,esc2 + { + BOOL esc1 = (len & 1); + bitstream_show_bits(idx, dec_v, bitidx, 16); + bits = level_run_tab[idx>>2]; + if ((bits >> 16) == 0) + bits = level_run_tab2[bits | (idx & 3)]; + len = ((bits >> 18) & 0xf) + 1; + bitstream_skip_bits(0, dec_v, bitidx, left, len); + + level = ((int)bits) >> 25; + run = (bits & 63) + 1; + + if (esc1) + { + level = level > 0 ? level + ((bits >> 12) & 31) * qmul : level - ((bits >> 12) & 31) * qmul; + } else + { + run += (bits >> 6) & 63; + run += run_diff; + } + + if (bits & (1 << 17)) + break; + } + } else + { + if ((bits >> 16) == 0) + bits = level_run_tab2[bits | (idx & 3)]; + len = ((bits >> 18) & 0xf) + 1; + bitstream_skip_bits(0, dec_v, bitidx, left, len); + level = ((int)bits) >> 25; + + run = (bits & 63) + 1; + if (bits & (1 << 17)) + break; + } + + ac_i += run; + idx = dec_scan_table[ac_i]; + block[idx] = level; +#ifdef DUMP + //DEBUG_MSG("-> level=%d, run=%d\n", level, run); + block_idx[idx] = ac_i; + block_run[idx] = run; +#endif + } + + + ac_i += run; + +#ifdef DUMP + //DEBUG_MSG("-> level=%d, run=%d\n", level, run); + block_idx[dec_scan_table[ac_i]] = ac_i; + block_run[dec_scan_table[ac_i]] = run; +#endif + + run = dec_scan_table[ac_i]; + block[run] = level; + + // we have reconstructed block at this moment... + coded = divx_pred_ac(block_i, block, dec_dc_pred_dir); + out_cbp |= coded << (5 - block_i); + block_coded[block_i] = coded; + } + + ////////////////////////////////////////////////////////////////////////////// + /// NOW ENCODE!!! + + divx_encode_mb_header(enc_v, ebitidx, enc_len, out_cbp, FALSE, 0); + + for (block_i = 0; block_i < 6; block_i++) + { + register int level; + register DWORD run, i; + int last_non_zero = 0; + int *enc_scan_table; + int enc_dc_pred_dir; + + block = blocks[block_i]; + +#ifdef DUMP + DEBUG_MSG("-----------------\n"); + DEBUG_MSG("{%d}: DC LEVEL=%d / INTRA=%d\n", block_i, dc_levels[block_i], mb_intra); + + for (i = 0; i < 64; i++) + { + if (block[i] != 0) + { + //DEBUG_MSG("[%d] = %d (%d,run=%d)\n", i, block[i], block_idx[i], block_run[i]); + DEBUG_MSG("[%d] = %d\n", i, block[i]); + } + } +#endif + + level = divx_pred_dc(block_i, dc_levels[block_i], &enc_dc_pred_dir, TRUE); + divx_encode_dc(enc_v, ebitidx, enc_len, level, block_i); + + if (!block_coded[block_i]) + continue; + + enc_scan_table = (enc_dc_pred_dir == 0) ? + intra_v_scantable.permutated : intra_h_scantable.permutated; + + // now output the table + level = 0; + run = 0; + for (i = 0; i < 64; i++) + { + register DWORD eidx = enc_scan_table[i]; + + if (block[eidx] != 0) + { + if (level && (int)run > last_non_zero) + { + divx_put_rl_vlc_esc3(enc_v, ebitidx, enc_len, level, 0, run - last_non_zero); + last_non_zero = run; + } + + level = block[eidx]; + run = i; + } + } + + divx_put_rl_vlc_esc3(enc_v, ebitidx, enc_len, level, 1, run - last_non_zero); + } + + return 0; +} + +#endif + +////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////// + +int divx_transcode(bitstream_callback callback) +{ + int code; + int block[64]; + + register DWORD dec_v; + register int bitidx, left; + register DWORD enc_v = 0; + register int ebitidx = 31; + register int enc_len = 0; + + DWORD *block_f; + + bitstream_set_callback(0, callback); + bitstream_decode_start(0, dec_v, bitidx, left); + + divx_decode_picture_header(dec_v, bitidx, left); + + if (divx_is_key_frame()) + { + divx_encode_vos_header(enc_v, ebitidx, enc_len); + divx_encode_vol_header(enc_v, ebitidx, enc_len); + //if (divx_encode_gop_header(enc_v, ebitidx, len) < 0) + // return -1; + bitstream_flush_output(BITSTREAM_MODE_OUTPUT, enc_v, ebitidx, enc_len); + + enc_v = 0; + ebitidx = 31; + enc_len = 0; + } + divx_encode_picture_header(enc_v, ebitidx, enc_len); + + // process VOP + first_slice_line = 1; + + mb_x = 0; + mb_y = 0; + + bits_len_tab_cur[0] = bits_len[rl_table_index]; + bits_len_tab_cur[1] = bits_len[3 + rl_chroma_table_index]; + bits_len_tab_cur[2] = bits_len[6 + rl_table_index]; + bits_len_tab2_cur[0] = bits_len2[rl_table_index]; + bits_len_tab2_cur[1] = bits_len2[3 + rl_chroma_table_index]; + bits_len_tab2_cur[2] = bits_len2[6 + rl_table_index]; + level_run_tab_cur[0] = level_run[rl_table_index]; + level_run_tab_cur[1] = level_run[3 + rl_chroma_table_index]; + level_run_tab_cur[2] = level_run[3 + rl_table_index]; + level_run_tab2_cur[0] = level_run2[rl_table_index]; + level_run_tab2_cur[1] = level_run2[3 + rl_chroma_table_index]; + level_run_tab2_cur[2] = level_run2[3 + rl_table_index]; + + block_f = block_flags; + +#ifdef USE_AC_CORRECTION + memset(ac_val_base, 0, ac_size); +#endif + + for (; mb_y < mb_height; mb_y++) + { + divx_set_block_index(); + + for (; mb_x < mb_width; mb_x++) + { + // process MB +#ifdef DUMP + DEBUG_MSG("-[%d: %d %d] --------------------------------------------\n", frame_number, mb_x, mb_y); +#endif + + int pred_x, pred_y; + DWORD cbp = 0; + DWORD block_i; + +#ifdef USE_AC_CORRECTION + use_acpred = 1; + + if (!mb_intra) + { + qmul = qscale << 1; + qadd = (qscale - 1) | 1; + +#ifdef DUMP + //DEBUG_MSG("qmul=%d, qadd=%d\n", qmul, qadd); +#endif + + } else + { + qmul = 1; + qadd = 0; + } + +#endif + +/* +if (frame_number == 0 && mb_x == 31 && mb_y == 16) +{ + int kk = 1; +} +*/ + pred_x = 0; + pred_y = 0; + mb_skip = FALSE; + + motion_x = 0; + motion_y = 0; + + divx_update_block_index(); + + if (pict_type == DIVX_I_TYPE) + { + BYTE *coded_val; + int i; + + mb_intra = TRUE; + divx_get_vlc2(code, dec_v, bitidx, left, mb_intra_vlc.table); + + for (i = 0; i < 6; i++) + { + int val = ((code >> (5 - i)) & 1); + if (i < 4) + { + int pred = divx_coded_block_pred(i, &coded_val); + val = val ^ pred; + *coded_val = (BYTE)val; + } + cbp |= val << (5 - i); + } + } + else if (pict_type == DIVX_P_TYPE) + { + if (use_skip_mb_code) + { + register DWORD ret; + bitstream_get_bits1(ret, 0, dec_v, bitidx, left); + if (ret) + { + mb_intra = FALSE; + mb_skip = TRUE; + } + } + if (!mb_skip) + { + divx_get_vlc3(code, dec_v, bitidx, left, mb_non_intra_vlc.table); + if (code < 0) + return -1; + mb_intra = ((~code & 0x40) >> 6) != 0; + cbp = code & 0x3f; + } + } + + if (mb_intra) + { + bitstream_get_bits1(ac_pred, 0, dec_v, bitidx, left); +#ifdef DUMP + DEBUG_MSG("CBP=%d, ACPRED=%d\n", cbp, ac_pred); +#endif + +#ifdef USE_AC_CORRECTION + use_acpred = 1; + + if (use_acpred && ac_pred) + { + if (divx_transcode_acpred_mb(enc_v, ebitidx, enc_len, dec_v, bitidx, left, cbp) < 0) + return -1; + continue; + } +#endif + + } else + { +#ifdef DUMP + //DEBUG_MSG("CBP=%d, intra=%d\n", cbp, mb_intra); +#endif + if (!mb_skip) + { + // predict motion + divx_pred_motion(&pred_x, &pred_y); + + motion_x = pred_x; + motion_y = pred_y; + divx_decode_motion(dec_v, bitidx, left, motion_x, motion_y); +#ifdef DUMP + DEBUG_MSG("MOTION mx=%d my=%d\n", motion_x, motion_y); +#endif + } + + if ((cbp | motion_x | motion_y) == 0) + mb_skip = TRUE; + + } + + divx_encode_mb_header(enc_v, ebitidx, enc_len, cbp, mb_skip, ac_pred); + + if (!mb_intra && !mb_skip) + { + divx_encode_motion(enc_v, ebitidx, enc_len, motion_x, pred_x, f_code); + divx_encode_motion(enc_v, ebitidx, enc_len, motion_y, pred_y, f_code); + } + + for (block_i = 0; block_i < 6; block_i++) + { + int ac_i, run_diff; + int last_non_zero; + int *dec_scan_table, *enc_scan_table; + int *enc_inv_scan_table; + + DIVX_BITS_LEN *bits_len_tab; + DWORD *level_run_tab, *level_run_tab2; + DWORD *bits_len_tab2, *uni_tbl; + + DWORD last_scan = 0; + DWORD coded = (cbp >> (5 - block_i)) & 1; + BOOL the_same_scan_table = TRUE; + int dec_dc_pred_dir, enc_dc_pred_dir; + + if (mb_intra) + { + int level, dc_level; + DWORD tbl_idx; + + run_diff = 0; + + // decode dc + if (block_i < 4) + { + divx_get_vlc3(level, dec_v, bitidx, left, dc_lum_vlc[dc_table_index].table); + } + else + { + divx_get_vlc3(level, dec_v, bitidx, left, dc_chroma_vlc[dc_table_index].table); + } + + if (level < 0) + return -1; + if (level == 119) + { + register DWORD ret; + bitstream_get_bits(level, 0, dec_v, bitidx, left, 8); + bitstream_get_bits1(ret, 0, dec_v, bitidx, left); + if (ret) + level = -level; + } + else if (level != 0) + { + register DWORD ret; + bitstream_get_bits1(ret, 0, dec_v, bitidx, left); + if (ret) + level = -level; + } + + dc_level = divx_pred_dc(block_i, level, &dec_dc_pred_dir, FALSE); +#ifdef DUMP + DEBUG_MSG("-----------------\n"); + DEBUG_MSG("{%d}: DC LEVEL=%d / INTRA=%d\n", block_i, dc_level, mb_intra); +#endif + + level = divx_pred_dc(block_i, dc_level, &enc_dc_pred_dir, TRUE); + divx_encode_dc(enc_v, ebitidx, enc_len, level, block_i); + if (!coded) + continue; + + ac_i = 0; + if (ac_pred && dec_dc_pred_dir != enc_dc_pred_dir) + the_same_scan_table = FALSE; +#ifdef DUMP + //DEBUG_MSG(" DIR: dec=%d, enc=%d\n", dec_dc_pred_dir, enc_dc_pred_dir); +#endif + + tbl_idx = block_i >> 2; + bits_len_tab = bits_len_tab_cur[tbl_idx]; + bits_len_tab2 = bits_len_tab2_cur[tbl_idx]; + uni_tbl = uni_table[0]; + level_run_tab = level_run_tab_cur[tbl_idx]; + level_run_tab2 = level_run_tab2_cur[tbl_idx]; + } else + { + if (!coded) + continue; + + run_diff = 1; + ac_i = -1; + bits_len_tab = bits_len_tab_cur[2]; + bits_len_tab2 = bits_len_tab2_cur[2]; + uni_tbl = uni_table[1]; + level_run_tab = level_run_tab_cur[2]; + level_run_tab2 = level_run_tab2_cur[2]; + } +#ifdef USE_SLOW_METHOD_LIMIT +#ifdef USE_SLOW_METHOD + if (frame_size > max_allowed_frame_size_for_correct_method) +#endif + the_same_scan_table = TRUE; +#endif + +#ifdef USE_ONLY_SLOW_METHOD + if (the_same_scan_table) + { + enc_dc_pred_dir = dec_dc_pred_dir; + the_same_scan_table = FALSE; + } +#endif + + + if (!the_same_scan_table) + { +#ifdef USE_AC_CORRECTION + // ???????????????????????????????? + dec_scan_table = ac_pred ? ((dec_dc_pred_dir == 0) ? + intra_v_scantable.permutated : + intra_h_scantable.permutated) : intra_scantable.permutated; + enc_scan_table = ac_pred ? ((enc_dc_pred_dir == 0) ? + intra_v_scantable.permutated : + intra_h_scantable.permutated) : intra_scantable.permutated; + enc_inv_scan_table = ac_pred ? ((enc_dc_pred_dir == 0) ? + intra_v_scantable.inv_permutated : + intra_h_scantable.inv_permutated) : intra_scantable.inv_permutated; +#else + dec_scan_table = (dec_dc_pred_dir == 0) ? + intra_v_scantable.permutated : // left + intra_h_scantable.permutated; // top + enc_scan_table = (enc_dc_pred_dir == 0) ? + intra_v_scantable.permutated : // left + intra_h_scantable.permutated; // top + enc_inv_scan_table = (enc_dc_pred_dir == 0) ? + intra_v_scantable.inv_permutated : // left + intra_h_scantable.inv_permutated; // top +#endif + } + + last_non_zero = ac_i; + + if (the_same_scan_table) + { + for(;;) + { + register DWORD idx, bits, len; + bitstream_show_bits(idx, dec_v, bitidx, 16); + + bits = bits_len_tab[idx>>2].bits; + len = bits_len_tab[idx>>2].len; + if (len >= 16) // normal (outlen < 16 bits, inlen < 14 bits) + { + bitstream_skip_bits(0, dec_v, bitidx, left, len & 0xf); + len >>= 4; + if (bits & 0x8000) // last + { + bits &= ~0x8000; + bitstream_put_bits(len, bits, enc_v, ebitidx, enc_len); + break; + } + bitstream_put_bits(len, bits, enc_v, ebitidx, enc_len); + } + else // extended || esc123 + { + if (len == 0) // extended (16 bits < outlen < 23 bits) + { + bits = bits_len_tab2[bits | (idx & 3)]; + len = ((bits >> 5) & 0xf) + 1; + bitstream_skip_bits(0, dec_v, bitidx, left, len); + len = (bits & 31); + if (bits & 0x80000000) // last + { + bits = (bits & ~(0x80000000)) >> 9; + bitstream_put_bits(len, bits, enc_v, ebitidx, enc_len); + break; + } + bitstream_put_bits(len, bits >> 9, enc_v, ebitidx, enc_len); + } + else if (len == 4) // extended-"ESC4" (outlen >= 23 bits) + { + DWORD last; + int level; + bits = level_run_tab[idx>>2]; + // if extended (first VLC is longer than 14 bits) + if ((bits >> 16) == 0) + bits = level_run_tab2[bits | (idx & 3)]; + len = ((bits >> 18) & 0xf) + 1; + bitstream_skip_bits(0, dec_v, bitidx, left, len); + + last = (bits & (1 << 17)) << 3; + level = ((int)bits) >> 25; + bits = ((bits & 63) << 14); + bits += ((level & 0xfff) << 1) + last + ((3 << 23) + (3 << 21) + (1 << 13) + 1); + bitstream_put_bits((7+2+1+6+1+12+1), bits, enc_v, ebitidx, enc_len); + if (last) + break; + } + else + { + bitstream_skip_bits(0, dec_v, bitidx, left, bits); + + if (len == 3) // esc3 + { + int level; + DWORD last; + bitstream_get_bits(bits, 0, dec_v, bitidx, left, (1+6+8)); + + level = (bits & 0x80) ? ((bits & 0xff) | 0xffffff00) : (bits & 0x7f); + last = ((bits << 6) & 0x100000); + + bits = last + (((bits >> 8) & 63) << 14); + bits += ((3 << 23) + (3 << 21) + (1 << 13) + 1) + ((level & 0xfff) << 1); + + bitstream_put_bits((7+2+1+6+1+12+1), bits, enc_v, ebitidx, enc_len); + if (last) + break; + } + else // esc1,esc2 + { + BOOL esc1 = (len & 1); + int level; + DWORD run, last; + bitstream_show_bits(idx, dec_v, bitidx, 16); + bits = level_run_tab[idx>>2]; + + // if extended esc1/2 (first VLC is longer than 14 bits) + if ((bits >> 16) == 0) + bits = level_run_tab2[bits | (idx & 3)]; + + len = ((bits >> 18) & 0xf) + 1; + bitstream_skip_bits(0, dec_v, bitidx, left, len); + + /* bits = level(7)+reserved(3)+first_len(4)+first_last(1)+add_level(5)+add_run(6)+run(6) */ + level = ((int)bits) >> 25; + run = (bits & 63) + 1; + last = (bits & (1 << 17)) << 3; + + if (esc1) + { + level = level > 0 ? level + ((bits >> 12) & 31) : level - ((bits >> 12) & 31); + } else + { + run += (bits >> 6) & 63; + run += run_diff; + } + + // now we have level, run, last - output in ESC3 mode + bits = ((3 << 23) + (3 << 21) + (1 << 13) + 1) + last + ((run - 1) << 14) + ((level & 0xfff) << 1); + + // TODO: use uni_tab + bitstream_put_bits((7+2+1+6+1+12+1), bits, enc_v, ebitidx, enc_len); + if (last) + break; + } + continue; + } + } + } + } + else /////////////////////////////////////////////////////////// + { +#ifdef USE_SLOW_METHOD + register int level; + register DWORD run, i; + + while (ac_i < 64) + { + register DWORD idx, bits, len; + bitstream_show_bits(idx, dec_v, bitidx, 16); + + bits = level_run_tab[idx>>2]; + + if (bits == 0) // esc123 + { + bits = bits_len_tab[idx>>2].bits; + len = bits_len_tab[idx>>2].len; + bitstream_skip_bits(0, dec_v, bitidx, left, bits); + + if (len == 3) // esc3 + { + bitstream_get_bits(bits, 0, dec_v, bitidx, left, (1+6+8)); + level = (bits & 0x80) ? ((bits & 0xff) | 0xffffff00) : (bits & 0x7f); + run = ((bits >> 8) & 63) + 1; + if (bits & 0x4000) + break; + } + else // esc1,esc2 + { + BOOL esc1 = (len & 1); + bitstream_show_bits(idx, dec_v, bitidx, 16); + bits = level_run_tab[idx>>2]; + if ((bits >> 16) == 0) + bits = level_run_tab2[bits | (idx & 3)]; + len = ((bits >> 18) & 0xf) + 1; + bitstream_skip_bits(0, dec_v, bitidx, left, len); + + level = ((int)bits) >> 25; + run = (bits & 63) + 1; + + if (esc1) + { + level = level > 0 ? level + ((bits >> 12) & 31) : level - ((bits >> 12) & 31); + } else + { + run += (bits >> 6) & 63; + run += run_diff; + } + + if (bits & (1 << 17)) + break; + } + } else + { + if ((bits >> 16) == 0) + bits = level_run_tab2[bits | (idx & 3)]; + len = ((bits >> 18) & 0xf) + 1; + bitstream_skip_bits(0, dec_v, bitidx, left, len); + level = ((int)bits) >> 25; + + run = (bits & 63) + 1; + if (bits & (1 << 17)) + break; + } + + ac_i += run; + idx = dec_scan_table[ac_i]; + bits = enc_inv_scan_table[idx]; + if (last_scan < bits) + last_scan = bits; + block[idx] = level; + block_f[idx] = block_flag; +#ifdef DUMP + //DEBUG_MSG("-> level=%d, run=%d\n", level, run); + block_idx[idx] = ac_i; + block_run[idx] = run; +#endif + } + + + ac_i += run; + +#ifdef DUMP + //DEBUG_MSG("-> level=%d, run=%d\n", level, run); + block_idx[dec_scan_table[ac_i]] = ac_i; + block_run[dec_scan_table[ac_i]] = run; +#endif + + run = dec_scan_table[ac_i]; + i = enc_inv_scan_table[run]; + if (last_scan < i) + last_scan = i; + block[run] = level; + block_f[run] = block_flag; + + // we have reconstructed block at this moment... +#ifdef DUMP + for (i = 0; i < 64; i++) + { + if (block_f[i] == block_flag) + { + //DEBUG_MSG("[%d] = %d (%d,run=%d)\n", i, block[i], block_idx[i], block_run[i]); + DEBUG_MSG("[%d] = %d\n", i, block[i]); + } + } +#endif + + // now output the table + level = 0; + run = 0; + + for (i = 0; i <= last_scan; i++) + { + register DWORD eidx = enc_scan_table[i]; + + if (block_f[eidx] == block_flag) + { + if (level && (int)run > last_non_zero) + { +#if 0 + if (!((level + 64) & (~127))) // ESC3 + { + idx = (DWORD)(level + 64) + (run - last_non_zero)*128; + last_non_zero = run; + idx = uni_tbl[idx]; + run = idx >> 24; + idx &= 0xffffff; + bitstream_put_bits(run, idx, enc_v, ebitidx, enc_len); + } + else +#endif + { + divx_put_rl_vlc_esc3(enc_v, ebitidx, enc_len, level, 0, run - last_non_zero); + last_non_zero = run; + } + } + level = block[eidx]; + run = i; + } + } + + if (level && (int)run > last_non_zero) + { + +#if 0 + if (!((level + 64) & (~127))) // ESC3 + { + idx = 128*64 + (DWORD)(level + 64) + (run - last_non_zero)*128; + idx = uni_tbl[idx]; + run = idx >> 24; + idx &= 0xffffff; + bitstream_put_bits(run, idx, enc_v, ebitidx, enc_len); + } else +#endif + { + divx_put_rl_vlc_esc3(enc_v, ebitidx, enc_len, level, 1, run - last_non_zero); + } + } + +#ifdef USE_AC_CORRECTION + if (use_acpred && mb_intra) + { + divx_pred_flaged_ac(block_i, block, block_f, block_flag, dec_dc_pred_dir); + } +#endif + + block_flag++; +#endif + } + } + + if (!mb_intra) + divx_clean_intra_table_entries(); + divx_update_motion_val(); + } + first_slice_line = 0; + mb_x = 0; + } + // flush last output bits + bitstream_flush_output(BITSTREAM_MODE_DONE, enc_v, ebitidx, enc_len); + frame_number++; + return 0; + +} + diff --git a/src/divx.h b/src/divx.h new file mode 100644 index 0000000..3cefe00 --- /dev/null +++ b/src/divx.h @@ -0,0 +1,122 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - DivX3->MPEG-4 transcoder header file + * \file divx.h + * \author bombur + * \version 0.1 + * \date 1.04.2007 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#ifndef SP_DIVX_H +#define SP_DIVX_H + +#include + +#ifdef DIVX_INTERNAL + +#define DIVX_I_TYPE 1 +#define DIVX_P_TYPE 2 + +// run length table +#define DIVX_MAX_RUN 64 +#define DIVX_MAX_LEVEL 64 + +typedef struct DIVX_VLC +{ + int bits; + signed short (*table)[2]; ///< code, bits + int table_size, table_allocated; +} DIVX_VLC; + +typedef struct DIVX_RL_VLC_ELEM +{ + signed short level; + signed char len; + BYTE run; +} DIVX_RL_VLC_ELEM; + +typedef struct DIVX_RL_TABLE +{ + DWORD n; ///< number of entries of table_vlc minus 1 + int last; ///< number of values for last = 0 + const WORD (*table_vlc)[2]; + const signed char *table_run; + const signed char *table_level; + BYTE *index_run[2]; ///< encoding only + signed char *max_level[2]; ///< encoding & decoding + signed char *max_run[2]; ///< encoding & decoding + DIVX_VLC vlc; ///< decoding only + DIVX_RL_VLC_ELEM *rl_vlc[32]; ///< decoding only +} DIVX_RL_TABLE; + +typedef struct DIVX_SCAN_TABLE +{ + const BYTE *scantable; + int permutated[64]; + int inv_permutated[64]; + BYTE raster_end[64]; +} DIVX_SCAN_TABLE; + +typedef struct DIVX_MV_TABLE +{ + int n; + const WORD *table_mv_code; + const BYTE *table_mv_bits; + const BYTE *table_mvx; + const BYTE *table_mvy; + WORD *table_mv_index; /* encoding: convert mv to index in table_mv */ + DIVX_VLC vlc; /* decoding: vlc */ +} DIVX_MV_TABLE; + +//////////////////////////////////////////////// + +#ifdef WIN32 +#pragma pack(1) +#endif + +typedef struct ATTRIBUTE_PACKED +{ + BYTE len; + WORD bits; +} DIVX_BITS_LEN; + +#ifdef WIN32 +#pragma pack() +#endif + +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +int divx_transcode_preinit(void); +int divx_transcode_predeinit(void); + +int divx_transcode_init(int width, int height, int time_incr_res); +int divx_transcode_deinit(void); + +int divx_transcode(bitstream_callback); + +BOOL divx_is_key_frame(void); + +#ifdef __cplusplus +} +#endif + +#endif // of SP_DIVX_H diff --git a/src/dvd.h b/src/dvd.h new file mode 100644 index 0000000..d2649f6 --- /dev/null +++ b/src/dvd.h @@ -0,0 +1,115 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - DVD player header file + * \file dvd.h + * \author bombur + * \version 0.1 + * \date 2.08.2004 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#ifndef SP_DVD_H +#define SP_DVD_H + +// must be equal to DVDMenuID_t from dvdtypes.h +typedef enum +{ + DVD_MENU_DEFAULT = -1, + DVD_MENU_ESCAPE = 0, + DVD_MENU_TITLE = 2, + DVD_MENU_ROOT = 3, + DVD_MENU_SUBTITLE = 4, + DVD_MENU_AUDIO = 5, + DVD_MENU_ANGLE = 6, + DVD_MENU_CHAPTERS = 7 +} DVD_MENU_TYPE; + + +//////////////////////////////////////////////////////////////////// +#ifdef __cplusplus +extern "C" { +#endif + +#include + +/// Open DVD player +int dvd_open(const char *path); +/// Close DVD player +int dvd_close(); +/// Reset DVD player +int dvd_reset(); + +/// Get next data block +int dvd_get_next_block(BYTE **buf, int *event, int *len); +/// Free data block (if cache used). +int dvd_free_block(BYTE *data); + +/// Get DVD error string +char *dvd_error_string(); + +/// Seek to given time and play +BOOL dvd_seek(int seconds); +BOOL dvd_seek_titlepart(int title, int part); +void dvd_get_cur(int *title, int *chapter); +int dvd_getnumchapters(int title); +int dvd_getnumtitles(); + +void dvd_button_play(); + +void dvd_setdeflang_menu(char *); +void dvd_setdeflang_audio(char *); +void dvd_setdeflang_spu(char *); + +/// Play DVD disc (from drive or folder) +int dvd_play(const char *dvdpath, bool play_from_drive); + +/// Stop playing +BOOL dvd_stop(); + +bool dvd_do_command(const SPString & command); + +bool dvd_get_saved(); + +/// Change playing speed +BOOL dvd_setspeed(MPEG_SPEED_TYPE speed); +MPEG_SPEED_TYPE dvd_getspeed(); + +void dvd_setdebug(BOOL ison); +BOOL dvd_getdebug(); + +/// Advance playing +int dvd_player_loop(); + +int dvd_getangle(); +bool dvd_ismenu(); + +// title = 1..99, chapter = 1.999 +// time in seconds +int dvd_get_total_time(int title, int chapter, int *time); +int dvd_get_chapter_for_time(int title, int time, int *chapter); + +void dvd_button_menu(DVD_MENU_TYPE menu = DVD_MENU_DEFAULT); +void dvd_button_angle(int setangl = -1); +void dvd_button_audio(char *setlang = NULL, int startfrom = 0); +void dvd_button_subtitle(char *setlang = NULL, int startfrom = 0); + + +#ifdef __cplusplus +} +#endif + +#endif // of SP_DVD_H diff --git a/src/dvd/Makefile b/src/dvd/Makefile new file mode 100644 index 0000000..32c7821 --- /dev/null +++ b/src/dvd/Makefile @@ -0,0 +1,89 @@ +######################################################################### +# +# SigmaPlayer source project - DVD module makefile +# \file Makefile +# \author bombur +# \version 0.3 +# \date 10.12.2008 +# +########################################################################## + + +########################################################################## + +MAIN_SRC := main.cpp + +PROJECT_SRC := \ + dvd.cpp \ + dvd_player.cpp \ + dvd_misc.c \ + dvd_css.c \ + module.cpp \ + module-dvd.cpp \ + module-init.cpp \ + ../libsp/containers/string.cpp + +PREBUILD := cd ../contrib/libdvdcss && make && cd ../libdvdnav && make && cd ../../dvd + +EXTERNAL_STATIC_LINKS_WITH := \ + ../contrib/memcpy.o \ + ../contrib/strcmp.o \ + ../contrib/strlen.o \ + ../contrib/libdvdnav/src/.libs/libdvdnav.a \ + ../contrib/libdvdcss/src/.libs/libdvdcss.a + +SPINCLUDE = ../libsp/ +DVDINCLUDE = ../contrib/libdvdnav/src/ +DVDINCLUDE1 = ../contrib/libdvdnav/src/dvdread/ +DVDINCLUDE2 = ../contrib/libdvdnav/src/vm/ +DVDINCLUDE3 = ../contrib/libdvdcss/ +DVDINCLUDE4 = ../contrib/libdvdcss/src/ + +ifeq "$(PLAYER_MODEL)" "Technosonic" +SPCFLAGS += -DSP_PLAYER_TECHNOSONIC=1 +LIBSP_SPECIFIC := \ + ../libsp/MP/sp_module.cpp \ + ../libsp/MP/sp_module_crt0.S + +endif + +ifeq "$(PLAYER_MODEL)" "DreamX108" +SPCFLAGS += -DSP_PLAYER_DREAMX108=1 +LIBSP_SPECIFIC := \ + ../libsp/MP/sp_module.cpp \ + ../libsp/MP/sp_module_crt0.S +endif + +ifeq "$(PLAYER_MODEL)" "Mecotek" +SPCFLAGS += -DSP_PLAYER_MECOTEK=1 +LIBSP_SPECIFIC := \ + ../libsp/MP/sp_module.cpp \ + ../libsp/MP/sp_module_crt0.S +endif + +SPCFLAGS += -fno-exceptions -I.. -I$(SPINCLUDE) -I$(DVDINCLUDE) -I$(DVDINCLUDE1) -I$(DVDINCLUDE2) -I$(DVDINCLUDE3) -I$(DVDINCLUDE4) -DDVDNAV_COMPILE=1 -D__UCLIBC_CTOR_DTOR__ -DCOMPILE_MODULE -D_GNU_SOURCE +SPCXXFLAGS += -fpermissive -fno-rtti + +SPCFLAGS += -D_strdup=strdup -D_lseek=lseek -D_read=read -D_open=open -D_close=close + +LOCAL_MAKEFILE := Makefile + +TARGET_TYPE := EXECUTABLE + +SRC := $(PROJECT_SRC) $(LIBSP_SPECIFIC) + +USE_STD_LIB := 1 + +COMPILKIND = release + +PREPROCESSORFLAGS += -D__STDC_LIMIT_MACROS -DSP_ARM=1 + +MAKE_CLEAN = rm -fr *.gdb && cd ../contrib/libdvdnav && make clean && cd ../libdvdcss && make clean && cd ../../dvd + +CROSS = arm-elf- +LDFLAGS = -elf2flt="-s128" -s --static -nostdlib +EXEFLAGS = -lgcc + + +include ../Makefile.inc + diff --git a/src/dvd/dvd-internal.h b/src/dvd/dvd-internal.h new file mode 100644 index 0000000..61cf578 --- /dev/null +++ b/src/dvd/dvd-internal.h @@ -0,0 +1,83 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - DVD player INTERNAL header file + * \file dvd/dvd-internal.h + * \author bombur + * \version 0.2 + * \date 2.08.2004 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#ifndef SP_DVD_INTERNAL_H +#define SP_DVD_INTERNAL_H + +//////////////////////////////////////////////////////////////////// +#ifdef __cplusplus +extern "C" { +#endif + +#include + +/// Pause playing +BOOL dvd_pause(); + +/// Continue playing from saved position +BOOL dvd_continue_play(); + +/// Set title (used for navigation) +int dvd_loadtitle(int titl); + +BOOL dvd_zoom_hor(int scale); +BOOL dvd_zoom_ver(int scale); +BOOL dvd_scroll(int offx, int offy); + +char *dvd_getdeflang_menu(); +char *dvd_getdeflang_audio(); +char *dvd_getdeflang_spu(); + +/// Menu button controls +void dvd_button_press(); +void dvd_button_up(); +void dvd_button_down(); +void dvd_button_left(); +void dvd_button_right(); + +void dvd_getstringlang(BYTE *strlang, WORD lang); + +void dvd_button_return(); + +void dvd_button_pause(); +void dvd_button_step(); +void dvd_button_slow(); +void dvd_button_prev(); +void dvd_button_next(); +void dvd_button_fwd(); +void dvd_button_rew(); + +//////////////// +/// internal funcs. + +// used by dvdnav_get_spu_logical_stream() +int dvd_get_spu_mode(KHWL_VIDEOMODE vmode); + +int dvd_savepos(bool reset); + +#ifdef __cplusplus +} +#endif + +#endif // of SP_DVD_INTERNAL_H diff --git a/src/dvd/dvd-langs.inc.cpp b/src/dvd/dvd-langs.inc.cpp new file mode 100644 index 0000000..71e4dc5 --- /dev/null +++ b/src/dvd/dvd-langs.inc.cpp @@ -0,0 +1,170 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - DVD language strings header file + * \file dvd/dvd-langs.inc.cpp + * \author bombur + * \version 0.1 + * \date 2.08.2004 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +static DvdLanguage dvdlangs[] = +{ + { "Afar", 0x6161, }, // "aa" + { "Abkhazian", 0x6162, }, // "ab" + { "Afrikaans", 0x6166, }, // "af" + { "Amharic", 0x616d, }, // "am" + { "Arabic", 0x6172, }, // "ar" + { "Assamese", 0x6173, }, // "as" + { "Aymara", 0x6179, }, // "ay" + { "Azerbaijani", 0x617a, }, // "az" + { "Bashkir", 0x6261, }, // "ba" + { "Byelorussian", 0x6265, }, // "be" + { "Bulgarian", 0x6267, }, // "bg" + { "Bihari", 0x6268, }, // "bh" + { "Bislama", 0x6269, }, // "bi" + { "Bengali", 0x626e, }, // "bn" + { "Tibetan", 0x626f, }, // "bo" + { "Breton", 0x6272, }, // "br" + { "Catalan", 0x6361, }, // "ca" + { "Corsican", 0x636f, }, // "co" + { "Czech", 0x6373, }, // "cs" + { "Welsh", 0x6379, }, // "cy" + { "Danish", 0x6461, }, // "da" + { "Deutsch", 0x6465, }, // "de" + { "Bhutani", 0x647a, }, // "dz" + { "Greek", 0x656c, }, // "el" + { "English", 0x656e, }, // "en" + { "Esperanto", 0x656f, }, // "eo" + { "Espanol", 0x6573, }, // "es" + { "Estonian", 0x6574, }, // "et" + { "Basque", 0x6575, }, // "eu" + { "Persian", 0x6661, }, // "fa" + { "Finnish", 0x6669, }, // "fi" + { "Fiji", 0x666a, }, // "fj" + { "Faroese", 0x666f, }, // "fo" + { "Francais", 0x6672, }, // "fr" + { "Frisian", 0x6679, }, // "fy" + { "Irish", 0x6761, }, // "ga" + { "Scots Gaelic", 0x6764, }, // "gd" + { "Galician", 0x676c, }, // "gl" + { "Guarani", 0x676e, }, // "gn" + { "Gujarati", 0x6775, }, // "gu" + { "Hausa", 0x6861, }, // "ha" + { "Hebrew", 0x6865, }, // "he" + { "Hindi", 0x6869, }, // "hi" + { "Croatian", 0x6872, }, // "hr" + { "Magyar", 0x6875, }, // "hu" + { "Armenian", 0x6961, }, // "ia" + { "Indonesian", 0x6964, }, // "id" + { "Interlingue", 0x6965, }, // "ie" + { "Inupiak", 0x696b, }, // "ik" + { "Indonesian", 0x696e, }, // "in" + { "Icelandic", 0x6973, }, // "is" + { "Italian", 0x6974, }, // "it" + { "Inuktitut", 0x6975, }, // "iu" + { "Hebrew", 0x6977, }, // "iw" + { "Japanese", 0x6a61, }, // "ja" + { "Yiddish", 0x6a69, }, // "ji" + { "Javanese", 0x6a77, }, // "jw" + { "Georgian", 0x6b61, }, // "ka" + { "Kazakh", 0x6b6b, }, // "kk" + { "Greenlandic", 0x6b6c, }, // "kl" + { "Cambodian", 0x6b6d, }, // "km" + { "Kannada", 0x6b6e, }, // "kn" + { "Korean", 0x6b6f, }, // "ko" + { "Kashmiri", 0x6b73, }, // "ks" + { "Kurdish", 0x6b75, }, // "ku" + { "Kirghiz", 0x6b79, }, // "ky" + { "Latin", 0x6c61, }, // "la" + { "Lingala", 0x6c6e, }, // "ln" + { "Laothian", 0x6c6f, }, // "lo" + { "Lithuanian", 0x6c74, }, // "lt" + { "Latvian", 0x6c76, }, // "lv" + { "Malagasy", 0x6d67, }, // "mg" + { "Maori", 0x6d69, }, // "mi" + { "Macedonian", 0x6d6b, }, // "mk" + { "Malayalam", 0x6d6c, }, // "ml" + { "Mongolian", 0x6d6e, }, // "mn" + { "Moldavian", 0x6d6f, }, // "mo" + { "Marathi", 0x6d72, }, // "mr" + { "Malay", 0x6d73, }, // "ms" + { "Maltese", 0x6d74, }, // "mt" + { "Burmese", 0x6d79, }, // "my" + { "Nauru", 0x6e61, }, // "na" + { "Nepali", 0x6e65, }, // "ne" + { "Nederlands", 0x6e6c, }, // "nl" + { "Norsk", 0x6e6f, }, // "no" + { "Occitan", 0x6f63, }, // "oc" + { "Oromo", 0x6f6d, }, // "om" + { "Oriya", 0x6f72, }, // "or" + { "Punjabi", 0x7061, }, // "pa" + { "Polish", 0x706c, }, // "pl" + { "Pashto", 0x7073, }, // "ps" + { "Portugues", 0x7074, }, // "pt" + { "Quechua", 0x7175, }, // "qu" + { "Rhaeto-Romance", 0x726d, }, // "rm" + { "Kirundi", 0x726e, }, // "rn" + { "Romanian", 0x726f, }, // "ro" + { "Russian", 0x7275, }, // "ru" + { "Kinyarwanda", 0x7277, }, // "rw" + { "Sanskrit", 0x7361, }, // "sa" + { "Sindhi", 0x7364, }, // "sd" + { "Sangho", 0x7367, }, // "sg" + { "Serbo-Croatian", 0x7368, }, // "sh" + { "Sinhalese", 0x7369, }, // "si" + { "Slovak", 0x736b, }, // "sk" + { "Slovenian", 0x736c, }, // "sl" + { "Samoan", 0x736d, }, // "sm" + { "Shona", 0x736e, }, // "sn" + { "Somali", 0x736f, }, // "so" + { "Albanian", 0x7371, }, // "sq" + { "Serbian", 0x7372, }, // "sr" + { "Siswati", 0x7373, }, // "ss" + { "Sesotho", 0x7374, }, // "st" + { "Sundanese", 0x7375, }, // "su" + { "Svenska", 0x7376, }, // "sv" + { "Swahili", 0x7377, }, // "sw" + { "Tamil", 0x7461, }, // "ta" + { "Telugu", 0x7465, }, // "te" + { "Tajik", 0x7467, }, // "tg" + { "Thai", 0x7468, }, // "th" + { "Tigrinya", 0x7469, }, // "ti" + { "Turkmen", 0x746b, }, // "tk" + { "Tagalog", 0x746c, }, // "tl" + { "Setswana", 0x746e, }, // "tn" + { "Tonga", 0x746f, }, // "to" + { "Turkish", 0x7472, }, // "tr" + { "Tsonga", 0x7473, }, // "ts" + { "Tatar", 0x7474, }, // "tt" + { "Twi", 0x7477, }, // "tw" + { "Uighur", 0x7567, }, // "ug" + { "Ukrainian", 0x756b, }, // "uk" + { "Urdu", 0x7572, }, // "ur" + { "Uzbek", 0x757a, }, // "uz" + { "Vietnamese", 0x7569, }, // "ui" + { "Volapuk", 0x766f, }, // "vo" + { "Wolof", 0x776f, }, // "wo" + { "Xhosa", 0x7868, }, // "xh" + { "Yiddish", 0x7969, }, // "yi" + { "Yoruba", 0x796f, }, // "yo" + { "Zhuang", 0x7a61, }, // "za" + { "Chinese", 0x7a68, }, // "zh" + { "Zulu", 0x7a75, }, // "zu" + { "", 0x0 }, + { "", 0xffff }, +}; diff --git a/src/dvd/dvd.cpp b/src/dvd/dvd.cpp new file mode 100644 index 0000000..0edc94f --- /dev/null +++ b/src/dvd/dvd.cpp @@ -0,0 +1,3157 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - DVD player source file. Uses libdvdnav. + * \file dvd/dvd.cpp + * \author bombur + * \version 0.21 + * \date 2.08.2004 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include "script.h" + +#include + +#include "dvd-internal.h" +#include "dvd_misc.h" +#include "media.h" +#include "settings.h" + + +class DvdLanguage +{ +public: + const char *str; + WORD code; +}; + + +#include "dvd-langs.inc.cpp" + +///////////////////////////////////// +#define DVD_DEBUG +#define DVD_USE_MACROVISION +// It's a must if we use our buffering!!! +#define DVD_USE_CACHE + +///////////////////////////////////// + +#ifndef DVD_DEBUG +static void empty_msg(char *,...) +{ +} +#define MSG empty_msg +#define MSG_ERROR empty_msg +#else +#define MSG if (dvd_msg) msg +#define MSG_ERROR msg_error +#endif + +////////////////////////////////////////////////////////////////////// + +//#define DVD_PTS_DEBUG 1 +//#define DVD_PACKETS_DEBUG 1 + +const int dvd_num_saved = 5; + +int MPEG_PACKET_LENGTH = 0; + +bool no_spu = false; +bool no_khwl_play = false; +#ifdef WIN32 +bool dvd_msg = true; +#else +bool dvd_msg = false; +#endif + +static int cur_titleid, cur_chapid, cur_titlpos; +static int old_titleid, old_chapid; +static int cur_pack; +static LONGLONG cur_cell_pts = 0, old_cell_pts = 0, cur_cell_length = 0, cur_vobu_pts = 0; +static LONGLONG cur_pgc_length = 0; +static LONGLONG saved_pts = 0; +static int old_vob_id = -1, old_vob_cell_id = -1; +static bool vts_changed = false; +static bool dvd_menu_ahead = false; +static bool need_user_reset = false; +static int number_of_angles = 1; + +static bool dvd_play_from_drive = false; + +static int dvd_skipoffs = 0; +static int num_packets = 0; + +static dvdnav_t *dvd = NULL; +static pci_t *cur_pci = NULL; +static dsi_t *cur_dsi = NULL; + +static DWORD dvd_ID = 0; +static bool dvd_was_saved = false; +static DWORD dvd_saved_pos = 0xffffffff; +static DWORD dvd_saved_title = 0xffffffff; +static int dvd_saved_vmode = -1, dvd_saved_audio_stream = -1, dvd_saved_spu_stream = -1; + +static LONGLONG dvd_vobu_start_ptm = 0; +static LONGLONG pts_base = 0; +static int dvd_vobu_start_pos = 0; +static bool dvd_seamless = true; +static bool dvd_lastbtnupdate = false; + +static bool dvd_not_played_yet = true; +static bool dvd_waiting_to_play = false; + +static int dvd_mv_flags = -1; +static bool dvd_use_mv = true; + +static int read_max_allowed_time = 180; + +extern bool msg_debug; + +extern "C" +{ + int mv_cgms_flags = 0; +} + +static int dvd_read_errors = 0; +static bool wait_for_user = false; +static int wait_pause = 0; +static ULONGLONG wait_time = 0; +static int wait_iter = 0; +static int dvd_cur_button = -1; +static bool dvd_buttons_enabled = false; +static bool dvd_button_selected = false; + +static bool need_to_set_pts = false; +static bool need_to_check_pts = false; +static LONGLONG vobu_sptm = 0; + +static LONGLONG dvd_vobu_last_ptm = 0; + +static bool need_skip = false, was_video = false, can_skip = false; +static int num_skip = 0; + +static bool dvd_scan = false; + +static bool new_frame_size = false; +static int dvd_video_info_cnt = 0; + +static int spu_command = 0; +static MPEG_SPEED_TYPE dvd_speed = MPEG_SPEED_NORMAL; +static KHWL_VIDEOMODE dvd_vmode = KHWL_VIDEOMODE_NORMAL; +static int dvd_spu_channel_letterbox = -1; +static int dvd_spu_channel_panscan = -1; +static KHWL_SPU_BUTTON_TYPE last_upd_but; + +static int dvd_spu_stream = -1, dvd_old_spu_stream = -1; +static int dvd_aud_stream = -1, dvd_old_aud_stream = -1; + +static int forced_spu = -1; +static WORD forced_spu_lang = 0xffff; +static int forced_audio = -1; +static WORD forced_audio_lang = 0xffff; + +static WORD deflang_menu = 0x656e, deflang_audio = 0x656e, deflang_spu = 0x656e; + +static BYTE *dvdlang_lut = NULL; + +static void dvd_clearhighlights(); +static void dvd_spu_enable(bool onoff); +static void dvd_buttons_enable(bool onoff); +static int dvd_player_loop_internal(bool feed = true); + +typedef struct DVD_BUTTON +{ + DWORD left, top, right, bottom; +} DVD_BUTTON; + +const int max_buttons = 256; +DVD_BUTTON cur_buttons[max_buttons]; +int cur_num_buttons = 0; +bool need_to_check_buttons = true; + +//////////////////////////////////////////////////////// +#if 0 + +typedef struct DVD_BUTTON_QUEUE +{ + DVD_BUTTON_QUEUE *next; + int i; + int bn; + int mode; + LONGLONG pts; +} DVD_BUTTON_QUEUE; + +DVD_BUTTON_QUEUE buttons[max_buttons]; +DVD_BUTTON_QUEUE *butqueue_first = NULL, *butqueue_last = NULL; + +void dvd_init_buttons_queue() +{ + for (int i = 0; i < max_buttons; i++) + buttons[i].i = -1; + butqueue_first = NULL; + butqueue_last = NULL; +} + +/// Adds button to the queue. +/// \TODO: Optimise! +void dvd_add_button(int bn, int mode, LONGLONG pts) +{ + DVD_BUTTON_QUEUE *newbut = buttons; + for (int i = 0; i < max_buttons; i++, newbut++) + { + if (newbut->i == -1) + { + if (butqueue_last == NULL) + butqueue_first = newbut; + else + butqueue_last->next = newbut; + butqueue_last = newbut; + newbut->i = i; + newbut->bn = bn; + newbut->mode = mode; + newbut->pts = pts; + newbut->next = NULL; + break; + } + } +} + +void dvd_update_buttons(LONGLONG curpts) +{ + /*if (dvd_cur_button == -1) + { + dvd_clearhighlights(); + return; + } + */ + DVD_BUTTON_QUEUE *cur = butqueue_first, *prev = NULL; + while (cur != NULL) + { + if (cur->pts <= curpts) + { + dvd_update_button(cur->bn, cur->mode); + // remove button from the queue + cur->i = -1; + if (prev == NULL) + butqueue_first = cur->next; + else + prev->next = cur->next; + if (cur == butqueue_last) + butqueue_last = prev; + } else + prev = cur; + cur = cur->next; + } +} + +void dvd_print_buttons_queue() +{ + MSG("DVD: Buttons queue:\n"); + DVD_BUTTON_QUEUE *cur = butqueue_first; + while (cur != NULL) + { + MSG("DVD: but=%d (%d) pts=%d\n", cur->bn, cur->i, cur->pts); + cur = cur->next; + } +} +#endif + +///////////////////////////////////////////////////////////////////////// + +void dvd_invalid() +{ + script_error_callback(SCRIPT_ERROR_INVALID); +} + +void dvd_setdebug(BOOL ison) +{ + dvd_msg = ison == TRUE; +} + +BOOL dvd_getdebug() +{ + return dvd_msg; +} + +void dvd_fip_init() +{ + // write dvd-specific FIP stuff... + fip_write_special(FIP_SPECIAL_DVD, 1); + const char *digits = " 00000"; + fip_write_string(digits); + fip_write_special(FIP_SPECIAL_COLON1, 1); + fip_write_special(FIP_SPECIAL_COLON2, 1); +} + +/////////////////////////////////////////////////////////// + +int dvd_open(const char *path) +{ + if (dvdnav_open(&dvd, path) != DVDNAV_STATUS_OK) + { + msg_error("Media: Couldn't open DVD: %s\n", path); + return -1; + } + + if (dvdnav_set_readahead_flag(dvd, 1) != DVDNAV_STATUS_OK) + { + msg_error("Media: Error on set_readahead_flag: %s\n", dvdnav_err_to_string(dvd)); + return -2; + } + + // set the languages + if (dvdnav_menu_language_select(dvd, dvd_getdeflang_menu()) != DVDNAV_STATUS_OK + || dvdnav_audio_language_select(dvd, dvd_getdeflang_audio()) != DVDNAV_STATUS_OK + || dvdnav_spu_language_select(dvd, dvd_getdeflang_spu()) != DVDNAV_STATUS_OK) + { + msg_error("Media: Error on setting languages: %s\n", dvdnav_err_to_string(dvd)); + return -3; + } + + // set the PGC positioning flag to have position information relatively to the + // whole feature instead of just relatively to the current chapter + if (dvdnav_set_PGC_positioning_flag(dvd, 1) != DVDNAV_STATUS_OK) + { + msg_error("Media: Error on set_PGC_positioning_flag: %s\n", dvdnav_err_to_string(dvd)); + return -4; + } + dvdnav_set_cache_memory(dvd, mpeg_getbufbase()); + + dvd_make_crc_table(); + dvd_was_saved = false; + dvd_saved_pos = 0xffffffff; + dvd_saved_title = 0xffffffff; + dvd_ID = dvd_get_disc_ID(dvd_play_from_drive ? NULL : path); + dvd_get_saved(); + script_player_saved_callback(); + + return 0; +} + +int dvd_close() +{ + if (dvdnav_close(dvd) != DVDNAV_STATUS_OK) + return -1; + return 0; +} + +int dvd_reset() +{ + if (dvdnav_reset(dvd) != DVDNAV_STATUS_OK) + return -1; + return 0; +} + +////////////////////////////////////////////////////////////////// + +int dvd_play(const char *dvdpath, bool play_from_drive) +{ + fip_clear(); + fip_write_string("LoAd"); + + cur_titleid = 0; + cur_chapid = 0; + cur_titlpos = 0; + + old_titleid = 0; + old_chapid = 0; + + cur_cell_pts = 0; cur_cell_length = 0; cur_vobu_pts = 0; + old_cell_pts = 0; + cur_pgc_length = 0; + old_vob_id = -1; + old_vob_cell_id = -1; + vts_changed = false; + + dvd_not_played_yet = true; + dvd_waiting_to_play = false; + + dvd_mv_flags = -1; + + dvd = NULL; + cur_pci = NULL; + cur_dsi = NULL; + dvd_read_errors = 0; + no_spu = false; + + dvd_play_from_drive = play_from_drive; + + MEDIA_TYPES mt = MEDIA_TYPE_DVD; + + // Open the disc or file. + int ret = media_open(dvdpath, mt); + if (ret < 0) + { + MSG_ERROR("DVD: media open FAILED.\n"); + return ret; + } + + MSG("DVD: start...\n"); + + khwl_stop(); + khwl_display_clear(); + + mpeg_init(MPEG_2, FALSE, FALSE, FALSE); + mpeg_setbuffer(MPEG_BUFFER_1, mpeg_getbufbase(), 8, 32768); + +/////////////////////////////////////////////////// + + MPEG_PACKET_LENGTH = mpeg_getpacketlength(); + + num_packets = 0; + + pts_base = 0; + dvd_vobu_start_ptm = 0; + dvd_vobu_start_pos = 0; + need_to_set_pts = false; + need_to_check_pts = false; + saved_pts = 0; + vobu_sptm = 0; + + wait_for_user = false; + wait_pause = 0; + wait_time = 0; + wait_iter = 0; + + need_skip = false; + can_skip = false; + was_video = false; + num_skip = 0; + dvd_menu_ahead = false; + need_user_reset = false; + + dvd_scan = false; + + dvd_seamless = true; + dvd_speed = MPEG_SPEED_NORMAL; + + spu_command = 0; + + dvd_spu_enable(true); + + dvd_buttons_enable(false); + + dvd_spu_channel_letterbox = -1; + dvd_spu_channel_panscan = -1; + dvd_spu_stream = -1; dvd_old_spu_stream = -1; + dvd_aud_stream = -1; dvd_old_aud_stream = -1; + + cur_num_buttons = 0; + need_to_check_buttons = true; + + new_frame_size = false; + dvd_video_info_cnt = 0; + dvd_lastbtnupdate = false; + + forced_spu = -1; + forced_spu_lang = 0xffff; + forced_audio = -1; + forced_audio_lang = 0xffff; + +/////////////////////////////////////////// + SPSafeFree(dvdlang_lut); + dvdlang_lut = (BYTE *)SPcalloc(65536); + for (int i = 0; i < 256; i++) + { + dvdlang_lut[dvdlangs[i].code] = (BYTE)i; + if (dvdlangs[i].code == 0xffff) + break; + } + + MSG("DVD: Setting default audio params.\n"); + MpegAudioPacketInfo defaudioparams; + defaudioparams.type = eAudioFormat_AC3; + defaudioparams.samplerate = 48000; + defaudioparams.fromstream = 0; + mpeg_setaudioparams(&defaudioparams); + + dvd_use_mv = settings_get(SETTING_DVD_MV) != 0; + + dvd_fip_init(); + fip_write_special(FIP_SPECIAL_PLAY, 1); + fip_write_special(FIP_SPECIAL_PAUSE, 0); + + script_audio_info_callback(""); + script_video_info_callback(""); + + khwl_set_window_zoom(KHWL_ZOOMMODE_DVD); + + return 0; +} + +BOOL dvd_pause() +{ + dvd_button_pause(); + return TRUE; +} + +void dvd_clearhighlights() +{ + MSG("DVD: Clear highlights...\n"); + if (!no_spu) + { + static KHWL_SPU_BUTTON_TYPE but; + memset(&but, 0, sizeof(but)); + khwl_setproperty(KHWL_SUBPICTURE_SET, eSubpictureUpdateButton, sizeof(but), &but); + last_upd_but = but; + } +#if 0 + dvd_init_buttons_queue(); +#endif + dvd_button_selected = false; +} + +void dvd_spu_enable(bool onoff) +{ + if (!no_spu) + { + if (onoff) + spu_command |= KHWL_SPU_ENABLE; + else + spu_command &= ~(KHWL_SPU_ENABLE); + khwl_setproperty(KHWL_SUBPICTURE_SET, eSubpictureCmd, sizeof(spu_command), &spu_command); + } +} + +void dvd_buttons_enable(bool onoff) +{ + if (!no_spu) + { + if (onoff) + { + spu_command |= (KHWL_SPU_ENABLE | KHWL_SPU_BUTTONS_ENABLE); + } else + { + spu_command &= ~(KHWL_SPU_BUTTONS_ENABLE); + dvd_clearhighlights(); + } + khwl_setproperty(KHWL_SUBPICTURE_SET, eSubpictureCmd, sizeof(spu_command), &spu_command); + + dvd_buttons_enabled = onoff; + + MSG("DVD: SPU buttons: %s\n", onoff ? "enable" : "disable"); + } +} + +void dvd_update_button(int bn, int mode) +{ + /// we handle different aspect ratios (like dxr3 does) + if (cur_pci != NULL) + { + if (bn > 0 && bn <= (int)cur_pci->hli.hl_gi.btn_ns) + { + dvd_button_selected = true; + + btni_t *button_ptr = NULL; + int b1 = cur_pci->hli.hl_gi.btngr1_dsp_ty; + int b2 = cur_pci->hli.hl_gi.btngr2_dsp_ty; + int b3 = cur_pci->hli.hl_gi.btngr3_dsp_ty; + MSG("DVD: * button%d: gr1=%d gr2=%d gr3=%d (ngr=%d)\n", bn, b1, b2, b3, cur_pci->hli.hl_gi.btngr_ns); + + DWORD gr = 6, gr_res = 0; + // use a letterbox button group for letterboxed anamorphic menus on tv out + if (dvd_vmode == KHWL_VIDEOMODE_LETTERBOX && dvd_spu_channel_letterbox >= 0) + { + gr = 2; + gr_res = 2; + } + if (dvd_vmode == KHWL_VIDEOMODE_PANSCAN && dvd_spu_channel_panscan >= 0) + { + gr = 4; + gr_res = 4; + } + // (otherwise use a normal 4:3 or widescreen button group) + DWORD i, btns_per_group = cur_pci->hli.hl_gi.btngr_ns == 0 ? 0 : 36 / cur_pci->hli.hl_gi.btngr_ns; + for (i = 0; button_ptr == NULL && i < cur_pci->hli.hl_gi.btngr_ns; i++) + { + int dsp_ty = 0; + switch (i) + { + case 0: + dsp_ty = cur_pci->hli.hl_gi.btngr1_dsp_ty; + break; + case 1: + dsp_ty = cur_pci->hli.hl_gi.btngr2_dsp_ty; + break; + case 2: + dsp_ty = cur_pci->hli.hl_gi.btngr3_dsp_ty; + break; + } + if ((dsp_ty & gr) == gr_res) + button_ptr = &cur_pci->hli.btnit[i * btns_per_group + bn - 1]; + } + bool needs_scale = false; + if (button_ptr == NULL) // this is really bad - we aren't sure that our scaling is good! :-( + { + button_ptr = &cur_pci->hli.btnit[bn - 1]; + needs_scale = true; + MSG("DVD: * Using our scaling...\n"); + } else + { + MSG("DVD: * Button group %d matched!\n", i); + } + + /*if (dvdnav_get_highlight_area(cur_pci, bn, mode, &hl) == DVDNAV_STATUS_OK) + */ + if (button_ptr != NULL) + { + KHWL_SPU_BUTTON_TYPE but; + but.left = button_ptr->x_start; + but.top = button_ptr->y_start; + but.right = button_ptr->x_end+1; + but.bottom = button_ptr->y_end+1; + if (needs_scale) + { + int w, h; + mpeg_getframesize(&w, &h); + khwl_transformcoord(dvd_vmode, &but.left, &but.top, w, h); + khwl_transformcoord(dvd_vmode, &but.right, &but.bottom, w, h); + } + + int pal = 0; + if (button_ptr->btn_coln > 0 && mode > 0) + pal = cur_pci->hli.btn_colit.btn_coli[button_ptr->btn_coln-1][mode - 1]; + but.color = pal >> 16; + but.contrast = pal & 0xffff; + if (!no_spu) + { + khwl_setproperty(KHWL_SUBPICTURE_SET, eSubpictureUpdateButton, sizeof(but), &but); + last_upd_but = but; + dvd_lastbtnupdate = true; + } + + MSG("DVD: * update_button (%d %d %d %d, %08x)\n", but.left, but.top, but.right, but.bottom, pal); + } + } + } +} + + + +void dvd_getcharlang(BYTE *strlang, WORD lang) +{ + strlang[0] = (BYTE)((lang >> 8) & 0xff); + if (strlang[0] <= 32) + strlang[0] = '?'; + else if (strlang[0] == 0xff) + strlang[0] = '-'; + strlang[1] = (BYTE)(lang & 0xff); + if (strlang[1] <= 32) + strlang[1] = '?'; + else if (strlang[1] == 0xff) + strlang[1] = '-'; + strlang[2] = '\0'; +} + +void dvd_getstringlang(BYTE *strlang, WORD lang) +{ + strlang[0] = (BYTE)((lang >> 8) & 0xff); + if (strlang[0] <= 32) + strlang[0] = '?'; + else if (strlang[0] == 0xff) + strlang[0] = '-'; + strlang[1] = (BYTE)(lang & 0xff); + if (strlang[1] <= 32) + strlang[1] = '?'; + else if (strlang[1] == 0xff) + strlang[1] = '-'; + strlang[2] = '\0'; +} + +WORD dvd_getintlang(BYTE *strlang) +{ + if (strlang[0] <= 32 || strlang[1] <= 32) + return 0xffff; + WORD lang = (WORD)((tolower(strlang[0]) << 8) | tolower(strlang[1])); + return lang; +} + +void dvd_setdeflang_menu(char *l) +{ + deflang_menu = dvd_getintlang((BYTE *)l); +} +char *dvd_getdeflang_menu() +{ + static char l[3]; + dvd_getcharlang((BYTE *)l, deflang_menu); + return l; +} +void dvd_setdeflang_audio(char *l) +{ + deflang_audio = dvd_getintlang((BYTE *)l); +} + +char *dvd_getdeflang_audio() +{ + static char l[3]; + dvd_getcharlang((BYTE *)l, deflang_audio); + return l; +} +void dvd_setdeflang_spu(char * l) +{ + deflang_spu = dvd_getintlang((BYTE *)l); +} +char *dvd_getdeflang_spu() +{ + static char l[3]; + dvd_getcharlang((BYTE *)l, deflang_spu); + return l; +} + +////////////////////////////////////////////////// + +int dvd_get_spu_mode(KHWL_VIDEOMODE vmode) +{ + if (vmode == KHWL_VIDEOMODE_LETTERBOX) + return 1; + else if (vmode == KHWL_VIDEOMODE_PANSCAN) + return 2; + // wide + return 0; +} + +int dvd_get_spu_stream_from_user(int stream_id, WORD lang) +{ + if (stream_id < 0 || lang == 0xffff) + return -1; + // first, test saved stream id + int logstream = dvd_get_spu_logical_stream(dvd, (BYTE)stream_id, dvd_get_spu_mode(dvd_vmode)); + WORD slang = logstream >= 0 ? dvdnav_spu_stream_to_lang(dvd, (BYTE)logstream) : (WORD)0xffff; + if (logstream >= 0 && slang == lang) + return stream_id; + // it seems that the streams were changed - find by language + for (int stream = 0; stream < 32; stream++) + { + logstream = dvd_get_spu_logical_stream(dvd, (BYTE)stream, dvd_get_spu_mode(dvd_vmode)); + slang = logstream >= 0 ? dvdnav_spu_stream_to_lang(dvd, (BYTE)logstream) : (WORD)0xffff; + if (logstream >= 0 && slang == lang) + return stream; + } + return -1; +} + +int dvd_get_audio_stream_from_user(int stream_id, WORD lang) +{ + if (stream_id < 0 || lang == 0xffff) + return -1; + // first, test saved stream id + int logstream = dvd_get_audio_logical_stream(dvd, (BYTE)stream_id); + WORD slang = logstream >= 0 ? dvdnav_audio_stream_to_lang(dvd, (BYTE)logstream) : (WORD)0xffff; + if (logstream >= 0 && slang == lang) + return stream_id; + // it seems that the streams were changed - find by language + for (int stream = 0; stream < 8; stream++) + { + logstream = dvd_get_audio_logical_stream(dvd, (BYTE)stream); + slang = logstream >= 0 ? dvdnav_audio_stream_to_lang(dvd, (BYTE)logstream) : (WORD)0xffff; + if (logstream >= 0 && slang == lang) + return stream; + } + return -1; +} + +BOOL dvd_setspeed(MPEG_SPEED_TYPE speed) +{ + if (dvd_speed == speed && speed != MPEG_SPEED_STEP) + { + MSG("DVD: Speed %d already set!\n", speed); + return FALSE; + } + + if (!mpeg_setspeed(speed)) + return FALSE; + + dvd_speed = speed; + + need_skip = false; + num_skip = 0; + + // restore pts + if (speed == MPEG_SPEED_NORMAL) + { + need_to_set_pts = true; + } else + { + dvd_clearhighlights(); + need_user_reset = true; + } + + // set 'fip' symbols + if (dvd_speed == MPEG_SPEED_PAUSE) + { + fip_write_special(FIP_SPECIAL_PLAY, 0); + fip_write_special(FIP_SPECIAL_PAUSE, 1); + } + else if (dvd_speed == MPEG_SPEED_STEP) + { + fip_write_special(FIP_SPECIAL_PLAY, 1); + fip_write_special(FIP_SPECIAL_PAUSE, 1); + } + else if (dvd_speed != MPEG_SPEED_STOP) + { + fip_write_special(FIP_SPECIAL_PLAY, 1); + fip_write_special(FIP_SPECIAL_PAUSE, 0); + } + + if ((dvd_speed & MPEG_SPEED_FAST_FWD_MASK) == MPEG_SPEED_FAST_FWD_MASK + || (dvd_speed & MPEG_SPEED_FAST_REV_MASK) == MPEG_SPEED_FAST_REV_MASK) + { + need_skip = true; + dvdnav_set_readahead_flag(dvd, 0); + } else + { + dvdnav_set_readahead_flag(dvd, 1); + } + + MSG("DVD: Speed set to %d\n", dvd_speed); + + return TRUE; +} + +MPEG_SPEED_TYPE dvd_getspeed() +{ + return dvd_speed; +} + +void dvd_user_reset() +{ + if (dvd_speed != MPEG_SPEED_NORMAL) + { + MSG("DVD: Resetting speed to normal.\n"); + dvd_setspeed(MPEG_SPEED_NORMAL); + script_speed_callback(); + } + if (mpeg_zoom_reset()) + { + MSG("DVD: Reset zoom&scroll to 0.\n"); + script_zoom_scroll_reset_callback(); + } + need_user_reset = false; +} + +#define VALID_XWDA(offset) \ + (((offset) & SRI_END_OF_CELL) != SRI_END_OF_CELL && \ + ((offset) & 0x80000000)) + +BOOL dvd_skip(dsi_t *dsi) +{ + /* + float stime[19] = { + 120, // 0 | 18 + 60, // 1 | 17 + 30, // 2 | 16 + 10, // 3 | 15 + 7.5, // 4 | 14 + 7, // 5 | 13 + 6.5, // 6 | 12 + 6, // 7 | 11 + 5.5, // 8 | 10 + 5, // 9 | 9 + 4.5, // 10 | 8 + 4, // 11 | 7 + 3.5, // 12 | 6 + 3, // 13 | 5 + 2.5, // 14 | 4 + 2, // 15 | 3 + 1.5, // 16 | 2 + 1, // 17 | 1 + 0.5 // 18 | 0 + }; + */ + + static int saved_bl = 0; + static int lastdelta = -16; + int skip = 0; + switch (dvd_speed) + { + case MPEG_SPEED_NORMAL: + saved_bl = 0; + return TRUE; + case MPEG_SPEED_FWD_8X: // 1 + skip = 18-dvd_skipoffs; + break; + case MPEG_SPEED_FWD_16X: // 2 + skip = 18-1-dvd_skipoffs; + break; + case MPEG_SPEED_FWD_32X: // 4 + skip = 18-3-dvd_skipoffs; + break; + case MPEG_SPEED_FWD_48X: // 6 + skip = 18-5-dvd_skipoffs; + break; + case MPEG_SPEED_REV_8X: + skip = 2+dvd_skipoffs; + break; + case MPEG_SPEED_REV_16X: + skip = 3+dvd_skipoffs; + break; + case MPEG_SPEED_REV_32X: + skip = 5+dvd_skipoffs; + break; + case MPEG_SPEED_REV_48X: + skip = 7+dvd_skipoffs; + break; + default: + return TRUE; + } + + int numbl = 0, lenbl = 0, delta = 0; + dvdnav_get_position(dvd, (unsigned int *)&numbl, (unsigned int *)&lenbl); + if (saved_bl != 0) + { + numbl = saved_bl; + //numbl -= dsi->dsi_gi.vobu_ea; + saved_bl = 0; + } else + { + if ((dvd_speed & MPEG_SPEED_FAST_FWD_MASK) == MPEG_SPEED_FAST_FWD_MASK) + { + while (skip <= 18 && !VALID_XWDA(dsi->vobu_sri.fwda[skip])) + skip++; + if (skip <= 18) + delta = (int)(dsi->vobu_sri.fwda[skip] & SRI_END_OF_CELL); + if (VALID_XWDA(dsi->vobu_sri.next_video)) + { + int nv = (int)(dsi->vobu_sri.next_video & SRI_END_OF_CELL); + if (delta < nv) + delta = nv; + } + } + else if ((dvd_speed & MPEG_SPEED_FAST_REV_MASK) == MPEG_SPEED_FAST_REV_MASK) + { + while (skip >= 0 && !VALID_XWDA(dsi->vobu_sri.bwda[skip])) + skip--; + if (skip >= 0) + delta = -(int)(dsi->vobu_sri.bwda[skip] & SRI_END_OF_CELL); + lastdelta = delta != 0 ? delta : -16; + } + + numbl += delta; + //if (delta < 0 && skip == 0) + if (delta <= 0 && skip <= 0) + { + numbl += lastdelta; // TODO: is it correct? + saved_bl = numbl; + dvdnav_prev_pg_search(dvd); + MSG("DVD: jump_prev_page!\n"); + return TRUE; + //dvdnav_get_position(dvd, (unsigned int *)&newnumbl, (unsigned int *)&lenbl); + //numbl += newnumbl + lenbl; + } else + saved_bl = 0; + + can_skip = false; + was_video = false; + } + if (numbl >= lenbl) + { + can_skip = false; + was_video = false; + return FALSE; + } + + MSG("DVD: (skip=%2d numbl=%8d)\n", skip, numbl); + + if (dvdnav_sector_search(dvd, numbl, SEEK_SET) != DVDNAV_STATUS_OK) + return FALSE; + dvd_seamless = false; + + return TRUE; +} + +void dvd_update_info() +{ + static int old_secs = 0; + unsigned int pos, len; + KHWL_TIME_TYPE displ; + displ.pts = 0; + displ.timeres = 90000; + + if (mpeg_is_displayed()) + khwl_getproperty(KHWL_TIME_SET, etimVideoFrameDisplayedTime, sizeof(displ), &displ); + +#if 0 + dvd_update_buttons(displ.pts); +#endif + if (old_titleid != cur_titleid || old_chapid != cur_chapid || (LONGLONG)displ.pts != saved_pts) + { + if (cur_titleid >= 0 && cur_chapid >= 0 && (LONGLONG)displ.pts >= 0) + { + if (cur_titleid != old_titleid) + { + uint64_t tim; + dvd_get_time(dvd, -1, -1, &tim); + int titlelength = (int)(tim / 90000); + script_totaltime_callback(titlelength); + + int numchapters = 0; + dvdnav_get_number_of_parts(dvd, cur_titleid, &numchapters); + script_dvd_title_callback(cur_titleid, numchapters); + } + if (cur_chapid != old_chapid) + { + script_dvd_chapter_callback(cur_chapid); + } + + old_titleid = cur_titleid; + old_chapid = cur_chapid; + saved_pts = displ.pts; + + dvdnav_get_position_in_title(dvd, &pos, &len); + dvd_vobu_start_pos = pos; + MSG("DVD: -- Title %d, Chapter %d, %d/%d --\n", cur_titleid, cur_chapid, pos, len); + + if (len != 0) + cur_titlpos = 100 * pos / len; + + char fip_out[10]; + int secs = (int)(displ.pts / 90000); + //int secs = (int)((displ.pts + old_cell_pts - pts_base) / 90000); + int ccid = cur_chapid; + if (ccid < 0) + ccid = 0; + if (ccid > 99) + ccid = 99; + if (secs < 0) + secs = 0; + if (secs >= 10*3600) + secs = 10*3600-1; + if (secs != old_secs) + { + script_time_callback(secs); + old_secs = secs; + } + fip_out[0] = (char)((ccid / 10) + '0'); + fip_out[1] = (char)((ccid % 10) + '0'); + fip_out[2] = (char)((secs/3600) + '0'); + int secs3600 = secs%3600; + fip_out[3] = (char)(((secs3600/60)/10) + '0'); + fip_out[4] = (char)(((secs3600/60)%10) + '0'); + fip_out[5] = (char)(((secs3600%60)/10) + '0'); + fip_out[6] = (char)(((secs3600%60)%10) + '0'); + fip_out[7] = '\0'; + /* + sprintf(fip_out, "%02d%1d%02d%02d", ccid, secs/3600, (secs%3600)/60, + (secs%3600)%60); + */ + fip_write_string(fip_out); + } + } +} + +void dvd_update_audio_info(BYTE *buf, int len) +{ + const char *afmt[] = { "Unknown", "MPEG-L1", "MPEG-L2", "Dolby AC3", "PCM", "DTS" }; + MpegAudioPacketInfo apinfo = mpeg_getaudioparams(); + SPString info, subinfo; + if (apinfo.type > 5) + apinfo.type = eAudioFormat_UNKNOWN; + info.Printf("%s", afmt[apinfo.type]); + int lfe = 0, bitrate = 0; + if (apinfo.type != eAudioFormat_UNKNOWN) + { + if (apinfo.type == eAudioFormat_AC3) + { + apinfo.samplerate = 0; + apinfo.numberofchannels = 0; + mpeg_parse_ac3_header(buf, len, &bitrate, NULL/*&apinfo.samplerate*/, + &apinfo.numberofchannels, &lfe, NULL); + } + if (bitrate > 0) + { + subinfo.Printf("%d kbps ", (bitrate + 500) / 1000); + } + if (apinfo.samplerate > 0) + { + subinfo.Printf("%d Hz ", apinfo.samplerate); + } + if (apinfo.numberofbitspersample > 0) + { + subinfo.Printf("%d bits ", apinfo.numberofbitspersample, + apinfo.numberofchannels == 1 ? "mono" : "stereo"); + } + if (apinfo.numberofchannels > 0) + { + if (apinfo.numberofchannels > 2) + subinfo.Printf("%d.%d", apinfo.numberofchannels, lfe); + else + subinfo.Printf("%s", apinfo.numberofchannels == 1 ? "mono" : "stereo"); + } + if (subinfo != "") + info = info + " @ " + subinfo; + } + + script_audio_info_callback(info); +} + +void dvd_update_video_info() +{ + const char *vfmt[] = { "Unknown", "MPEG-1", "MPEG-2", "", "MPEG-4" }; + SPString info; + int vf = mpeg_get_video_format(); + if (vf > 3) + vf = 0; + + info.Printf("%s", vfmt[vf]); + + int rate = (8*mpeg_getrate(FALSE) + 500) / 1000; + if (rate > 0) + info.Printf(" @ %d kbps", rate); + + script_video_info_callback(info); +} + +void dvd_get_cur(int *title, int *chapter) +{ + *title = cur_titleid; + *chapter = cur_chapid; +} + +int dvd_player_loop_internal(bool feed) +{ + int feed_cnt = 0; +get_some_more: + + bool was_feed = false; + + // if user paused before play actually started... + if (dvd_waiting_to_play) + { + dvd_update_info(); + return 0; + } + + dvd_not_played_yet = false; + + if (wait_for_user || wait_pause > 0) + { + // if no more packets + if (mpeg_wait(TRUE)) + { + // update last button highlight + // Some new SPU data could be sent since the last highlight event + // (or resolution/ratio could be changed...) + if (!no_spu && last_upd_but.right != 0 && last_upd_but.bottom != 0) + { + if (dvd_lastbtnupdate) + { + MSG("DVD: Last button update...\n"); + dvd_lastbtnupdate = false; + } + + khwl_setproperty(KHWL_SUBPICTURE_SET, eSubpictureUpdateButton, sizeof(last_upd_but), &last_upd_but); + + // update 1 time + //last_upd_but.right = 0; + } + } + dvd_update_info(); + } + // wait for user commands + if (wait_for_user) + return 0; + + if (wait_pause > 0) + { + const int wait_delay = 300; + int sl = wait_pause < wait_delay ? wait_pause * 1000 : wait_delay * 1000; + usleep(sl); + // now get current 'time' + ULONGLONG newt = clock() * 1000 / CLOCKS_PER_SEC; + + //wait_pause -= (int)(newt - wait_time); + wait_pause -= 300; + + wait_time = newt; + wait_iter++; + if (wait_pause <= 0) // at last! + { + wait_pause = 0; + dvdnav_still_skip(dvd); + MSG("DVD: ...skipped: %d cycles.\n", wait_iter); + } else + return 0; + } + + BYTE *buf = NULL; + + MEDIA_EVENT event = MEDIA_EVENT_OK; + int len = 0; + + int ret = media_get_next_block(&buf, &event, &len); + if (ret > 0 && buf == NULL) + { + MSG_ERROR("DVD: Not initialized. STOP!\n"); + return 1; + } + BYTE *base = buf; + + if (ret == -1) + { + if (need_skip) + { + // perhaps, there was a NAV packet error, so just skip a sector... + if (dvd_skip_sector(dvd) == 0) + return 0; + } + dvd_read_errors++; + MSG_ERROR("DVD: Error getting next block (tries=%d): %s\n", dvd_read_errors, media_geterror()); + need_to_set_pts = true; + if (dvd_read_errors > 3) + { + dvd_read_errors = 0; + if (dvdnav_current_title_info(dvd, &cur_titleid, &cur_chapid) && cur_titleid > 0) + { + dvdnav_part_play(dvd, cur_titleid, ++cur_chapid); + MSG_ERROR("DVD: Too much errors! Skip to next part...\n"); + int next_cur_titleid, next_cur_chapid; + if (!dvdnav_current_title_info(dvd, &next_cur_titleid, &next_cur_chapid)) + next_cur_chapid = -1; + if (cur_chapid != next_cur_chapid) + { + MSG_ERROR("DVD: Cannot skip to next part. Stop playing...\n"); + return 1; + } + return 0; + } else + { + MSG_ERROR("DVD: Too much errors! Stop playing...\n"); + return 1; + } + } + return -1; + } else + if (ret == 0) // wait... + return 0; + if (dvd_read_errors > 0) + dvd_read_errors--; + + if (dvd_scan) + dvd_seamless = false; + +#ifdef DVD_PACKETS_DEBUG + MSG("DVD: ==packet %d (%08x)\n", event, base); +#endif + + switch ((int)event) + { + case DVDNAV_BLOCK_OK: + { + // if started paused, don't fill buffer + if (dvd_speed == MPEG_SPEED_PAUSE && num_packets == 0) + { + return 0; + } + + START_CODE_TYPES sct; + LONGLONG scr = 0; + int cnt = 0; + while ((sct = mpeg_findpacket(buf, base, cnt)) != START_CODE_END) + { + cnt = 0; + bool datapacket = false; + switch (sct) + { + case START_CODE_PACK: + scr = mpeg_parse_program_stream_pack_header(buf, base); + scr += pts_base; + if (scr < 0) + scr = 0; + // this is needed for 'khwl' + scr |= SPTM_SCR_FLAG; + break; + case START_CODE_SYSTEM: + case START_CODE_PCI: + { + int len = (buf[0] << 8) | buf[1]; + if (!mpeg_incParsingBufIndex(buf, base, len + 2)) + goto brk; + break; + } + case START_CODE_MPEG_VIDEO: + case START_CODE_MPEG_AUDIO1: + case START_CODE_MPEG_AUDIO2: + case START_CODE_PRIVATE1: + datapacket = true; + break; + default:; + }; + + if (datapacket) + { + if (dvd_scan) + break; + MpegPacket *packet = NULL; + + if (sct != START_CODE_MPEG_VIDEO && dvd_speed != MPEG_SPEED_NORMAL) + break; + + packet = mpeg_feed_getlast(); + if (packet == NULL) // well, it won't really help + break; + + memset((BYTE *)packet + 4, 0, sizeof(MpegPacket) - 4); + + // delayed stream change for forced streams detection... + if (sct == START_CODE_PRIVATE1 && dvd_spu_stream != dvd_old_spu_stream) + { + /*if (dvd_menu_ahead) // force ">0x80" streams? + dvd_spu_stream &= 0x1f; + mpeg_setspustream(dvd_spu_stream); + MSG("DVD: Set SPU stream: %d (%d)\n", dvd_spu_stream, dvd_old_spu_stream); + dvd_old_spu_stream = dvd_spu_stream; + */ + } + + // delayed stream change for forced streams detection... + /* + if (sct != START_CODE_MPEG_VIDEO && dvd_aud_stream != dvd_old_aud_stream) + { + dvd_old_aud_stream = dvd_aud_stream; + if (dvd_menu_ahead) // force ">0x80" streams? + dvd_aud_stream &= 0x1f; + mpeg_setaudiostream(dvd_aud_stream); + MSG("DVD: Set Audio stream: %d (%d)\n", dvd_aud_stream, dvd_old_aud_stream); + dvd_old_aud_stream = dvd_aud_stream; + } + */ + + packet->pts = pts_base; + if (mpeg_extractpacket(buf, base, packet, sct, FALSE) != 1) + break; + + // sometimes, we get 'fake' packets that 'khwl' doesn't like + if (packet->size < 1) + break; + + if (mpeg_audio_format_changed()) + { + dvd_update_audio_info(buf, 7); + } + + if (packet->flags == 2) + { + if (need_to_check_pts) + { + //if (mpeg_detect_pts_wrap(pts)) + //{ + // need_to_set_pts = true; + // vobu_sptm = packet->pts; + //} + } + + if (new_frame_size) + { + int w, h; + mpeg_getframesize(&w, &h); + if (w != 0 && h != 0) + { + script_framesize_callback(w, h); + new_frame_size = false; + } + int fps = mpeg_get_fps(); + if (fps > 0) + script_framerate_callback(fps); + } + + if (dvd_video_info_cnt++ > 63) + { + dvd_update_video_info(); + dvd_video_info_cnt = 0; + } + } + + packet->scr = scr; + packet->vobu_sptm = (vobu_sptm + pts_base) | SPTM_SCR_FLAG; + + if (feed) + { + if (packet->flags == 2 && packet->type == 0) + { + //MSG("DVD: setpts(%d)\n", packet->pts); + //mpeg_setpts(packet->pts); +#if 0 + LONGLONG stc = mpeg_getpts(); + LONGLONG good_scr = packet->pts; + if (dvd_speed == MPEG_SPEED_NORMAL && stc > good_scr + dvd_good_delta_stc) + { +#ifndef WIN32 + //khwl_pause(); + khwl_stop(); + //pts_base += stc - good_scr; + mpeg_start(); + msg("DVD: Resync (%d > %d)...\n", (int)stc, (int)good_scr); +#endif + } +#endif + + if (need_to_set_pts) + need_to_set_pts = false; + } + + // increase bufidx + mpeg_setbufidx(MPEG_BUFFER_1, packet); + + + if (packet->type == 0 && packet->pts != 0) + { +#ifdef DVD_PTS_DEBUG +#ifdef WIN32 + MSG("DVD: %d (%d) %I64d = %I64d [%I64d]\n", +#else + MSG("DVD: %d (%d) %Ld = %Ld [%Ld]\n", +#endif + num_packets, packet->type, packet->pts - pts_base, + packet->pts, packet->scr & ~SPTM_SCR_FLAG); +#endif + was_video = true; + } + + // send to khwl + mpeg_feed((MPEG_FEED_TYPE)packet->type); + was_feed = true; + + } else + { + // skip + } + num_packets++; + break; // do not find any more packets in this block + } + } +brk: ; + } + break; + case DVDNAV_NOP: + // Nothing to do here. + break; + case DVDNAV_STILL_FRAME: + /* We have reached a still frame. A real player application would wait + * the amount of time specified by the still's length while still handling + * user input to make menus and other interactive stills work. + * A length of 0xff means an indefinite still which has to be skipped + * indirectly by some user interaction. */ + { + if (feed) + { + dvdnav_still_event_t *still_event = (dvdnav_still_event_t *)buf; + if (still_event->length < 0xff) + { + // we'll finish after the next few cycles + wait_time = clock() * 1000 / CLOCKS_PER_SEC; + wait_pause = still_event->length * 1000; + wait_iter = 0; + + MSG("DVD: Waiting %d seconds of still frame\n", still_event->length); + } + else + { + wait_for_user = true; + MSG("DVD: Indefinite length still frame - waiting for user interaction...\n"); + } + dvd_seamless = false; + + if (dvd_speed != MPEG_SPEED_NORMAL) + { + dvd_setspeed(MPEG_SPEED_NORMAL); + script_speed_callback(); + } + mpeg_play(); + + } else + dvdnav_still_skip(dvd); + } + break; + + case DVDNAV_WAIT: + /* We have reached a point in DVD playback, where timing is critical. + * Player application with internal fifos can introduce state + * inconsistencies, because libdvdnav is always the fifo's length + * ahead in the stream compared to what the application sees. + * Such applications should wait until their fifos are empty + * when they receive this type of event. */ + if (feed) + { + MSG("DVD: Skipping wait condition\n"); + mpeg_wait(FALSE); + mpeg_start(); + + MSG("DVD: Skipping DONE\n"); + } + dvdnav_wait_skip(dvd); + break; + + case DVDNAV_SPU_CLUT_CHANGE: + if (feed) + { + if (dvd_speed == MPEG_SPEED_NORMAL) + { + MSG("DVD: CLUT change\n"); + dvd_clearhighlights(); + + need_to_check_buttons = true; + + int *clut = (int *)buf; + if (!no_spu) + { + for (int i = 0; i < 16; i++) + clut[i] = bswap_32(clut[i]); + + khwl_setproperty(KHWL_SUBPICTURE_SET, eSubpictureUpdatePalette, sizeof(KHWL_SPU_PALETTE_ENTRY) * 16, buf); + } + } + } + break; + + case DVDNAV_SPU_STREAM_CHANGE: + { + if (feed) + { + if (dvd_speed == MPEG_SPEED_NORMAL) + { + dvd_clearhighlights(); + + + need_to_check_buttons = true; + + dvdnav_spu_stream_change_event_t *stream_event = + (dvdnav_spu_stream_change_event_t *) buf; + int stream; + if (dvd_vmode == KHWL_VIDEOMODE_LETTERBOX) + { + stream = stream_event->physical_letterbox; + dvd_spu_channel_letterbox = stream; + } + else if (dvd_vmode == KHWL_VIDEOMODE_PANSCAN) + { + stream = stream_event->physical_pan_scan; + dvd_spu_channel_panscan = stream; + } + else + stream = stream_event->physical_wide; + bool in_menu = dvdnav_is_domain_vts(dvd) == 0; + int logstream = (stream & 0x80) == 0 ? + dvd_get_spu_logical_stream(dvd, (BYTE)stream, dvd_get_spu_mode(dvd_vmode)) : -1; + WORD lang = logstream >= 0 ? dvdnav_spu_stream_to_lang(dvd, (BYTE)logstream) : (WORD)0xffff; + BYTE strlang[3]; + dvd_getcharlang(strlang, lang); + MSG("DVD: SPU stream event: %d (%d %d %d), log=%d, lang='%c%c'\n", stream, + stream_event->physical_wide, stream_event->physical_letterbox, stream_event->physical_pan_scan, + logstream, strlang[0], strlang[1]); + + // reset saved pos if user entered menu + if (in_menu) + { + dvd_savepos(true); + } + + // \TODO: handle dvd_menu_ahead + + // drop current stream if not user-defined + if (!in_menu && (stream & 0x80) == 0x80 && forced_spu == -1) + { + dvd_spu_stream = -1; + lang = 0xffff; + logstream = -1; + MSG("DVD: - Dropping SPU stream to %d (was %d)\n", dvd_spu_stream, dvd_old_spu_stream); + } + // accept any stream in menu or only valid language streams otherwise + else if ((/*logstream >= 0 && lang != 0xffff && */forced_spu == -1) || in_menu) + { + dvd_spu_stream = stream; + MSG("DVD: - Set SPU stream: %d (was %d)\n", dvd_spu_stream, dvd_old_spu_stream); +/* + if (!in_menu) + { + forced_spu = -1; + forced_spu_lang = 0xffff; + MSG("DVD: - Resetting user's subtitle choice.\n"); + } +*/ + } + // set user-forced value in all other cases + else + { + dvd_spu_stream = dvd_get_spu_stream_from_user(forced_spu, forced_spu_lang); + + logstream = dvd_get_spu_logical_stream(dvd, (BYTE)dvd_spu_stream, dvd_get_spu_mode(dvd_vmode)); + lang = logstream >= 0 ? dvdnav_spu_stream_to_lang(dvd, (BYTE)logstream) : (WORD)0xffff; + dvd_getcharlang(strlang, lang); + + MSG("DVD: - User-ignoring SPU stream! (user set %d,'%c%c')\n", dvd_spu_stream, strlang[0], strlang[1]); + } + + script_spu_stream_callback(logstream >= 0 ? logstream+1 : 0); + script_spu_lang_callback(dvdlangs[dvdlang_lut[lang]].str); + + mpeg_setspustream(dvd_spu_stream); + dvd_old_spu_stream = dvd_spu_stream; + } + } + break; + } + + case DVDNAV_AUDIO_STREAM_CHANGE: + { + if (feed) + { + if (dvd_speed == MPEG_SPEED_NORMAL) + { + dvdnav_audio_stream_change_event_t *stream_event = + (dvdnav_audio_stream_change_event_t *) buf; + BYTE strlang[3]; + + int stream = stream_event->physical; + bool in_menu = dvdnav_is_domain_vts(dvd) == 0; + int logstream = dvd_get_audio_logical_stream(dvd, (BYTE)stream); + WORD lang = logstream >= 0 ? dvdnav_audio_stream_to_lang(dvd, (BYTE)logstream) : (WORD)0xffff; + dvd_getcharlang(strlang, lang); + + MSG("DVD: Audio stream change event (%d), log=%d, lang='%c%c'\n", stream, + logstream, strlang[0], strlang[1]); + // we don't care about the language, we just need a valid logical stream. + if (forced_audio == -1/*logstream >= 0*/ || in_menu) + { + dvd_aud_stream = stream; + MSG("DVD: - Audio stream change: %d\n", stream); +/* + if (!in_menu) + { + forced_audio = -1; + forced_audio_lang = 0xffff; + MSG("DVD: - Resetting user's audio choice.\n"); + } +*/ + } + else + { + dvd_aud_stream = dvd_get_audio_stream_from_user(forced_audio, forced_audio_lang); + + logstream = dvd_get_audio_logical_stream(dvd, (BYTE)dvd_spu_stream); + lang = logstream >= 0 ? dvdnav_audio_stream_to_lang(dvd, (BYTE)logstream) : (WORD)0xffff; + dvd_getcharlang(strlang, lang); + + MSG("DVD: - User-ignoring audio stream! (user set %d,'%c%c')\n", dvd_aud_stream, strlang[0], strlang[1]); + } + + script_audio_stream_callback(logstream >= 0 ? logstream+1 : 0); + script_audio_lang_callback(dvdlangs[dvdlang_lut[lang]].str); + + mpeg_setaudiostream(dvd_aud_stream); + } + } + break; + } + + case DVDNAV_HIGHLIGHT: + { + if (feed) + { + //dvdnav_highlight_area_t hl; + dvdnav_highlight_event_t *highlight_event = (dvdnav_highlight_event_t *)buf; + MSG("DVD: Selected button %d (pts=%d)\n", highlight_event->buttonN, (int)highlight_event->pts); + + //dvdnav_get_current_highlight(dvd, &dvd_cur_button); + dvd_cur_button = highlight_event->buttonN; + + dvd_update_button(highlight_event->buttonN, highlight_event->display); + /*dvd_add_button(highlight_event->buttonN, highlight_event->display, + (LONGLONG)(signed int)highlight_event->pts + pts_base); + */ + + // try updating immediately? + //if (mpeg_is_playing()) + // dvd_update_info(); + } + break; + } + + case DVDNAV_VTS_CHANGE: + { + // Some status information like video aspect and video scale permissions do + // not change inside a VTS. Therefore this event can be used to query such + // information only when necessary and update the decoding/displaying accordingly. + + vts_changed = true; + if (feed) + { + int aspect = dvdnav_get_video_aspect(dvd); + int permission = dvdnav_get_video_scale_permission(dvd); + + const char *asp[] = { "4:3", "? (#1)", "? (#2)", "16:9" }; + MSG("DVD: VTS change. Aspect = %s (Perm = %x)\n", asp[aspect], permission); + + if (dvd_speed == MPEG_SPEED_NORMAL) + khwl_stop(); + + dvd_buttons_enable(false); + + need_to_check_buttons = true; + + cur_titleid = 0; + cur_chapid = 0; + + // change video mode + int tvtype = settings_get(SETTING_TVTYPE); + dvd_vmode = KHWL_VIDEOMODE_NORMAL; + if (aspect == 0) + { + if (tvtype == 0 || tvtype == 1) + dvd_vmode = KHWL_VIDEOMODE_NORMAL; + else + { + if ((permission & 3) == 0) + dvd_vmode = (tvtype == 2) ? KHWL_VIDEOMODE_HCENTER : KHWL_VIDEOMODE_VCENTER; + else if ((permission & 2) == 2) + dvd_vmode = KHWL_VIDEOMODE_HCENTER; + else + dvd_vmode = KHWL_VIDEOMODE_VCENTER; + } + } + else if (aspect == 3) + { + if (tvtype == 2 || tvtype == 3) + dvd_vmode = KHWL_VIDEOMODE_WIDE; + else + { + if ((permission & 3) == 0) + dvd_vmode = (tvtype == 0) ? KHWL_VIDEOMODE_LETTERBOX : KHWL_VIDEOMODE_PANSCAN; + else if ((permission & 1) == 1) + dvd_vmode = KHWL_VIDEOMODE_PANSCAN; + else + dvd_vmode = KHWL_VIDEOMODE_LETTERBOX; + + } + } + MSG("DVD: set vmode = %d\n", dvd_vmode); + + khwl_display_clear(); + khwl_setvideomode(dvd_vmode, TRUE); + new_frame_size = true; + + script_dvd_menu_callback(dvdnav_is_domain_vts(dvd) != 1); + + if (dvd_speed == MPEG_SPEED_NORMAL) + { + mpeg_resetstreams(); + mpeg_start(); + } + dvd_seamless = false; + } + break; + } + + case DVDNAV_CELL_CHANGE: + /* Some status information like the current Title and Part numbers do not + * change inside a cell. Therefore this event can be used to query such + * information only when necessary and update the decoding/displaying + * accordingly. */ + { + dvdnav_cell_change_event_t *cell_change = (dvdnav_cell_change_event_t *)buf; + + int tt, ptt; + if (dvdnav_current_title_info(dvd, &tt, &ptt) == DVDNAV_STATUS_ERR) + break; + //dvdnav_get_position(dvd, (uint32_t *)&pos, (uint32_t *)&len); + MSG("DVD: Cell change event: (Title %d, Chapter %d)\n", tt, ptt); + //MSG("DVD: Title %d, Chapter %d\n", cell_change->pgN, cell_change->cellN); + MSG("DVD: - p_s=%d p_l=%d pgc_l=%d c_s=%d c_l=%d\n", (int)cell_change->pg_start, (int)cell_change->pg_length, + (int)cell_change->pgc_length, (int)cell_change->cell_start, (int)cell_change->cell_length); + + cur_titleid = tt;//cell_change->pgN; + cur_chapid = ptt;//cell_change->cellN; + + cur_cell_pts = cell_change->cell_start; + cur_cell_length = cell_change->cell_length; + cur_pgc_length = cell_change->pgc_length; + + if (feed) + { + if (dvd_speed == MPEG_SPEED_NORMAL) + { + dvd_update_info(); + need_to_check_pts = true; + + } + //MSG("DVD: At position %.0f%%%% inside the feature\n", 100 * (double)pos / (double)len); + } + break; + } + + case DVDNAV_NAV_PACKET: + /* A NAV packet provides PTS discontinuity information, angle linking information and + * button definitions for DVD menus. Angles are handled completely inside libdvdnav. + * For the menus to work, the NAV packet information has to be passed to the overlay + * engine of the player so that it knows the dimensions of the button areas. */ + { + /* Applications with fifos should not use these functions to retrieve NAV packets, + * they should implement their own NAV handling, because the packet you get from these + * functions will already be ahead in the stream which can cause state inconsistencies. + * Applications with fifos should therefore pass the NAV packet through the fifo + * and decoding pipeline just like any other data. */ + + cur_pci = dvdnav_get_current_nav_pci(dvd); + cur_dsi = dvdnav_get_current_nav_dsi(dvd); + + if (was_video) + { + was_video = false; + can_skip = true; + } + + //if (!need_skip) + { + LONGLONG cell_pts = old_cell_pts; + bool vob_changed = false; + if (cur_dsi->dsi_gi.vobu_vob_idn != old_vob_id || + cur_dsi->dsi_gi.vobu_c_idn != old_vob_cell_id || vts_changed) + { + //if (!vts_changed) + cell_pts = cur_cell_pts; + + // patch for weird DVDs... + if (cur_pci->pci_gi.vobu_s_ptm > cell_pts) + cell_pts = 0; + + if (cur_dsi->dsi_gi.vobu_vob_idn != old_vob_id) + { + MSG("DVD: ** VOB changed!\n"); + vob_changed = true; + } + + old_vob_id = cur_dsi->dsi_gi.vobu_vob_idn; + old_vob_cell_id = cur_dsi->dsi_gi.vobu_c_idn; + MSG("DVD: ** VOBU: cell changed! newvts=%d\n", (int)vts_changed); + MSG("DVD: * vob_id: %d, cell_id: %d\n", (int)cur_dsi->dsi_gi.vobu_vob_idn, + (int)cur_dsi->dsi_gi.vobu_c_idn); + MSG("DVD: * start=%d, end=%d (old=%d), cell_pts=%d)\n", + (int)cur_pci->pci_gi.vobu_s_ptm, (int)cur_pci->pci_gi.vobu_e_ptm, + (int)dvd_vobu_last_ptm, (int)cur_cell_pts); + MSG("DVD: * vob: %d, s=%d, e=%d\n", (int)cur_vobu_pts, (int)cur_dsi->sml_pbi.vob_v_s_s_ptm, + (int)cur_dsi->sml_pbi.vob_v_e_e_ptm); + + if (cell_pts < pts_base) + { + if (!dvd_seamless && !need_skip && !dvd_scan && dvd_speed == MPEG_SPEED_NORMAL) + { + khwl_stop(); + mpeg_start(); + } + } + + // see if we have more than 1 angle + int ca, na = 1; + dvdnav_get_angle_info(dvd, &ca, &na); + if (na != number_of_angles) + { + MSG("DVD: Angles detected: %d!\n", na); + + fip_write_special(FIP_SPECIAL_CAMERA, na > 1 ? 1 : 0); + + // turn off read-ahead? + if (na > 1) + { + MSG("DVD: * read-ahead = off\n"); + dvdnav_set_readahead_flag(dvd, 0); + } + else if (!need_skip) + { + MSG("DVD: * read-ahead = on\n"); + dvdnav_set_readahead_flag(dvd, 1); + } + + number_of_angles = na; + } + +#if 0 + // save current position on cell boundary + if (cur_vobu_pts != 0) + dvd_savepos(false); +#endif + } + vts_changed = false; + old_cell_pts = cell_pts; + + cur_vobu_pts = cur_pci->pci_gi.vobu_s_ptm + cell_pts; + + if (cur_pci->pci_gi.vobu_s_ptm != dvd_vobu_last_ptm) + { + vobu_sptm = cur_pci->pci_gi.vobu_s_ptm; + LONGLONG delta = dvd_vobu_last_ptm - vobu_sptm; + + LONGLONG oldptsbase = pts_base; + // don't change pts for multi-angle vobu changes + if (dvd_seamless) + { + if (number_of_angles <= 1) + pts_base += delta; + + MSG("DVD: * pts_base=%d (old=%d)\n", (int)pts_base, (int)oldptsbase); + } + else + { + if (delta != 0 && !need_skip && !dvd_scan && dvd_speed == MPEG_SPEED_NORMAL) + { + khwl_stop(); + mpeg_start(); + need_to_set_pts = true; + } + LONGLONG et = dvdnav_convert_time(&cur_pci->pci_gi.e_eltm); // &cur_dsi->dsi_gi.c_eltm + + pts_base = et + cur_cell_pts - vobu_sptm; + + MSG("DVD: Cell PTS = %d + %d, VOBU_s_ptm=%d.\n", + (int)et, (int)cur_cell_pts, (int)vobu_sptm); + MSG("DVD: * pts_base = %d (old=%d)\n", (int)pts_base, + (int)oldptsbase); + + } + + dvd_seamless = true; + +#ifdef DVD_USE_MACROVISION + // Macrovision APS + int apstb = (cur_pci->pci_gi.vobu_cat >> 14) & 3; + int mv_flags = (mv_cgms_flags << 2) | apstb; + if (dvd_mv_flags != mv_flags) + { + if (dvd_use_mv) + { + MSG("DVD: Macrovision flag (%d) set.\n", mv_flags); + khwl_setproperty(KHWL_VIDEO_SET, evMacrovisionFlags, sizeof(int), &mv_flags); + } else + { + MSG("DVD: Macrovision flag (%d) SKIPPED.\n", mv_flags); + } + dvd_mv_flags = mv_flags; + } +#endif + + if (delta != 0) + { + /*need_to_set_pts = true;*/ + } + } + dvd_vobu_start_ptm = cur_pci->pci_gi.vobu_s_ptm; + dvd_vobu_last_ptm = cur_pci->pci_gi.vobu_e_ptm; + + } + + if (need_skip && can_skip) + { + /////////// TODO: this is experimental! +#if 0 + KHWL_TIME_TYPE displ; + displ.pts = 0; + displ.timeres = 90000; + khwl_getproperty(KHWL_TIME_SET, etimVideoFrameDisplayedTime, sizeof(displ), &displ); + if (displ.pts >= (ULONGLONG)pts) +#endif + { + if (!dvd_skip(cur_dsi)) + { + MSG_ERROR("DVD: Error seeking next block/program: %s\n", media_geterror()); + media_free_block(base); + dvd_setspeed(MPEG_SPEED_NORMAL); + script_speed_callback(); + return -1; + } + } + break; + } + + if (feed) + { + int button; + + // compare current buttons and old buttons + + bool thesame = true; + if (cur_num_buttons != cur_pci->hli.hl_gi.btn_ns) + thesame = false; + else if (need_to_check_buttons) + { + for (button = 0; button < cur_num_buttons; button++) + { + DVD_BUTTON &b = cur_buttons[button]; + btni_t *btni = &(cur_pci->hli.btnit[button]); + if (b.left != btni->x_start || b.top != btni->y_start || + b.right != btni->x_end || b.bottom != btni->y_end) + { + thesame = false; + break; + } + } + } + need_to_check_buttons = false; + + if (cur_pci->hli.hl_gi.hli_ss == 1) + { + MSG("DVD: ! Menu ahead (was=%d)!\n", dvd_menu_ahead); + // patch for non-accurate menus + //if (dvd_menu_ahead && (cur_num_buttons == cur_pci->hli.hl_gi.btn_ns)) + // thesame = true; + dvd_menu_ahead = true; + + if (forced_spu != -1) + { + MSG("DVD: Resetting user's subtitle choice to -1\n"); + forced_spu = -1; + forced_spu_lang = 0xffff; + } + if (forced_audio != -1) + { + MSG("DVD: Resetting user's audio choice to -1\n"); + forced_audio = -1; + forced_audio_lang = 0xffff; + } + + // special 'allow all' mode + if (dvd_spu_stream < 0) + { + MSG("DVD: Enabling 'allow-all-streams' SPU mode.\n"); + mpeg_setspustream(-2); + } + + need_user_reset = true; + + } else + { + if (dvd_menu_ahead) + { + MSG("DVD: ! Menu end!\n"); + dvd_menu_ahead = false; + + // restore current SPU stream + MSG("DVD: Restoring SPU stream to %d.\n", dvd_spu_stream); + mpeg_setspustream(dvd_spu_stream); + } + } + + if (!dvdnav_is_domain_vts(dvd) && need_user_reset) + { + dvd_user_reset(); + } + + if (!thesame) + cur_num_buttons = cur_pci->hli.hl_gi.btn_ns; + + if (cur_pci->hli.hl_gi.btn_ns > 0) + { + if (!thesame) + { + MSG("DVD: Found %i DVD menu buttons...\n", cur_pci->hli.hl_gi.btn_ns); + + for (button = 0; button < cur_pci->hli.hl_gi.btn_ns; button++) + { + btni_t *btni = &(cur_pci->hli.btnit[button]); + MSG("DVD: Button %i top-left @ (%i,%i), bottom-right @ (%i,%i)\n", + button + 1, btni->x_start, btni->y_start, btni->x_end, btni->y_end); + + cur_buttons[button].left = btni->x_start; + cur_buttons[button].top = btni->y_start; + cur_buttons[button].right = btni->x_end; + cur_buttons[button].bottom = btni->y_end; + } + } + if (!dvd_buttons_enabled) + dvd_buttons_enable(true); + if (cur_pci->hli.hl_gi.fosl_btnn > 0) + { + if (dvd_cur_button != cur_pci->hli.hl_gi.fosl_btnn) + { + MSG("DVD: * force button = %d!\n", cur_pci->hli.hl_gi.fosl_btnn); + } + dvd_cur_button = cur_pci->hli.hl_gi.fosl_btnn; + + } + if (!dvd_button_selected) + { + //dvdnav_get_current_highlight(dvd, &dvd_cur_button); + MSG("DVD: * selecting button = %d:\n", dvd_cur_button); + if (dvd_cur_button > cur_num_buttons) + dvd_cur_button = cur_num_buttons - 1; + else if (dvd_cur_button < 1) + dvd_cur_button = 1; + dvdnav_button_select(dvd, cur_pci, dvd_cur_button); + } + + } else + { + if (dvd_buttons_enabled) + dvd_buttons_enable(false); + } + dvd_update_info(); + } + break; + } + + case DVDNAV_HOP_CHANNEL: + { + // This event is issued whenever a non-seamless operation has been executed. + // Applications with fifos should drop the fifos content to speed up responsiveness. + + if (feed) + { + if (dvd_speed == MPEG_SPEED_NORMAL) + { + MSG("DVD: HOP channel!\n"); + + khwl_stop(); + dvd_buttons_enable(false); + need_to_check_buttons = true; + + mpeg_start(); + + // \TODO: this is a test... + mpeg_setpts(0); + dvd_seamless = false; + } + } + break; + } + + case DVDNAV_STOP: + { + // Playback should end here. + if (feed) + { + MSG("DVD: STOP!!!\n"); + } + // we may not free the block + dvd_savepos(true); + return 1; + } + + default: // skip + MSG_ERROR("DVD: Unknown event (%i)\n", event); + break; + } + + // don't forget to free not used block! + // NOTE: it does nothing if our cache is used! + media_free_block(base); + + if (was_feed && feed_cnt++ < 2) + goto get_some_more; + + return 0; +} + +int dvd_player_loop() +{ + return dvd_player_loop_internal(true); +} + +int dvd_get_next_block(BYTE **buf, int *event, int *len) +{ + struct timeval tv1, tv2; + // measure time (see below) + BOOL need_measure = (dvd_speed == MPEG_SPEED_NORMAL) ? mpeg_is_playing() : FALSE; + if (need_measure) + gettimeofday(&tv1, NULL); + +#ifndef DVD_USE_CACHE + if (dvdnav_get_next_block(dvd, *buf, event, len) == DVDNAV_STATUS_ERR) + return -1; + return 1; +#else + dvdnav_reset_cache_flag(dvd); + if (dvdnav_get_next_cache_block(dvd, buf, (int *)event, len) == DVDNAV_STATUS_ERR) + return -1; + + // we check if decoder waits too long - audio/video mistiming will happen then (only raw-read counts) + if (need_measure && *event == DVDNAV_BLOCK_OK) + { + // we don't check mpeg_feed_getstackdepth(). It may not be empty if long delay occurs. + // we just check if we read from disk way too long... + gettimeofday(&tv2, NULL); + int read_dtime = (int)((tv2.tv_sec - tv1.tv_sec) * 1000 + (tv2.tv_usec - tv1.tv_usec) / 1000); + if (read_dtime > read_max_allowed_time) + { + msg("DVD: Disc read Time (%d msec) > limit!\n", read_dtime); + khwl_stop(); + mpeg_start(); + msg("DVD: Resync...\n"); + } + } + + return dvdnav_get_cache_flag(dvd); +#endif +} + +int dvd_free_block(BYTE *data) +{ +#ifdef DVD_USE_CACHE + if (dvdnav_free_cache_block(dvd, data) != DVDNAV_STATUS_OK) + return -1; +#endif + return 0; +} + +BOOL dvd_stop() +{ + mpeg_deinit(); + + dvd_savepos(false); + dvd_not_played_yet = true; + + SPSafeFree(dvdlang_lut); + + dvd_spu_enable(false); + + if (dvd != NULL) + { + if (media_close() < 0) + { + MSG_ERROR("DVD: Error on dvdnav_close: %s\n", dvdnav_err_to_string(dvd)); + return FALSE; + } + } + + fip_clear(); + dvd_fip_init(); + khwl_setvideomode(KHWL_VIDEOMODE_NONE, TRUE); + + dvd = NULL; + + cur_pack = -1; + + return TRUE; +} + +bool dvd_end_still(bool skipstill = true) +{ + dvd_user_reset(); + /* + if (wait_for_user) + need_to_set_pts = true; + */ + wait_for_user = false; + bool ret = false; + if (wait_pause > 0) + { + if (skipstill) + { + MSG("DVD: Fast-skipping still frame...\n"); + dvdnav_still_skip(dvd); + // we don't need to do anything more... + ret = true; + } + wait_pause = 0; + } + + mpeg_start(); + return ret; +} + +int dvd_getnumtitles() +{ + int titl = 0; + dvdnav_get_number_of_titles(dvd, &titl); + return titl; +} + +int dvd_getnumchapters(int title) +{ + int chap; + dvdnav_get_number_of_parts(dvd, title, &chap); + return chap; +} + +BOOL dvd_seek_titlepart(int title, int part) +{ + if (title < 1 || part < 1) + return FALSE; + dvd_end_still(); + dvd_setspeed(MPEG_SPEED_NORMAL); + script_speed_callback(); + + MSG("DVD: seek to title %d, part %d.\n", title, part); + + if (!dvdnav_is_domain_vts(dvd)) + { + MSG_ERROR("DVD: Cannot seek - not in play!\n"); + return FALSE; + } + if (dvdnav_part_play(dvd, title, part) != DVDNAV_STATUS_OK) + { + MSG_ERROR("DVD: seek error.\n"); + return FALSE; + } + return TRUE; +} + +BOOL dvd_seek(int seconds) +{ + if (seconds < 0) + return FALSE; + + dvd_end_still(); + dvd_setspeed(MPEG_SPEED_NORMAL); + script_speed_callback(); + MSG("DVD: Seek to %d secs...\n", seconds); + + LONGLONG newpts = seconds * 90000; + + if (dvdnav_time_search(dvd, newpts) != DVDNAV_STATUS_OK) + { + MSG_ERROR("DVD: seek error.\n"); + return FALSE; + } + +#ifdef MY_SEARCH + bool simpleseek = false; + if (!dvdnav_is_domain_vts(dvd)) + { + MSG_ERROR("DVD: Cannot seek - not in play!\n"); + return FALSE; + //simpleseek = true; + } + + bool needs_restore = false; + LONGLONG cur_vobu_pts0 = 0; + int base_titleid = 1, base_chapid = 1; + int num_parts = 0; + int raf = 0; + + dvdnav_current_title_info(dvd, &cur_titleid, &cur_chapid); + base_titleid = cur_titleid; + base_chapid = cur_chapid; + + /// \TODO: don't start from beginning in all cases + if (dvdnav_title_play(dvd, cur_titleid) != DVDNAV_STATUS_OK) + goto err; + dvdnav_current_title_info(dvd, &cur_titleid, &cur_chapid); + dvdnav_get_number_of_parts(dvd, cur_titleid, &num_parts); + + old_cell_pts = 0; + old_vob_id = -1; + + // use navigation info + dvd_scan = true; + + dvdnav_get_readahead_flag(dvd, &raf); + dvdnav_set_readahead_flag(dvd, 0); + + //if (newpts < cur_vobu_pts || !simpleseek) + { + cur_vobu_pts = -1; + while (cur_vobu_pts == -1) + dvd_player_loop_internal(false); + } + cur_vobu_pts0 = cur_vobu_pts; + + //while (true) + { + // search start chapter + LONGLONG last_ptm = cur_cell_pts; + while (cur_cell_pts < newpts) + { + if (last_ptm > cur_cell_pts) + { + needs_restore = true; + goto err; + } + last_ptm = cur_cell_pts; + MSG("DVD: jump to part %d...\n", cur_chapid); + dvdnav_part_play(dvd, cur_titleid, ++cur_chapid); + cur_cell_pts = -1; + while (cur_cell_pts == -1) + dvd_player_loop_internal(false); + if (cur_chapid >= num_parts) + break; + } + if (cur_chapid > 0) + { + dvdnav_part_play(dvd, cur_titleid, --cur_chapid); + cur_cell_pts = -1; + while (cur_cell_pts == -1) + dvd_player_loop_internal(false); + } + // now we know the chapter! + const int numdirs = 3; + int dir = 0, sks[3] = { 18-16, 12, 18-4 }; + int lastskip = 400; + // zig-zag iterations + while (true) + { + int from = SEEK_CUR, skip = 0, sk = sks[dir]; + if (dir % 2 == 0) + { + while (sk < 19 && !VALID_XWDA(cur_dsi->vobu_sri.fwda[sk])) + sk++; + if (sk < 19) + skip = cur_dsi->vobu_sri.fwda[sk] & SRI_END_OF_CELL; + else + skip = lastskip; + lastskip = skip; + if (skip == 0) + break; + } else + { + while (sk > 0 && !VALID_XWDA(cur_dsi->vobu_sri.bwda[sk])) + sk--; + if (sk > 0) + skip = cur_dsi->vobu_sri.bwda[sk] & SRI_END_OF_CELL; + else + skip = lastskip; + lastskip = skip; + if (skip == 0) + break; + + unsigned int curpos = 0, curlen = 0; + if (dvdnav_get_position(dvd, &curpos, &curlen) != DVDNAV_STATUS_OK) + goto err; + skip = curpos - skip; + from = SEEK_SET; + } + + if (media_seek(dvd, skip, from) == -1) + goto err; + cur_vobu_pts = -1; + while (cur_vobu_pts == -1) + dvd_player_loop_internal(false); + + if (dir == numdirs - 1 && cur_vobu_pts >= newpts) + break; + else if ((dir % 2) == 0 && cur_vobu_pts >= newpts) + dir++; + else if ((dir % 2) == 1 && cur_vobu_pts < newpts) + dir++; + + } + } +err: + if (needs_restore) + { + MSG_ERROR("DVD: Cannot seek - restore pos!\n"); + dvdnav_part_play(dvd, base_titleid, base_chapid); + //media_rewind(dvd); + } + dvdnav_set_readahead_flag(dvd, raf); + dvd_scan = false; +#endif + + MSG("DVD: seek DONE!\n"); + + khwl_stop(); + dvd_buttons_enable(false); + //need_to_set_pts = true; + mpeg_start(); + + dvd_seamless = false; + + return TRUE; + +#if 0 + dvd_scan = true; + while (mpeg_getrate() == 0) + dvd_player_loop_internal(false); + dvd_scan = false; + + int rate = mpeg_getrate(); + MSG("DVD: mpeg_rate = %d\n", rate); + + int delta, from; + LONGLONG old_pts = dvd_vobu_start_ptm;//= pts - pts_base; + if (old_pts != 0 && simpleseek) + { + LONGLONG d = (newpts - old_pts) * (rate * 50) / 90000; + delta = (int)(d / 2048); // align to packet's boundary + + delta += dvd_vobu_start_pos; + + from = SEEK_SET;//SEEK_CUR; + + } else + { + LONGLONG d = newpts * (rate * 50) / 90000; + delta = (int)(d / 2048); // align to packet's boundary + from = SEEK_SET; + } + if (media_seek(dvd, delta, from) == -1) + return FALSE; +#endif + + return TRUE; +} + + +void dvd_button_press() +{ + dvd_end_still(false); + if (cur_pci != NULL) + { + MSG("DVD: Activating current button...\n"); + // handle mode2 updates + int button; + dvdnav_get_current_highlight(dvd, &button); + dvd_update_button(button, 2); + + dvdnav_button_activate(dvd, dvdnav_get_current_nav_pci(dvd)); + + } +} + +void dvd_button_up() +{ + dvd_end_still(false); + if (cur_pci != NULL) + { + MSG("DVD: Selecting upper button...\n"); + dvdnav_upper_button_select(dvd, dvdnav_get_current_nav_pci(dvd)); + } +} + +void dvd_button_down() +{ + dvd_end_still(false); + if (cur_pci != NULL) + { + MSG("DVD: Selecting lower button...\n"); + dvdnav_lower_button_select(dvd, dvdnav_get_current_nav_pci(dvd)); + } +} + +void dvd_button_left() +{ + dvd_end_still(false); + if (cur_pci != NULL) + { + MSG("DVD: Selecting left button...\n"); + dvdnav_left_button_select(dvd, dvdnav_get_current_nav_pci(dvd)); + } +} + +void dvd_button_right() +{ + dvd_end_still(false); + if (cur_pci != NULL) + { + MSG("DVD: Selecting right button...\n"); + dvdnav_right_button_select(dvd, dvdnav_get_current_nav_pci(dvd)); + } +} + +void dvd_button_menu(DVD_MENU_TYPE menu) +{ + dvd_end_still(false); + if (menu == DVD_MENU_DEFAULT) + { + if (dvdnav_is_domain_vts(dvd)) + { + dvdnav_menu_call(dvd, DVD_MENU_Root/*DVD_MENU_Part*/); // we want VTSM, not VMGM ! + } else + dvdnav_menu_call(dvd, DVD_MENU_Escape); + } else + dvdnav_menu_call(dvd, (DVDMenuID_t)menu); + +} + +bool dvd_ismenu() +{ + return dvdnav_is_domain_vts(dvd) != 1; +} + +void dvd_button_angle(int setangl) +{ + int num = 0, current = 0; + dvdnav_get_angle_info(dvd, ¤t, &num); + + if (num != 0) + { + if (setangl >= 0) + current = setangl; + else + { + current++; + if (current > num) + current = 1; + } + if (dvdnav_angle_change(dvd, current)) + return; + } + dvd_invalid(); +} + +int dvd_getangle() +{ + int num = 0, current = 0; + dvdnav_get_angle_info(dvd, ¤t, &num); + return current; +} + +void dvd_button_audio(char *setlang, int startfrom) +{ + if (dvdnav_is_domain_vts(dvd)) + { + WORD setl = 0xffff; + if (setlang == NULL) + { + MSG("DVD: dvd_audio (%d)\n", startfrom); + setl = 0xffff; + } + else + { + MSG("DVD: dvd_audio %s\n", setlang); + setl = dvd_getintlang((BYTE *)setlang); + } + int audiostream = -1, logstream = -1; + WORD lang = 0xffff, firstlang = 0xffff; + bool wasany = false, found = false; + int firstaudiostream = -1, firstlogstream = -1; + int curaudiostream = mpeg_getaudiostream(); + for (audiostream = startfrom; audiostream < 8; audiostream++) + { + logstream = dvd_get_audio_logical_stream(dvd, (BYTE)audiostream); + lang = logstream >= 0 ? dvdnav_audio_stream_to_lang(dvd, (BYTE)logstream) : (WORD)0xffff; + if (logstream >= 0 /*&& lang != 0xffff*/) + { + wasany = true; + if (firstaudiostream == -1) + { + firstaudiostream = audiostream; + firstlogstream = logstream; + firstlang = lang; + } + if ((setl == 0xffff && audiostream > curaudiostream) || + (setl != 0xffff && lang == setl)) + { + found = true; + break; + } + } + } + if (!wasany) + { + MSG("DVD: No audio streams found.\n"); + dvd_invalid(); + return; + } + // set to the first if we didn't found any valid stream after the current + if (!found) + { + audiostream = firstaudiostream; + logstream = firstlogstream; + lang = firstlang; + } +/* + // -2 means that we don't want any audio + forced_audio = audiostream == -1 ? -2 : audiostream; + forced_audio_lang = lang; + mpeg_setaudiostream(audiostream); +*/ + dvd_set_audio_stream(dvd, audiostream); + + BYTE strlang[3]; + dvd_getcharlang(strlang, lang); + MSG("DVD: setaudiostream = %d, lang='%c%c'\n", audiostream, strlang[0], strlang[1]); + script_audio_stream_callback(logstream >= 0 ? logstream+1 : 0); + script_audio_lang_callback(dvdlangs[dvdlang_lut[lang]].str); + + } else + dvd_invalid(); +} + +void dvd_button_subtitle(char *setlang, int startfrom) +{ + if (dvdnav_is_domain_vts(dvd)) + { + WORD setl = 0xffff; + int spustream = -1, logstream = -1, curstream = -1; + if (setlang == NULL) + { + MSG("DVD: dvd_subtitle (%d)\n", startfrom); + setl = 0xffff; + + curstream = mpeg_getspustream(); + logstream = dvd_get_spu_logical_stream(dvd, (BYTE)curstream, dvd_get_spu_mode(dvd_vmode)); + if (logstream < 0) + curstream = -1; + } + else + { + MSG("DVD: dvd_subtitle %s\n", setlang); + setl = dvd_getintlang((BYTE *)setlang); + } + WORD lang = 0xffff; + bool wasany = false, found = false; + for (spustream = startfrom; spustream < 32; spustream++) + { + logstream = dvd_get_spu_logical_stream(dvd, (BYTE)spustream, dvd_get_spu_mode(dvd_vmode)); + lang = logstream >= 0 ? dvdnav_spu_stream_to_lang(dvd, (BYTE)logstream) : (WORD)0xffff; + if (logstream >= 0/* && lang != 0xffff*/) + { + wasany = true; + if ((setl == 0xffff && spustream > curstream) || + (setl != 0xffff && lang == setl)) + { + found = true; + break; + } + } + } + if (!wasany) + { + MSG("DVD: No subtitle streams found.\n"); + dvd_invalid(); + return; + } + // switch off if we didn't found any valid stream after the current + if (!found) + { + spustream = -1; + logstream = -1; + lang = 0xffff; + } +/* + // -2 means that we don't want any subtitles + forced_spu = spustream == -1 ? -2 : spustream; + forced_spu_lang = lang; + mpeg_setspustream(spustream); +*/ + dvd_set_spu_stream(dvd, spustream, dvd_get_spu_mode(dvd_vmode)); + + BYTE strlang[3]; + dvd_getcharlang(strlang, lang); + MSG("DVD: setspustream = %d, lang='%c%c'\n", spustream, strlang[0], strlang[1]); + script_spu_stream_callback(logstream >= 0 ? logstream+1 : 0); + script_spu_lang_callback(dvdlangs[dvdlang_lut[lang]].str); + + } else + dvd_invalid(); +} + +void dvd_button_return() +{ + MSG("DVD: dvd_go_up\n"); + if (!dvdnav_is_domain_vts(dvd)) + { + dvd_end_still(); // true + if (!dvdnav_go_up(dvd)) + dvd_invalid(); + } else + { + dvd_invalid(); + } +} + + +void dvd_button_prev() +{ + if (!dvd_end_still()) + { + if (dvdnav_current_title_info(dvd, &cur_titleid, &cur_chapid) && cur_titleid > 0) + { + dvdnav_part_play(dvd, cur_titleid, --cur_chapid); + MSG("DVD: dvd_jump_prev\n"); + return; + } + dvd_invalid(); + } +} + +void dvd_button_next() +{ + if (!dvd_end_still()) + { + MSG("DVD: dvd_jump_next\n"); + if (dvdnav_current_title_info(dvd, &cur_titleid, &cur_chapid) && cur_titleid > 0) + { + if (!dvdnav_part_play(dvd, cur_titleid, ++cur_chapid)) + dvd_invalid(); + } else + { + if (!dvdnav_next_pg_search(dvd)) + dvd_invalid(); + } + } +} + +static void dvd_st_spd(MPEG_SPEED_TYPE spd, int mode) +{ + int sp = (int)spd; + if (mode > 0) + sp++; + else if (mode < 0) + sp--; + if (dvd_setspeed((MPEG_SPEED_TYPE)sp)) + { + MSG("DVD: dvd_speed = 0x%3x\n", sp); + } else + dvd_invalid(); +} + +void dvd_button_slow() +{ + if (dvdnav_is_domain_vts(dvd)) + { + MSG("DVD: dvd_button_slow\n"); + if (dvd_speed == MPEG_SPEED_SLOW_FWD_8X || dvd_speed == MPEG_SPEED_NORMAL) + dvd_st_spd(MPEG_SPEED_SLOW_FWD_2X, 0); + else if (dvd_speed == MPEG_SPEED_SLOW_REV_8X) + dvd_st_spd(MPEG_SPEED_SLOW_REV_2X, 0); + else if ((dvd_speed & MPEG_SPEED_SLOW_FWD_MASK) == MPEG_SPEED_SLOW_FWD_MASK) + dvd_button_rew(); + else if ((dvd_speed & MPEG_SPEED_SLOW_REV_MASK) == MPEG_SPEED_SLOW_REV_MASK) + dvd_button_fwd(); + else + dvd_invalid(); + } + else + dvd_invalid(); +} + +void dvd_button_fwd() +{ + if (dvdnav_is_domain_vts(dvd)) + { + if (dvd_speed == MPEG_SPEED_REV_8X || dvd_speed == MPEG_SPEED_NORMAL || dvd_speed == MPEG_SPEED_PAUSE) + dvd_st_spd(MPEG_SPEED_FWD_8X, 0); + else if (dvd_speed >= MPEG_SPEED_FWD_8X && dvd_speed < MPEG_SPEED_FWD_48X) + dvd_st_spd(dvd_speed, 1); + else if (dvd_speed > MPEG_SPEED_REV_8X && dvd_speed <= MPEG_SPEED_REV_48X) + dvd_st_spd(dvd_speed, -1); + // use it in slow mode too + else if (dvd_speed == MPEG_SPEED_SLOW_REV_2X) + dvd_st_spd(MPEG_SPEED_SLOW_FWD_2X, 0); + else if (dvd_speed > MPEG_SPEED_SLOW_FWD_2X && dvd_speed <= MPEG_SPEED_SLOW_FWD_8X) + dvd_st_spd(dvd_speed, -1); + else if (dvd_speed >= MPEG_SPEED_SLOW_REV_2X && dvd_speed < MPEG_SPEED_SLOW_REV_8X) + dvd_st_spd(dvd_speed, 1); + else + dvd_invalid(); + } else + dvd_invalid(); +} + +void dvd_button_rew() +{ + if (dvdnav_is_domain_vts(dvd)) + { + if (dvd_speed == MPEG_SPEED_FWD_8X || dvd_speed == MPEG_SPEED_NORMAL || dvd_speed == MPEG_SPEED_PAUSE) + dvd_st_spd(MPEG_SPEED_REV_8X, 0); + else if (dvd_speed >= MPEG_SPEED_REV_8X && dvd_speed < MPEG_SPEED_REV_48X) + dvd_st_spd(dvd_speed, 1); + else if (dvd_speed > MPEG_SPEED_FWD_8X && dvd_speed <= MPEG_SPEED_FWD_48X) + dvd_st_spd(dvd_speed, -1); + // use it in slow mode too + /* + // don't use slow rev mode - it's not ready?.. + else if (dvd_speed == MPEG_SPEED_SLOW_FWD_2X) + dvd_st_spd(MPEG_SPEED_SLOW_REV_2X, 0); + */ + else if (dvd_speed > MPEG_SPEED_SLOW_REV_2X && dvd_speed <= MPEG_SPEED_SLOW_REV_8X) + dvd_st_spd(dvd_speed, -1); + else if (dvd_speed >= MPEG_SPEED_SLOW_FWD_2X && dvd_speed < MPEG_SPEED_SLOW_FWD_8X) + dvd_st_spd(dvd_speed, 1); + else + dvd_invalid(); + } else + dvd_invalid(); +} + +void dvd_button_play() +{ + if (dvd_waiting_to_play) + { + dvd_waiting_to_play = false; + dvd_setspeed(MPEG_SPEED_NORMAL); + MSG("DVD: dvd_waiting_to_play = false\n"); + } + if (dvdnav_is_domain_vts(dvd)) + { + dvd_setspeed(MPEG_SPEED_NORMAL); + MSG("DVD: dvd_play\n"); + } else + dvd_invalid(); +} + +void dvd_button_pause() +{ + if (dvd_not_played_yet) + { + dvd_waiting_to_play = true; + dvd_setspeed(MPEG_SPEED_PAUSE); + MSG("DVD: dvd_waiting_to_play = true\n"); + return; + } else + if (dvdnav_is_domain_vts(dvd)) + { + if (dvd_speed == MPEG_SPEED_PAUSE || dvd_speed == MPEG_SPEED_STEP) + { + dvd_setspeed(MPEG_SPEED_STEP); + MSG("DVD: dvd_step\n"); + } + else + { + MSG("DVD: dvd_pause\n"); + dvd_setspeed(MPEG_SPEED_PAUSE); + dvd_savepos(false); + } + } else + dvd_invalid(); +} + +void dvd_button_step() +{ + if (dvdnav_is_domain_vts(dvd)) + { + dvd_setspeed(MPEG_SPEED_STEP); + MSG("DVD: dvd_step\n"); + } else + dvd_invalid(); +} + + +BOOL dvd_zoom_hor(int scale) +{ + if (dvdnav_is_domain_vts(dvd)) + { + MSG("DVD: Zoomed set to %d.\n", scale); + mpeg_zoom_hor(scale); + return TRUE; + } else + dvd_invalid(); + need_user_reset = true; + return FALSE; +} + +BOOL dvd_zoom_ver(int scale) +{ + if (dvdnav_is_domain_vts(dvd)) + { + MSG("DVD: Zoomed set to %d.\n", scale); + mpeg_zoom_ver(scale); + return TRUE; + } else + dvd_invalid(); + need_user_reset = true; + return FALSE; +} + +BOOL dvd_scroll(int offsetx, int offsety) +{ + if (dvdnav_is_domain_vts(dvd)) + { + MSG("DVD: Scroll set to %d,%d.\n", offsetx, offsety); + mpeg_scroll(offsetx, offsety); + return TRUE; + } else + dvd_invalid(); + need_user_reset = true; + return FALSE; +} + +int dvd_get_total_time(int title, int chapter, int *tim) +{ + uint64_t time; + int ret = dvd_get_time(dvd, title - 1, chapter - 1, &time); + *tim = (int)(time / 90000); + return ret; +} + +int dvd_get_chapter_for_time(int title, int time, int *chapter) +{ + int chap; + int ret = dvd_get_chapter(dvd, title - 1, (uint64_t)time * 90000, &chap); + *chapter = chap + 1; + return ret; +} + +char *dvd_error_string() +{ + return (char *)dvdnav_err_to_string(dvd); +} + +int dvd_savepos(bool reset) +{ + DWORD pos = 0, title = 0, lenbl = 0; + DWORD data1, data2; + if (reset) + { + pos = 0xffffffff; + title = 0xffffffff; + data1 = data2 = 0xffffffff; + // if already reset + if (dvd_saved_pos == pos && dvd_saved_title == title) + return 0; + MSG("DVD: Resetting saved movie position.\n"); + } else + { + if (!dvdnav_is_domain_vts(dvd)) + return -1; + if (dvdnav_get_position(dvd, (unsigned int *)&pos, (unsigned int *)&lenbl) != DVDNAV_STATUS_OK) + return -1; + int ch; + if (dvdnav_current_title_info(dvd, (int *)&title, &ch) != DVDNAV_STATUS_OK) + return FALSE; + + // in menu or somewhere? + if (pos == 0 || title == 0) + return -1; + + dvd_saved_vmode = dvd_vmode; + dvd_saved_audio_stream = mpeg_getaudiostream(); + dvd_saved_spu_stream = mpeg_getspustream(); + data1 = dvd_saved_vmode & 0xff; + data1 |= (((dvd_saved_audio_stream + 128) & 0xff) << 8); + data1 |= (((dvd_saved_spu_stream + 128) & 0xff) << 16); + } + + if (!dvd_was_saved && !reset) + { + DWORD id = settings_get(SETTING_DVD_ID1); + DWORD titlepos = settings_get(SETTING_DVD_POS1); + DWORD ddata1 = settings_get(SETTING_DVD_DATA11); + DWORD ddata2 = settings_get(SETTING_DVD_DATA12); + // shift other saved positions + // we use this loop order to detect saved pos and break - this saves the last item + for (int i = 1; i < dvd_num_saved; i++) + { + if (id == dvd_ID && titlepos != 0xffffffff) + break; + DWORD oldid = settings_get((SETTING_SET)(SETTING_DVD_ID1 + i * 4)); + DWORD oldtitlepos = settings_get((SETTING_SET)(SETTING_DVD_POS1 + i * 4)); + DWORD olddata1 = settings_get((SETTING_SET)(SETTING_DVD_DATA11 + i * 4)); + DWORD olddata2 = settings_get((SETTING_SET)(SETTING_DVD_DATA12 + i * 4)); + settings_set((SETTING_SET)(SETTING_DVD_ID1 + i * 4), id); + settings_set((SETTING_SET)(SETTING_DVD_POS1 + i * 4), titlepos); + settings_set((SETTING_SET)(SETTING_DVD_DATA11 + i * 4), ddata1); + settings_set((SETTING_SET)(SETTING_DVD_DATA12 + i * 4), ddata2); + id = oldid; + titlepos = oldtitlepos; + ddata1 = olddata1; + ddata2 = olddata2; + } + settings_set(SETTING_DVD_ID1, dvd_ID); + dvd_was_saved = true; + } + + dvd_saved_pos = pos; + dvd_saved_title = title; + settings_set(SETTING_DVD_POS1, ((title & 0xff) << 24) | (pos & 0xffffff)); + settings_set(SETTING_DVD_DATA11, data1); + script_player_saved_callback(); + MSG("DVD: Saving pos = %d/%d, vmode=%d, spu=%d, aud=%d\n", dvd_saved_title, dvd_saved_pos, + dvd_saved_vmode, dvd_saved_spu_stream, dvd_saved_audio_stream); + return 0; +} + +bool dvd_get_saved() +{ + dvd_saved_pos = 0xffffffff; + dvd_saved_title = 0xffffffff; + dvd_saved_vmode = -1; dvd_saved_audio_stream = -1; dvd_saved_spu_stream = -1; + for (int i = 0; i < dvd_num_saved; i++) + { + DWORD id = (DWORD)settings_get((SETTING_SET)(SETTING_DVD_ID1 + i * 4)); + if (dvd_ID == id) + { + DWORD titlepos = (DWORD)settings_get((SETTING_SET)(SETTING_DVD_POS1 + i * 4)); + if (titlepos != 0xffffffff) // not-empty slot + { + dvd_saved_pos = titlepos & 0xffffff; + dvd_saved_title = (titlepos >> 24) & 0xff; + DWORD data1 = (DWORD)settings_get((SETTING_SET)(SETTING_DVD_DATA11 + i * 4)); + dvd_saved_vmode = data1 & 0xff; + dvd_saved_audio_stream = ((data1 >> 8) & 0xff) - 128; + dvd_saved_spu_stream = ((data1 >> 16) & 0xff) - 128; + break; + } + } + } + return dvd_saved_pos != 0xffffffff && dvd_saved_title != 0xffffffff && dvd_saved_title != 0xff; +} + +BOOL dvd_continue_play() +{ + if (dvd_saved_pos == 0xffffffff || dvd_saved_title == 0xffffffff || dvd_saved_title == 0xff) + return FALSE; + + wait_for_user = false; + wait_pause = 0; + dvd_reset_vm(dvd); + dvdnav_title_play(dvd, dvd_saved_title); + + // TODO: find a better solution... + bool old_dvd_waiting_to_play = dvd_waiting_to_play; + dvd_waiting_to_play = false; + for (int i = 0; i < 10; i++) + { + dvd_player_loop_internal(false); + } + dvd_waiting_to_play = old_dvd_waiting_to_play; + + if (dvdnav_current_title_info(dvd, &cur_titleid, &cur_chapid) != DVDNAV_STATUS_OK) + return FALSE; + if (dvdnav_sector_search(dvd, dvd_saved_pos, SEEK_SET) != DVDNAV_STATUS_OK) + return FALSE; + + old_titleid = -1; + old_chapid = -1; + saved_pts = -1; + msg("DVD: Continue play from saved pos = %d, title=%d\n", dvd_saved_pos, dvd_saved_title); + + script_dvd_menu_callback(dvdnav_is_domain_vts(dvd) != 1); + + // set other saved params + msg("DVD: restore vmode = %d, aID=%d, sID=%d\n", dvd_saved_vmode, + dvd_saved_audio_stream, dvd_saved_spu_stream); + + // restore aspect ratio & vmode + dvd_vmode = (KHWL_VIDEOMODE)dvd_saved_vmode; + khwl_display_clear(); + khwl_setvideomode(dvd_vmode, TRUE); + new_frame_size = true; + + // restore audio stream + mpeg_setaudiostream(-1); + if (dvd_saved_audio_stream >= 0) + dvd_button_audio(NULL, dvd_saved_audio_stream); + // sanity check + if (mpeg_getaudiostream() < 0) + mpeg_setaudiostream(0); + + // restore SPU stream + mpeg_setspustream(-1); + if (dvd_saved_spu_stream >= 0) + dvd_button_subtitle(NULL, dvd_saved_spu_stream); + + return TRUE; +} diff --git a/src/dvd/dvd_css.c b/src/dvd/dvd_css.c new file mode 100644 index 0000000..94608a2 --- /dev/null +++ b/src/dvd/dvd_css.c @@ -0,0 +1,528 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - libdvdcss hardware CSS replacement + * (parts of code from libdvdcss) + * \file dvd/dvd_css.c + * \author bombur + * \version 0.1 + * \date 7.02.2006 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#include + +#include "config.h" + +#ifdef WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#pragma warning(disable: 4201) +#include +#pragma warning(default: 4201) +#else +#include +#include +#endif + +#include +#include +#include +#include + +#ifdef HAVE_SYS_PARAM_H +# include +#endif +#ifdef HAVE_UNISTD_H +# include +#endif +#include + +#ifdef HAVE_LIMITS_H +# include +#endif + +// these are from libdvdcss: +#include "dvdcss/dvdcss.h" +#include "common.h" +#include "css.h" +#ifdef WIN32 +#pragma warning(disable: 4200) +#pragma warning(disable: 4201) +#pragma warning(disable: 4214) +#endif +#include "ioctl.h" +#ifdef WIN32 +#pragma warning(default: 4200) +#pragma warning(default: 4201) +#pragma warning(default: 4214) +#endif +#include "device.h" +#include "libdvdcss.h" + +#include +#include + +///////////////////////////////////////////// +#ifdef DVDCSS_USE_EXTERNAL_CSS + +///////////////////////// +//#define OUR_DEBUG_OUTPUT +///////////////////////// + +#ifdef OUR_DEBUG_OUTPUT +void msg(char *text, ...); +#define debug_error(e) msg("dvdcss err: %s\n", e) +#define debug_msg(m) msg("dvdcss: %s\n", m) +#else +#define debug_error(e) +//_dvdcss_error( dvdcss, e) +#define debug_msg(m) +//_dvdcss_debug( dvdcss, m) +#endif + + +extern int mv_cgms_flags; + +/***************************************************************************** + * Local prototypes + *****************************************************************************/ +static int GetBusKey ( dvdcss_t ); +static int GetASF ( dvdcss_t ); +static int ReadTitleKey(int i_fd, int *pi_agid, int i_pos, uint8_t *p_key, int *cgms_flags); + + +/***************************************************************************** + * _dvdcss_test: check if the disc is encrypted or not + *****************************************************************************/ +int _dvdcss_test( dvdcss_t dvdcss ) +{ + int i_ret, i_copyright; + + i_ret = ioctl_ReadCopyright( dvdcss->i_fd, 0 /* i_layer */, &i_copyright ); + +#ifdef WIN32 + if( i_ret < 0 ) + { + /* Maybe we didn't have enough priviledges to read the copyright + * (see ioctl_ReadCopyright comments). + * Apparently, on unencrypted DVDs _dvdcss_disckey() always fails, so + * we can check this as a work-around. */ + i_ret = 0; + if( _dvdcss_disckey( dvdcss ) < 0 ) + i_copyright = 0; + else + i_copyright = 1; + } +#endif + + if( i_ret < 0 ) + { + /* Since it's the first ioctl we try to issue, we add a notice */ + debug_error("css error: ioctl_ReadCopyright failed, " + "make sure there is a DVD in the drive, and that " + "you have used the correct device node." ); + + return i_ret; + } + + return i_copyright; +} + +/***************************************************************************** + * GetBusKey : Go through the CSS Authentication process + ***************************************************************************** + * It simulates the mutual authentication between logical unit and host, + * and stops when a session key (called bus key) has been established. + * Always do the full auth sequence. Some drives seem to lie and always + * respond with ASF=1. For instance the old DVD roms on Compaq Armada says + * that ASF=1 from the start and then later fail with a 'read of scrambled + * block without authentication' error. + *****************************************************************************/ +static int GetBusKey( dvdcss_t dvdcss ) +{ + uint8_t p_challenge[2*KEY_SIZE]; + uint8_t p_challenge2[2*KEY_SIZE]; + dvd_key_t p_key1; + dvd_key_t p_key2; + char psz_warning[80]; + int i_ret = -1; + int i; + + debug_msg("requesting AGID" ); + i_ret = ioctl_ReportAgid( dvdcss->i_fd, &dvdcss->css.i_agid ); + + /* We might have to reset hung authentication processes in the drive + by invalidating the corresponding AGID'. As long as we haven't got + an AGID, invalidate one (in sequence) and try again. */ + for( i = 0; i_ret == -1 && i < 4 ; ++i ) + { + sprintf( psz_warning, + "ioctl ReportAgid failed, invalidating AGID %d", i ); + debug_msg(psz_warning ); + + /* This is really _not good_, should be handled by the OS. + Invalidating an AGID could make another process fail some + where in it's authentication process. */ + dvdcss->css.i_agid = i; + ioctl_InvalidateAgid( dvdcss->i_fd, &dvdcss->css.i_agid ); + + debug_msg("requesting AGID (2)" ); + i_ret = ioctl_ReportAgid( dvdcss->i_fd, &dvdcss->css.i_agid ); + } + + /* Unable to authenticate without AGID */ + if( i_ret == -1 ) + { + debug_error("ioctl ReportAgid failed, fatal" ); + return -1; + } + + /* Get challenge from host */ + khwl_getproperty(KHWL_DECODER_SET, edecCSSChlg, sizeof(p_challenge), p_challenge); + + /* Send challenge to LU */ + if( ioctl_SendChallenge( dvdcss->i_fd, + &dvdcss->css.i_agid, p_challenge) < 0 ) + { + debug_error("ioctl SendChallenge failed" ); + ioctl_InvalidateAgid( dvdcss->i_fd, &dvdcss->css.i_agid ); + return -1; + } + + /* Get key1 from LU */ + if( ioctl_ReportKey1( dvdcss->i_fd, &dvdcss->css.i_agid, p_key1) < 0) + { + debug_error("ioctl ReportKey1 failed" ); + ioctl_InvalidateAgid( dvdcss->i_fd, &dvdcss->css.i_agid ); + return -1; + } + + /* Send key1 to host */ + khwl_setproperty(KHWL_DECODER_SET, edecCSSKey1, KEY_SIZE, p_key1); + + /* Get challenge from LU */ + if( ioctl_ReportChallenge( dvdcss->i_fd, + &dvdcss->css.i_agid, p_challenge2) < 0 ) + { + debug_error("ioctl ReportKeyChallenge failed" ); + ioctl_InvalidateAgid( dvdcss->i_fd, &dvdcss->css.i_agid ); + return -1; + } + + /* Send challenge to host */ + khwl_setproperty(KHWL_DECODER_SET, edecCSSChlg2, sizeof(p_challenge2), p_challenge2); + + /* Get key2 from host */ + khwl_getproperty(KHWL_DECODER_SET, edecCSSKey2, sizeof(p_key2), p_key2); + + /* Send key2 to LU */ + if( ioctl_SendKey2( dvdcss->i_fd, &dvdcss->css.i_agid, p_key2) < 0 ) + { + debug_error("AUTH: ioctl SendKey2 failed" ); + ioctl_InvalidateAgid( dvdcss->i_fd, &dvdcss->css.i_agid ); + return -1; + } + + /* The drive has accepted us as authentic. */ + debug_msg("AUTH established" ); + + return 0; +} + +/***************************************************************************** + * _dvdcss_title: crack or decrypt the current title key if needed + ***************************************************************************** + * This function should only be called by dvdcss->pf_seek and should eventually + * not be external if possible. + *****************************************************************************/ +int _dvdcss_title ( dvdcss_t dvdcss, int i_block ) +{ + dvd_key_t p_title_key; + int i_ret = -1; + +// debug_msg("_dvdcss_title() start" ); + + if( ! dvdcss->b_scrambled ) + { + debug_msg("_dvdcss_title() end (not scrambled)" ); + return 0; + } + +// debug_msg("_dvdcss_title() 2" ); + + /* Crack or decrypt CSS title key for current VTS */ + i_ret = _dvdcss_titlekey( dvdcss, i_block, p_title_key ); + + if( i_ret < 0 ) + { + debug_error("fatal error in vts css key" ); + return i_ret; + } + + memcpy( dvdcss->css.p_title_key, p_title_key, KEY_SIZE ); + + debug_msg("_dvdcss_title() end" ); + + return 0; +} + +/***************************************************************************** + * _dvdcss_disckey: get disc key. + ***************************************************************************** + * This function should only be called if DVD ioctls are present. + * It will set dvdcss->i_method = DVDCSS_METHOD_TITLE if it fails to find + * a valid disc key. + * Two decryption methods are offered: + * -disc key hash crack, + * -decryption with player keys if they are available. + *****************************************************************************/ +int _dvdcss_disckey( dvdcss_t dvdcss ) +{ + unsigned char p_buffer[ DVD_DISCKEY_SIZE ]; + + debug_msg("disckey start" ); + + if( GetBusKey( dvdcss ) < 0 ) + { + return -1; + } + + /* Get encrypted disc key */ + if( ioctl_ReadDiscKey( dvdcss->i_fd, &dvdcss->css.i_agid, p_buffer) < 0 ) + { + debug_error("ioctl ReadDiscKey failed" ); + return -1; + } + + /* This should have invalidated the AGID and got us ASF=1. */ + if( GetASF( dvdcss ) != 1 ) + { + /* Region mismatch (or region not set) is the most likely source. */ + _dvdcss_error( dvdcss, + "ASF not 1 after reading disc key (region mismatch?)" ); + ioctl_InvalidateAgid( dvdcss->i_fd, &dvdcss->css.i_agid ); + return -1; + } + + /* Set disc key to the host */ + khwl_setproperty(KHWL_DECODER_SET, edecCSSDiscKey, sizeof(p_buffer), p_buffer); + + debug_msg("disckey success" ); + + return 0; +} + + +/**************************************************************************** + * ReadTitleKey() + * This is an extended version of ioctl_ReadTitleKey() with CGMS support. + ****************************************************************************/ +int ReadTitleKey(int i_fd, int *pi_agid, int i_pos, uint8_t *p_key, int *cgms_flags) +{ + int i_ret = 0; + int cpm = 0, cp_sec = 0, cgms = 0; + +#ifdef WIN32 + if( WIN2K ) /* NT/2k/XP */ + { + DWORD tmp; + uint8_t buffer[DVD_TITLE_KEY_LENGTH]; + PDVD_COPY_PROTECT_KEY key = (PDVD_COPY_PROTECT_KEY) &buffer; + + memset( &buffer, 0, sizeof( buffer ) ); + + key->KeyLength = DVD_TITLE_KEY_LENGTH; + key->SessionId = *pi_agid; + key->KeyType = DvdTitleKey; + key->KeyFlags = 0; + key->Parameters.TitleOffset.QuadPart = (LONGLONG) i_pos * 2048; + + i_ret = DeviceIoControl( (HANDLE)i_fd, IOCTL_DVD_READ_KEY, key, + key->KeyLength, key, key->KeyLength, &tmp, NULL ) ? 0 : -1; + + memcpy( p_key, key->KeyData, DVD_KEY_SIZE ); + + cpm = (key->KeyFlags >> 7) & 1; + cp_sec = (key->KeyFlags >> 6) & 1; + cgms = (key->KeyFlags >> 4) & 3; + } else + { + debug_msg("Cannot get Macrovision flags for this Windows version."); + } +#else + dvd_authinfo auth_info; + + memset( &auth_info, 0, sizeof( auth_info ) ); + auth_info.type = DVD_LU_SEND_TITLE_KEY; + auth_info.lstk.agid = *pi_agid; + auth_info.lstk.lba = i_pos; + + i_ret = ioctl(i_fd, DVD_AUTH, &auth_info ); + + memcpy( p_key, auth_info.lstk.title_key, DVD_KEY_SIZE ); + + cpm = (auth_info.lstk.cpm & 1); + cp_sec = (auth_info.lstk.cp_sec & 1); + cgms = (auth_info.lstk.cgms & 3); +#endif + +#ifdef OUR_DEBUG_OUTPUT + msg("dvdcss: MV/CGMS Flags = (%d %d %d)\n", cpm, cp_sec, cgms); +#endif + + *cgms_flags = cgms; + + return i_ret; +} + + +/***************************************************************************** + * _dvdcss_titlekey: get title key. + *****************************************************************************/ +int _dvdcss_titlekey( dvdcss_t dvdcss, int i_pos, dvd_key_t p_title_key ) +{ + static uint8_t p_garbage[ DVDCSS_BLOCK_SIZE ]; /* we never read it back */ + uint8_t p_key[ KEY_SIZE ]; + int i_ret = 0; + + debug_msg("titlekey start" ); + + if (dvdcss->b_ioctls) + { + /* We need to authenticate again every time to get a new session key */ + if( GetBusKey( dvdcss ) < 0 ) + { + return -1; + } + + /* Get encrypted title key */ + i_ret = ReadTitleKey( dvdcss->i_fd, &dvdcss->css.i_agid, i_pos+1, p_key, &mv_cgms_flags); + if (i_ret < 0) + { + /* We need to authenticate again every time to get a new session key */ + if( GetBusKey( dvdcss ) < 0 ) + { + return -1; + } + + /* Get encrypted title key */ + i_ret = ReadTitleKey( dvdcss->i_fd, &dvdcss->css.i_agid, i_pos, p_key, &mv_cgms_flags); + if (i_ret < 0 ) + { + debug_error("ioctl ReadTitleKey failed (region mismatch?)" ); + } + } + + if (i_ret >= 0) + { + khwl_setproperty(KHWL_DECODER_SET, edecCSSTitleKey, sizeof(p_key), p_key); + memcpy( p_title_key, p_key, KEY_SIZE ); + debug_msg("Titlekey set OK!" ); + } else + { + debug_msg("Titlekey not found!"); + } + + /* Test ASF, it will be reset to 0 if we got a Region error */ + switch( GetASF( dvdcss ) ) + { + case -1: + /* An error getting the ASF status, something must be wrong. */ + debug_msg("lost ASF requesting title key" ); + ioctl_InvalidateAgid( dvdcss->i_fd, &dvdcss->css.i_agid ); + i_ret = -1; + break; + + case 0: + /* This might either be a title that has no key, + * or we encountered a region error. */ + debug_msg("lost ASF requesting title key" ); + break; + + case 1: + /* Drive status is ok. */ + /* If the title key request failed, but we did not loose ASF, + * we might stil have the AGID. Other code assume that we + * will not after this so invalidate it(?). */ + if( i_ret < 0 ) + { + debug_msg("Invalidating Agid (in title key)" ); + ioctl_InvalidateAgid( dvdcss->i_fd, &dvdcss->css.i_agid ); + } + break; + } + + /* The title key request failed */ + if (i_ret < 0) + { + debug_msg("titlekey error - resetting drive " ); + + /* Read an unscrambled sector and reset the drive */ + dvdcss->pf_seek( dvdcss, 0 ); + dvdcss->pf_read( dvdcss, p_garbage, 1 ); + dvdcss->pf_seek( dvdcss, 0 ); + + // we don't have a cracking mechanism anyway ???? + _dvdcss_disckey( dvdcss ); + + /* Fallback */ + } + } + + return i_ret; +} + +// we do nothing! All work is done in hardware! +int _dvdcss_unscramble( dvd_key_t p_key, uint8_t *p_sec ) +{ + return 0; +} + +/* Following functions are local */ + +/***************************************************************************** + * GetASF : Get Authentication success flag + ***************************************************************************** + * Returns : + * -1 on ioctl error, + * 0 if the device needs to be authenticated, + * 1 either. + *****************************************************************************/ +static int GetASF( dvdcss_t dvdcss ) +{ + int i_asf = 0; + + if( ioctl_ReportASF( dvdcss->i_fd, NULL, &i_asf ) != 0 ) + { + /* The ioctl process has failed */ + debug_error("GetASF fatal error" ); + return -1; + } + + if( i_asf ) + { + debug_msg("GetASF authenticated, ASF=1" ); + } + else + { + debug_msg("GetASF not authenticated, ASF=0" ); + } + + return i_asf; +} + +#endif // DVDCSS_USE_EXTERNAL_CSS diff --git a/src/dvd/dvd_misc.c b/src/dvd/dvd_misc.c new file mode 100644 index 0000000..bcaf014 --- /dev/null +++ b/src/dvd/dvd_misc.c @@ -0,0 +1,696 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - libdvdnav cache replacement + * \file dvd/dvd_misc.c + * \author bombur + * \version 0.1 + * \date 1.02.2006 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#include + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include + +#include "dvdnav.h" +#include "dvdnav_internal.h" +#include "read_cache.h" + +#include "dvd_misc.h" + +#include +#include +#include +#include +#include "media.h" + + +///////////// DVD Caching... +/* + [bombur]: let me describe the situation. + - dvdnav_read_cache_block() is called several times + - we have 8 buffers; each contains 16 blocks max. + - we won't precache at the vobu start + - we won't allocate any buffers or memory here +*/ + +/////////////////////// +//#define NO_CACHE +/////////////////////// + +// must be equal to MPEG_NUM_BUFS +#define READ_CACHE_CHUNKS 8 + +// must be equal to MPEG_BUF_SIZE +#define READ_CACHE_BUF_SIZE 32768 + +#define READ_AHEAD_SIZE (READ_CACHE_BUF_SIZE / DVD_VIDEO_LB_LEN) + +typedef struct read_cache_chunk_s +{ + uint8_t *cache_buffer; + int32_t cache_start_sector; /* -1 means cache invalid */ + int32_t cache_read_count; /* this many sectors are already read */ + size_t cache_block_count; /* this many sectors will go in this chunk */ + size_t cache_malloc_size; + int cache_missed; // used when a cache-missed block was written +} read_cache_chunk_t; + +struct read_cache_s +{ + read_cache_chunk_t chunk[16 /*READ_CACHE_CHUNKS */]; + int current; + int last_sector; + int wait_for_new_buffer; + int called_flag; + + int range_start; // save VOBU sector range to avoid unused cache + int range_count; + + /* Bit of strange cross-linking going on here :) -- Gotta love C :) */ + dvdnav_t *dvd_self; +}; + +read_cache_t *dvdnav_read_cache_new(dvdnav_t* dvd_self) +{ + read_cache_t *self; + int i; + + self = (read_cache_t *)malloc(sizeof(read_cache_t)); + + if(self) + { + self->current = 0; + self->dvd_self = dvd_self; + + self->wait_for_new_buffer = 1; + self->called_flag = 0; + self->last_sector = 0; + self->range_start = 0; + self->range_count = 0; + + dvdnav_read_cache_clear(self); + for (i = 0; i < READ_CACHE_CHUNKS; i++) + { + self->chunk[i].cache_buffer = NULL; + self->chunk[i].cache_read_count = 0; + self->chunk[i].cache_block_count = READ_AHEAD_SIZE; + } + } + + return self; +} + +void dvdnav_read_cache_free(read_cache_t* self) +{ + dvdnav_t *tmp; + + /* all buffers returned, free everything */ + tmp = self->dvd_self; + free(self); + free(tmp); +} + +/* This function MUST be called whenever self->file changes. */ +void dvdnav_read_cache_clear(read_cache_t *self) +{ + return; +} + +/* This function is called just after reading the NAV packet. */ +void dvdnav_pre_cache_blocks(read_cache_t *self, int sector, size_t block_count) +{ + self->range_start = sector; + self->range_count = block_count; + return; +} + +// This is our function - pass 'khwl' buffer pointers +int dvdnav_set_cache_memory(dvdnav_t* dvd_self, uint8_t *block_mem) +{ + read_cache_t *self; + int i; + + self = dvd_self->cache; + + if (self) + { + for (i = 0; i < READ_CACHE_CHUNKS; i++) + { + self->chunk[i].cache_buffer = block_mem; + self->chunk[i].cache_malloc_size = READ_CACHE_BUF_SIZE; + block_mem += READ_CACHE_BUF_SIZE; + } + } + return DVDNAV_STATUS_OK; +} + +void dvdnav_reset_cache_flag(dvdnav_t* dvd_self) +{ + if (dvd_self->cache) + dvd_self->cache->called_flag = 0; +} +int dvdnav_get_cache_flag(dvdnav_t* dvd_self) +{ + return (dvd_self->cache != NULL) ? dvd_self->cache->called_flag : 0; +} + +int dvdnav_read_cache_block(read_cache_t *self, int sector, size_t block_count, uint8_t **buf) +{ + int i, use, count; + int32_t res; + + if(!self) + return 0; + + self->called_flag = 1; + +retry: + use = -1; +#ifndef NO_CACHE + // sorry, our read-ahead cache works with only 1 block at a time + if(self->dvd_self->use_read_ahead && block_count == 1) + { + // find our chunk - try the current chunk first + read_cache_chunk_t cur = self->chunk[self->current]; + if (*buf >= cur.cache_buffer && *buf < (cur.cache_buffer + cur.cache_malloc_size)) + use = self->current; + else + { + // search others + for (i = 0; i < READ_CACHE_CHUNKS; i++) + { + if (*buf >= self->chunk[i].cache_buffer && + *buf < (self->chunk[i].cache_buffer + self->chunk[i].cache_malloc_size)) + use = i; + } + } + } +#endif + if (use >= 0) + { + read_cache_chunk_t *chunk; + chunk = &self->chunk[use]; + self->current = use; + self->last_sector = sector; + // this is not the first buffer, so try searching the cache + if (*buf != chunk->cache_buffer) + { + // we want to return already filled buf - so we check the sector bounds + if (!chunk->cache_missed && sector >= chunk->cache_start_sector && + sector < (chunk->cache_start_sector + chunk->cache_read_count) && + sector + block_count <= chunk->cache_start_sector + chunk->cache_block_count) + { + if (*buf == chunk->cache_buffer + (sector - chunk->cache_start_sector) * DVD_VIDEO_LB_LEN) + { + return DVD_VIDEO_LB_LEN; + } + } + // ask another free buf + msg("--cache skip\n"); + if (media_skip_buffer(buf)) + goto retry; + // if not, then cache-miss + } + else + { + // start a new chunk - read it entirely + chunk->cache_missed = 0; + chunk->cache_start_sector = sector; + + count = chunk->cache_block_count; + +#if 0 + // if we don't want to get out of VOBU range... + if (self->range_count > 0) + { + if (sector >= self->range_start + && sector < self->range_start + self->range_count + && sector + count >= self->range_start + self->range_count) + { + count = self->range_start + self->range_count - sector; + msg("--cache limited to %d\n", count); + } + } +#endif + + chunk->cache_read_count = (int32_t)DVDReadBlocks(self->dvd_self->file, + chunk->cache_start_sector, + count, + chunk->cache_buffer); + if (chunk->cache_read_count > 0) + { + self->wait_for_new_buffer = 0; + if (*buf == chunk->cache_buffer) + { + return DVD_VIDEO_LB_LEN; + } + } + else + return 0; + } + chunk->cache_missed = 1; + } + + if (self->dvd_self->use_read_ahead) + { + // cache miss... + msg("--cache miss\n"); + } + res = (int32_t)DVDReadBlocks(self->dvd_self->file, + sector, 1, *buf) * DVD_VIDEO_LB_LEN; + + return res; + +} + +dvdnav_status_t dvdnav_free_cache_block(dvdnav_t *self, unsigned char *buf) +{ + read_cache_t *cache; + + if (!self) + return DVDNAV_STATUS_ERR; + + cache = self->cache; + if (!cache) + return DVDNAV_STATUS_ERR; + + return DVDNAV_STATUS_OK; +} + +int8_t dvd_get_audio_logical_stream(dvdnav_t *dvd, uint8_t audio_num) +{ + int audioN; + if(!dvd) + { + //printerr("Passed a NULL pointer."); + return -1; + } + if(!dvd->started) + { + //printerr("Virtual DVD machine not started."); + return -1; + } + + pthread_mutex_lock(&dvd->vm_lock); + if (!dvd->vm->state.pgc) + { + //printerr("No current PGC."); + pthread_mutex_unlock(&dvd->vm_lock); + return -1; + } + + for (audioN = 0; audioN < 8; audioN++) + { + if (vm_get_audio_stream(dvd->vm, audioN) == audio_num) + { + pthread_mutex_unlock(&dvd->vm_lock); + return audioN; + } + } + + pthread_mutex_unlock(&dvd->vm_lock); + return -1; +} + + +/// This is almost exact copy of 'libdvdnav' function, except aspect mode +int8_t dvd_get_spu_logical_stream(dvdnav_t *dvd, uint8_t subp_num, int mode) +{ + int subpN; + + if(!dvd) + { + //printerr("Passed a NULL pointer."); + return -1; + } + if(!dvd->started) + { + //printerr("Virtual DVD machine not started."); + return -1; + } + + if (!dvd->vm->state.pgc) + { + //printerr("No current PGC."); + return -1; + } + for (subpN = 0; subpN < 32; subpN++) + { + if (vm_get_subp_stream(dvd->vm, subpN, mode) == subp_num) + return subpN; + } + return -1; +} + +int dvd_set_spu_stream(dvdnav_t *dvd, int stream, int mode) +{ + int source_aspect = vm_get_video_aspect(dvd->vm); + int req_s, subpN, streamN = -1; + + if((dvd->vm->state).domain != VTS_DOMAIN) + return 0; + + for (req_s = stream; req_s >= 0 && req_s < 32; req_s++) + { + for (subpN = 0; subpN < 32; subpN++) + { + /* Is this logical stream present */ + if ((dvd->vm->state).pgc->subp_control[subpN] & (1<<31)) + { + if(source_aspect == 0) /* 4:3 */ + streamN = ((dvd->vm->state).pgc->subp_control[subpN] >> 24) & 0x1f; + if (source_aspect == 3) /* 16:9 */ + { + switch (mode) + { + case 0: + streamN = ((dvd->vm->state).pgc->subp_control[subpN] >> 16) & 0x1f; + break; + case 1: + streamN = ((dvd->vm->state).pgc->subp_control[subpN] >> 8) & 0x1f; + break; + case 2: + streamN = (dvd->vm->state).pgc->subp_control[subpN] & 0x1f; + } + } + // that's it! + if (streamN == req_s) + { + (dvd->vm->state).SPST_REG &= ~0x7f; // Keep other bits. + (dvd->vm->state).SPST_REG |= 0x40; // Turn it on + (dvd->vm->state).SPST_REG |= subpN; + return 1; + } + } + } + } + // not found, Turn it off + (dvd->vm->state).SPST_REG &= ~0x40; + return 1; +} + +int dvd_set_audio_stream(dvdnav_t *dvd, int stream) +{ + int req_s, audioN; + + if((dvd->vm->state).domain != VTS_DOMAIN) + return 0; + + for (req_s = stream; req_s >= 0 && req_s < 8; req_s++) + { + for (audioN = 0; audioN < 8; audioN++) + { + if((dvd->vm->state).pgc->audio_control[audioN] & (1<<15)) + { + int streamN = ((dvd->vm->state).pgc->audio_control[audioN] >> 8) & 0x07; + if (streamN == req_s) + { + (dvd->vm->state).AST_REG = audioN; + return 1; + } + } + } + } + // not found, + (dvd->vm->state).AST_REG = 15; // non-existant? + return 1; +} + + +int dvd_get_time(dvdnav_t *dvd, int title, int chapter, uint64_t *time) +{ + uint64_t length = 0; + uint32_t first_cell_nr, last_cell_nr, cell_nr; + int cur_title, cur_part; + cell_playback_t *cell; + dvd_state_t *state; + int ttn; + + *time = 0; + + pthread_mutex_lock(&dvd->vm_lock); + + if (!dvd->vm->vmgi) + { + //printerr("Bad VM state."); + pthread_mutex_unlock(&dvd->vm_lock); + return DVDNAV_STATUS_ERR; + } + if (!dvd->started) + { + /* don't report an error but be nice */ + vm_start(dvd->vm); + dvd->started = 1; + } + + if(dvd->position_current.still != 0) + { + //printerr("Cannot seek in a still frame."); + pthread_mutex_unlock(&dvd->vm_lock); + return DVDNAV_STATUS_ERR; + } + + state = &(dvd->vm->state); + if(!state->pgc) + { + //printerr("No current PGC."); + pthread_mutex_unlock(&dvd->vm_lock); + return DVDNAV_STATUS_ERR; + } + + dvdnav_current_title_info(dvd, &cur_title, &cur_part); + if (title >= 0 && title + 1 != cur_title) + { + //printerr("Cannot seek in a still frame."); + pthread_mutex_unlock(&dvd->vm_lock); + return DVDNAV_STATUS_ERR; + } + + first_cell_nr = 1; + if (title < 0 || chapter < 0) + last_cell_nr = state->pgc->nr_of_cells; + else + { + vts_ptt_srpt_t *vts_ptt_srpt = dvd->vm->vtsi->vts_ptt_srpt; + if (chapter == 0) + last_cell_nr = 0; + else + { + last_cell_nr = state->pgc->nr_of_cells; + chapter--; + ttn = dvd->vm->vmgi->tt_srpt->title[title].vts_ttn - 1; + if (ttn >= 0 && ttn < vts_ptt_srpt->nr_of_srpts) + { + if (chapter < vts_ptt_srpt->title[ttn].nr_of_ptts) + { + int pgN = vts_ptt_srpt->title[ttn].ptt[chapter].pgn; // state->pgN + if(pgN < state->pgc->nr_of_programs) + last_cell_nr = state->pgc->program_map[pgN] - 1; + else + last_cell_nr = state->pgc->nr_of_cells; + } + } + } + } + + length = 0; + for(cell_nr = first_cell_nr; (cell_nr <= last_cell_nr); cell_nr ++) + { + cell = &(state->pgc->cell_playback[cell_nr-1]); + length += dvdnav_convert_time(&cell->playback_time); + } + + *time = length; + + pthread_mutex_unlock(&dvd->vm_lock); + return 1; +} + +int dvd_get_chapter(dvdnav_t *dvd, int title, uint64_t time, int *chapter) +{ + uint64_t length = 0; + uint32_t last_cell_nr, cell_nr; + int32_t found = 0; + int chap = 0; + int cur_title, cur_part; + cell_playback_t *cell; + dvd_state_t *state; + vts_ptt_srpt_t *vts_ptt_srpt = dvd->vm->vtsi->vts_ptt_srpt; + int ttn; + + *chapter = 0; + + pthread_mutex_lock(&dvd->vm_lock); + + if (!dvd->vm->vmgi) + { + //printerr("Bad VM state."); + pthread_mutex_unlock(&dvd->vm_lock); + return DVDNAV_STATUS_ERR; + } + if (!dvd->started) + { + /* don't report an error but be nice */ + vm_start(dvd->vm); + dvd->started = 1; + } + + if (dvd->position_current.still != 0) + { + //printerr("Cannot seek in a still frame."); + pthread_mutex_unlock(&dvd->vm_lock); + return DVDNAV_STATUS_ERR; + } + + dvdnav_current_title_info(dvd, &cur_title, &cur_part); + if (title + 1 != cur_title) + { + //printerr("Cannot seek in a still frame."); + pthread_mutex_unlock(&dvd->vm_lock); + return DVDNAV_STATUS_ERR; + } + + state = &(dvd->vm->state); + if (!state->pgc || title < 0) + { + //printerr("No current PGC."); + pthread_mutex_unlock(&dvd->vm_lock); + return DVDNAV_STATUS_ERR; + } + + + last_cell_nr = state->pgc->nr_of_cells; + cell_nr = 1; + length = 0; + ttn = dvd->vm->vmgi->tt_srpt->title[title].vts_ttn - 1; + if (ttn >= 0 && ttn < vts_ptt_srpt->nr_of_srpts) + { + for (chap = 0; chap < vts_ptt_srpt->title[ttn].nr_of_ptts; chap++) + { + int pgN = vts_ptt_srpt->title[ttn].ptt[chap].pgn; // state->pgN + if(pgN < state->pgc->nr_of_programs) + last_cell_nr = state->pgc->program_map[pgN] - 1; + else + last_cell_nr = state->pgc->nr_of_cells; + for(; (cell_nr <= last_cell_nr) && !found; cell_nr ++) + { + cell = &(state->pgc->cell_playback[cell_nr-1]); + length += dvdnav_convert_time(&cell->playback_time); + if (length > time) + { + found = 1; + break; + } + } + if (found) + break; + } + } + + *chapter = chap; + + pthread_mutex_unlock(&dvd->vm_lock); + return 1; +} + +// declared in vm.c +extern uint8_t dvd_name_data[DVD_VIDEO_LB_LEN]; + +static uint32_t crc_table[256]; + +void dvd_make_crc_table() +{ + uint32_t c, poly; // polynomial exclusive-or pattern + uint32_t n, k; + // make exclusive-or pattern from polynomial (0xedb88320L) + static const uint8_t p[] = { 0, 1, 2, 4, 5, 7, 8, 10, 11, 12, 16, 22, 23, 26 }; + poly = 0L; + for (n = 0; n < sizeof(p)/sizeof(uint8_t); n++) + poly |= 1L << (31 - p[n]); + + for (n = 0; n < 256; n++) + { + c = n; + for (k = 0; k < 8; k++) + c = (c & 1) ? poly ^ (c >> 1) : c >> 1; + crc_table[n] = c; + } +} + +uint32_t dvd_get_disc_ID(const char *use_name) +{ + int i, l; + uint32_t crc32 = 0xffffffffL; + uint8_t *data; + + if (use_name != NULL) + { + data = (uint8_t *)use_name; + l = strlen(use_name); + } else + { + data = dvd_name_data; + l = 128; + } + + for(i = 0; i < l; i++) + { + register uint8_t c = data[i]; + crc32 = crc_table[(crc32 & 0xff) ^ c] ^ (crc32 >> 8); + } + crc32 ^= 0xffffffffL; + +#if 0 + { + char d[130]; + for (i = 0; i < l; i++) + { + if (data[i] >= 32 && data[i] < 127) + d[i] = data[i]; + else + d[i] = ' '; + } + d[l] = '\0'; + msg("* [%s]\n", d); + } +#endif + msg("* DVD: ID = %8x\n", crc32); + return crc32; +} + +void dvd_reset_vm(dvdnav_t *dvd) +{ + vm_reset(dvd->vm, NULL); + dvd->position_current.still = 0; + dvd->sync_wait = 0; +} + +int dvd_skip_sector(dvdnav_t *dvd) +{ + if (dvd->nav_packet_error) + { + dvd->vobu.vobu_start ++; + return 0; + } + return -1; +} diff --git a/src/dvd/dvd_misc.h b/src/dvd/dvd_misc.h new file mode 100644 index 0000000..4d7e911 --- /dev/null +++ b/src/dvd/dvd_misc.h @@ -0,0 +1,76 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - libdvdnav cache replacement header file + * \file dvd/dvd_misc.h + * \author bombur + * \version 0.1 + * \date 1.02.2006 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + + +#ifndef SP_DVD_MISC_H +#define SP_DVD_MISC_H + +#ifdef __cplusplus +extern "C" { +#endif + +// all basic funcs are already in + +/// Set memory chunk enough to hold MPEG_NUM_BUFS cache buffers +int dvdnav_set_cache_memory(dvdnav_t* dvd_self, uint8_t *block_mem); + +// used to determine if dvdnav_read_cache_block() was called +// (we want to pass a new block only if the real data was returned) +void dvdnav_reset_cache_flag(dvdnav_t* dvd_self); +int dvdnav_get_cache_flag(dvdnav_t* dvd_self); + + +// defined in dvd_misc.c because of dvdnav_internal.h + +// like dvdnav function, but uses 'mode' +int8_t dvd_get_spu_logical_stream(dvdnav_t *dvd, uint8_t subp_num, int mode); +// like dvdnav function +int8_t dvd_get_audio_logical_stream(dvdnav_t *dvd, uint8_t audio_num); + +/// set spu stream to vm register (tricky!) +int dvd_set_spu_stream(dvdnav_t *dvd, int stream, int mode); + +/// set audio stream vm register (tricky!) +int dvd_set_audio_stream(dvdnav_t *dvd, int stream); + +/// time in PTS, title/chapter - 0-based. +int dvd_get_time(dvdnav_t *dvd, int title, int chapter, uint64_t *time); +/// time in PTS, title/chapter - 0-based. +int dvd_get_chapter(dvdnav_t *dvd, int title, uint64_t time, int *chapter); + +int64_t dvdnav_convert_time(dvd_time_t *time); + +// Disc ID (CRC32) stuff: +void dvd_make_crc_table(); +uint32_t dvd_get_disc_ID(const char *use_name); + +void dvd_reset_vm(dvdnav_t *dvd); + +int dvd_skip_sector(dvdnav_t *dvd); + +#ifdef __cplusplus +} +#endif + +#endif // of SP_DVD_MISC_H diff --git a/src/dvd/dvd_player.cpp b/src/dvd/dvd_player.cpp new file mode 100644 index 0000000..5d7d6e7 --- /dev/null +++ b/src/dvd/dvd_player.cpp @@ -0,0 +1,139 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - DVD player wrapper impl. + * \file dvd/dvd_player.cpp + * \author bombur + * \version 0.1 + * \date 12.10.2006 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include "dvd-internal.h" + +#include + +bool dvd_do_command(const SPString & command) +{ + if (command.CompareNoCase("continue") == 0) + { + dvd_continue_play(); + dvd_button_play(); + script_update_variable(SCRIPT_VAR_PLAYER_SPEED); + return true; + } + else if (command.CompareNoCase("pause") == 0) + { + dvd_button_pause(); + script_update_variable(SCRIPT_VAR_PLAYER_SPEED); + return true; + } + else if (command.CompareNoCase("step") == 0) + { + dvd_button_step(); + return true; + } + else if (command.CompareNoCase("slow") == 0) + { + dvd_button_slow(); + script_update_variable(SCRIPT_VAR_PLAYER_SPEED); + return true; + } + else if (command.CompareNoCase("forward") == 0) + { + dvd_button_fwd(); + script_update_variable(SCRIPT_VAR_PLAYER_SPEED); + return true; + } + else if (command.CompareNoCase("rewind") == 0) + { + dvd_button_rew(); + script_update_variable(SCRIPT_VAR_PLAYER_SPEED); + return true; + } + else if (command.CompareNoCase("prev") == 0) + { + dvd_button_prev(); return true; + } + else if (command.CompareNoCase("next") == 0) + { + dvd_button_next(); return true; + } + else if (command.CompareNoCase("press") == 0) + { + dvd_button_press(); return true; + } + else if (command.CompareNoCase("left") == 0) + { + dvd_button_left(); return true; + } + else if (command.CompareNoCase("right") == 0) + { + dvd_button_right(); return true; + } + else if (command.CompareNoCase("up") == 0) + { + dvd_button_up(); return true; + } + else if (command.CompareNoCase("down") == 0) + { + dvd_button_down(); return true; + } + else if (command.CompareNoCase("menu") == 0) + { + dvd_button_menu(); return true; + } + else if (command.CompareNoCase("rootmenu") == 0) + { + dvd_button_menu(DVD_MENU_ROOT); return true; + } + else if (command.CompareNoCase("return") == 0) + { + dvd_button_return(); return true; + } + else if (command.CompareNoCase("angle") == 0) + { + dvd_button_angle(); return true; + } + else if (command.CompareNoCase("audio") == 0) + { + dvd_button_audio(); + script_update_variable(SCRIPT_VAR_PLAYER_LANGUAGE_AUDIO); + script_update_variable(SCRIPT_VAR_PLAYER_AUDIO_STREAM); + return true; + } + else if (command.CompareNoCase("subtitle") == 0) + { + dvd_button_subtitle(); + script_update_variable(SCRIPT_VAR_PLAYER_LANGUAGE_SUBTITLE); + script_update_variable(SCRIPT_VAR_PLAYER_SUBTITLE_STREAM); + return true; + } + return false; +} diff --git a/src/dvd/main.cpp b/src/dvd/main.cpp new file mode 100644 index 0000000..65fe4b6 --- /dev/null +++ b/src/dvd/main.cpp @@ -0,0 +1,47 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - DVD player module main source file. + * \file dvd/main.cpp + * \author bombur + * \version 0.21 + * \date 2.08.2004 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#include +#include +#include + +#include + +extern "C" +{ + extern MODULE_FUNC_TABLE dvd_funcs[]; + extern MODULE_FUNC_TABLE init_funcs[]; + extern MODULE_DESC module_dvd_desc; +}; + +int main(int argc, char *argv[]) +{ + if (argc > 1) + { + module_start(argv[1], &module_dvd_desc, dvd_funcs, init_funcs); + module_wait(); + } + + return 0; +} diff --git a/src/dvd/module-dvd.cpp b/src/dvd/module-dvd.cpp new file mode 100644 index 0000000..9ed60e3 --- /dev/null +++ b/src/dvd/module-dvd.cpp @@ -0,0 +1 @@ +#include "../module-dvd.cpp" diff --git a/src/dvd/module-init.cpp b/src/dvd/module-init.cpp new file mode 100644 index 0000000..84b3f24 --- /dev/null +++ b/src/dvd/module-init.cpp @@ -0,0 +1 @@ +#include "../module-init.cpp" diff --git a/src/dvd/module.cpp b/src/dvd/module.cpp new file mode 100644 index 0000000..ae62f76 --- /dev/null +++ b/src/dvd/module.cpp @@ -0,0 +1 @@ +#include "../module.cpp" diff --git a/src/gui/console.cpp b/src/gui/console.cpp new file mode 100644 index 0000000..4826930 --- /dev/null +++ b/src/gui/console.cpp @@ -0,0 +1,238 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - console class header file + * \file console.h + * \author bombur + * \version 0.1 + * \date 4.07.2004 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#include +#include +#include + +#include +#include + +Console *console = NULL; + +Console::Console(int cols, int rows) +{ + if (cols < 1) + cols = 1; + if (rows < 1) + rows = 1; + numrows = rows; + numcols = cols; + str = (char **)SPmalloc(numrows * sizeof(char *)); + str[0] = (char *)SPmalloc(numrows * numcols * sizeof(char)); + slen = (int *)SPmalloc(numrows * sizeof(int)); + for (int i = 1; i < numrows; i++) + str[i] = str[i - 1] + numcols * sizeof(char); + num = numrows; + + width = 0; + height = 0; + + font = NULL; +#ifdef CONSOLE_USE_FONT_8x8 + SetFont(CONSOLE_FONT_8x8); +#else + SetFont(CONSOLE_FONT_8x16); +#endif +} + +bool Console::SetFont(CONSOLE_FONT_TYPE type) +{ + switch (type) + { + case CONSOLE_FONT_8x8: + cur_fnt = (BYTE *)font8x8; + font_width = 8; + font_height = 8; + break; + case CONSOLE_FONT_8x16: + cur_fnt = (BYTE *)font8x16; + font_width = 8; + font_height = 16; + break; + default: + return false; + } + + if (font != NULL) + SPfree(font); + + font = (BYTE *)SPmalloc(font_width * font_height * 256); + + UpdateFont(); + + // sorry, console size will change if font changes + width = numcols * font_width; + height = numrows * font_height; + + // center left-bottom + x = gui.left; + y = gui.bottom - height; + + dirty = true; + + return true; +} + +void Console::UpdateFont() +{ + int siz = font_width * font_height; + int chsiz = ((font_width + 7) / 8); + BYTE c1 = (BYTE)gui.GetWhiteColor(); + BYTE c2 = (BYTE)gui.GetTransparentColor(); + for (int i = 0; i < 256; i++) + { + BYTE *ff = font + i * siz; + BYTE *sff = cur_fnt + i * chsiz * font_height; + for (int j = 0; j < siz; j++) + *ff++ = ((sff[j / font_width] >> (font_width - 1 - j % font_width)) & 1) != 0 ? c1 : c2; + } +} + +Console::~Console() +{ + if (font != NULL) + SPfree(font); + + SPfree(str[0]); + SPfree(str); + SPfree(slen); +} + +bool Console::Printf(char *s) +{ + char *start = s; + for (; *s != '\0'; s++) + { + switch (*s) + { + case '\r': + continue; + case '\n': + AddString(start, s - start); + start = s + 1; + continue; + // TODO: add tabs + } + } + if (s != start) + AddString(start, s - start); + + return false; +} + +bool Console::AddString(const char *s, int len) +{ + if (num > 0) + num--; + else + { + // shift pointers + for (int iy = 0; iy < numrows - 1; iy++) + { + strncpy(str[iy], str[iy + 1], slen[iy + 1]); + slen[iy] = slen[iy + 1]; + } + } + int last = numrows - num - 1; + + if (len == 0) + len = 99999; + int sl, sll; + for (sl = 0, sll = 0; s[sl] != '\0' && sl < len && sl < numcols; sl++) + { + if (s[sl] == '\r' || s[sl] == '\n') + continue; + str[last][sll++] = s[sl]; + } + slen[last] = sll; + dirty = true; + return true; +} + +bool Console::Update(Context *context, int x1, int y1, int x2, int y2) +{ + if (font == NULL || context == NULL) + return false; + if (num == numrows) + return false; + int nx = MIN((x2 + font_width - 1) / font_width, numcols), ny = MIN((y2 + font_height - 1) / font_height, numrows); + int charsize = font_width * font_height; + int xx = (x1 / font_width) * font_width; + BYTE *d = context->data + xx; + BYTE trc = (BYTE)gui.GetTransparentColor(); + for (int iy = MAX(y1 / font_height, num), yy = iy * font_height; iy < ny; iy++, yy += font_height) + { + int iiy = iy - num; + BYTE *dst = d + yy * context->pitch; + char *s = str[iiy]; + int fy2 = MIN(font_height - 1, y2 - yy); + int fx = x1 - xx; + int dx = xx; + for (int ix = x1 / font_width; ix < MIN(nx, slen[iiy]); ix++) + { + int xxi = ix * font_width; + int fw = x2 - xxi + 1 < font_width ? x2 - xxi + 1 : font_width; + BYTE *base = font + (BYTE)s[ix] * charsize; + for (int fy = MAX(y1 - yy, 0); fy <= fy2; fy++) + memcpy(dst + fy * context->pitch + fx, base + fy * font_width + fx, fw); + fx = 0; + dst += fw; + dx += fw; + } + // finish the line + int pad = x2 + 1 - dx - fx; + if (pad > 0) + { + for (int fy = MAX(0, y1 - yy); fy <= fy2; fy++) + memset(dst + fy * context->pitch + fx, trc, pad); + } + } + return true; +} + +bool Console::TextOut(Context *context, char *str, int nx) +{ + if (font == NULL || context == NULL) + return false; + int charsize = font_width * font_height; + BYTE *dst = context->data; + int ix, nnx = (nx < 1) ? 9999 : nx; + for (ix = 0; str[ix] && ix < nnx; ix++) + { + BYTE *base = font + str[ix] * charsize; + for (int fy = 0; fy < font_height; fy++) + memcpy(dst + fy * context->pitch, base + fy * font_width, font_width); + dst += font_width; + } + // finish the line + if (nx > ix) + { + int pad = (nx - ix) * font_width; + BYTE trc = (BYTE)gui.GetTransparentColor(); + for (int fy = 0; fy < font_height; fy++) + memset(dst + fy * context->pitch, trc, pad); + } + return true; +} diff --git a/src/gui/console.h b/src/gui/console.h new file mode 100644 index 0000000..cb10a2a --- /dev/null +++ b/src/gui/console.h @@ -0,0 +1,81 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - console class header file + * \file console.h + * \author bombur + * \version 0.1 + * \date 4.07.2004 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#ifndef SP_CONSOLE_H +#define SP_CONSOLE_H + +#include + +enum CONSOLE_FONT_TYPE +{ + CONSOLE_FONT_8x8 = 0, + CONSOLE_FONT_8x16 +}; + +/// Console class +class Console : public Window +{ +public: + /// ctor + Console(int cols = 80, int rows = 20); + + /// dtor + virtual ~Console(); + + /// Formatted string output + bool Printf(char *str); + + /// add one string ('\n' are ignored) + bool AddString(const char *str, int len = 0); + + /// change font + bool SetFont(CONSOLE_FONT_TYPE type); + /// call this if palette changed + void UpdateFont(); + + /// Update part of window in LOCAL coords + virtual bool Update(Context *context, int x1, int y1, int x2, int y2); + + /// Debug output (called from window) + bool TextOut(Context *context, char *str, int nx = 0); + +public: + /// string data + char **str; + /// cached string lengths + int *slen; + /// console dims in chars + int numrows, numcols; + /// number of non-filled strings (from top to the first string) + int num; + /// rastered font + BYTE *font; + /// bitfont + BYTE *cur_fnt; + int font_width, font_height; +}; + +extern Console *console; + +#endif // of SP_CONSOLE_H diff --git a/src/gui/console_font.inc.cpp b/src/gui/console_font.inc.cpp new file mode 100644 index 0000000..4a16cf9 --- /dev/null +++ b/src/gui/console_font.inc.cpp @@ -0,0 +1,800 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - console built-in font source file + * \file console_font.inc.cpp + * \author bombur + * \version 0.1 + * \date 14.02.2005 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +unsigned char font8x8[] = +{ + 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, + 0x7E, 0x81, 0xA5, 0x81, 0xBD, 0x99, 0x81, 0x7E, + 0x7E, 0xFF, 0xDB, 0xFF, 0xC3, 0xE7, 0xFF, 0x7E, + 0x6C, 0xFE, 0xFE, 0xFE, 0x7C, 0x38, 0x10, 0x00, + 0x10, 0x38, 0x7C, 0xFE, 0x7C, 0x38, 0x10, 0x00, + 0x38, 0x7C, 0x38, 0xFE, 0xFE, 0xD6, 0x10, 0x38, + 0x10, 0x10, 0x38, 0x7C, 0xFE, 0x7C, 0x10, 0x38, + 0x00, 0x00, 0x18, 0x3C, 0x3C, 0x18, 0x00, 0x00, + 0xFF, 0xFF, 0xE7, 0xC3, 0xC3, 0xE7, 0xFF, 0xFF, + 0x00, 0x3C, 0x66, 0x42, 0x42, 0x66, 0x3C, 0x00, + 0xFF, 0xC3, 0x99, 0xBD, 0xBD, 0x99, 0xC3, 0xFF, + 0x0F, 0x07, 0x0F, 0x7D, 0xCC, 0xCC, 0xCC, 0x78, + 0x3C, 0x66, 0x66, 0x66, 0x3C, 0x18, 0x7E, 0x18, + 0x3F, 0x33, 0x3F, 0x30, 0x30, 0x70, 0xF0, 0xE0, + 0x7F, 0x63, 0x7F, 0x63, 0x63, 0x67, 0xE6, 0xC0, + 0x99, 0x5A, 0x3C, 0xE7, 0xE7, 0x3C, 0x5A, 0x99, + 0x80, 0xE0, 0xF8, 0xFE, 0xF8, 0xE0, 0x80, 0x00, + 0x02, 0x0E, 0x3E, 0xFE, 0x3E, 0x0E, 0x02, 0x00, + 0x18, 0x3C, 0x7E, 0x18, 0x18, 0x7E, 0x3C, 0x18, + 0x66, 0x66, 0x66, 0x66, 0x66, 0x00, 0x66, 0x00, + 0x7F, 0xDB, 0xDB, 0x7B, 0x1B, 0x1B, 0x1B, 0x00, + 0x7E, 0xC3, 0x78, 0xCC, 0xCC, 0x78, 0x8C, 0xF8, + 0x00, 0x00, 0x00, 0x00, 0x7E, 0x7E, 0x7E, 0x00, + 0x18, 0x3C, 0x7E, 0x18, 0x7E, 0x3C, 0x18, 0xFF, + 0x18, 0x3C, 0x7E, 0x18, 0x18, 0x18, 0x18, 0x00, + 0x18, 0x18, 0x18, 0x18, 0x7E, 0x3C, 0x18, 0x00, + 0x00, 0x18, 0x0C, 0xFE, 0x0C, 0x18, 0x00, 0x00, + 0x00, 0x30, 0x60, 0xFE, 0x60, 0x30, 0x00, 0x00, + 0x00, 0x00, 0xC0, 0xC0, 0xC0, 0xFE, 0x00, 0x00, + 0x00, 0x24, 0x66, 0xFF, 0x66, 0x24, 0x00, 0x00, + 0x00, 0x18, 0x3C, 0x7E, 0xFF, 0xFF, 0x00, 0x00, + 0x00, 0xFF, 0xFF, 0x7E, 0x3C, 0x18, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x30, 0x78, 0x78, 0x30, 0x30, 0x00, 0x30, 0x00, + 0x6C, 0x6C, 0x6C, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x6C, 0x6C, 0xFE, 0x6C, 0xFE, 0x6C, 0x6C, 0x00, + 0x30, 0x7C, 0xC0, 0x78, 0x0C, 0xF8, 0x30, 0x00, + 0x00, 0xC6, 0xCC, 0x18, 0x30, 0x66, 0xC6, 0x00, + 0x38, 0x6C, 0x38, 0x76, 0xDC, 0xCC, 0x76, 0x00, + 0x60, 0x60, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x18, 0x30, 0x60, 0x60, 0x60, 0x30, 0x18, 0x00, + 0x60, 0x30, 0x18, 0x18, 0x18, 0x30, 0x60, 0x00, + 0x00, 0x66, 0x3C, 0xFF, 0x3C, 0x66, 0x00, 0x00, + 0x00, 0x30, 0x30, 0xFC, 0x30, 0x30, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x70, 0x30, 0x60, + 0x00, 0x00, 0x00, 0xFC, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x30, 0x00, + 0x06, 0x0C, 0x18, 0x30, 0x60, 0xC0, 0x80, 0x00, + 0x78, 0xCC, 0xDC, 0xFC, 0xEC, 0xCC, 0x78, 0x00, + 0x30, 0xF0, 0x30, 0x30, 0x30, 0x30, 0xFC, 0x00, + 0x78, 0xCC, 0x0C, 0x38, 0x60, 0xCC, 0xFC, 0x00, + 0x78, 0xCC, 0x0C, 0x38, 0x0C, 0xCC, 0x78, 0x00, + 0x1C, 0x3C, 0x6C, 0xCC, 0xFE, 0x0C, 0x0C, 0x00, + 0xFC, 0xC0, 0xF8, 0x0C, 0x0C, 0xCC, 0x78, 0x00, + 0x38, 0x60, 0xC0, 0xF8, 0xCC, 0xCC, 0x78, 0x00, + 0xFC, 0xCC, 0x0C, 0x18, 0x30, 0x60, 0x60, 0x00, + 0x78, 0xCC, 0xCC, 0x78, 0xCC, 0xCC, 0x78, 0x00, + 0x78, 0xCC, 0xCC, 0x7C, 0x0C, 0x18, 0x70, 0x00, + 0x00, 0x00, 0x30, 0x30, 0x00, 0x30, 0x30, 0x00, + 0x00, 0x00, 0x30, 0x30, 0x00, 0x70, 0x30, 0x60, + 0x18, 0x30, 0x60, 0xC0, 0x60, 0x30, 0x18, 0x00, + 0x00, 0x00, 0xFC, 0x00, 0xFC, 0x00, 0x00, 0x00, + 0x60, 0x30, 0x18, 0x0C, 0x18, 0x30, 0x60, 0x00, + 0x78, 0xCC, 0x0C, 0x18, 0x30, 0x00, 0x30, 0x00, + 0x7C, 0xC6, 0xDE, 0xDE, 0xDE, 0xC0, 0x78, 0x00, + 0x30, 0x78, 0xCC, 0xCC, 0xFC, 0xCC, 0xCC, 0x00, + 0xFC, 0x66, 0x66, 0x7C, 0x66, 0x66, 0xFC, 0x00, + 0x3C, 0x66, 0xC0, 0xC0, 0xC0, 0x66, 0x3C, 0x00, + 0xFC, 0x6C, 0x66, 0x66, 0x66, 0x6C, 0xFC, 0x00, + 0xFE, 0x62, 0x68, 0x78, 0x68, 0x62, 0xFE, 0x00, + 0xFE, 0x62, 0x68, 0x78, 0x68, 0x60, 0xF0, 0x00, + 0x3C, 0x66, 0xC0, 0xC0, 0xCE, 0x66, 0x3E, 0x00, + 0xCC, 0xCC, 0xCC, 0xFC, 0xCC, 0xCC, 0xCC, 0x00, + 0x78, 0x30, 0x30, 0x30, 0x30, 0x30, 0x78, 0x00, + 0x1E, 0x0C, 0x0C, 0x0C, 0xCC, 0xCC, 0x78, 0x00, + 0xE6, 0x66, 0x6C, 0x78, 0x6C, 0x66, 0xE6, 0x00, + 0xF0, 0x60, 0x60, 0x60, 0x62, 0x66, 0xFE, 0x00, + 0xC6, 0xEE, 0xFE, 0xD6, 0xC6, 0xC6, 0xC6, 0x00, + 0xC6, 0xE6, 0xF6, 0xDE, 0xCE, 0xC6, 0xC6, 0x00, + 0x38, 0x6C, 0xC6, 0xC6, 0xC6, 0x6C, 0x38, 0x00, + 0xFC, 0x66, 0x66, 0x7C, 0x60, 0x60, 0xF0, 0x00, + 0x78, 0xCC, 0xCC, 0xCC, 0xDC, 0x78, 0x1C, 0x00, + 0xFC, 0x66, 0x66, 0x7C, 0x78, 0x6C, 0xE6, 0x00, + 0x78, 0xCC, 0xE0, 0x38, 0x1C, 0xCC, 0x78, 0x00, + 0xFC, 0xB4, 0x30, 0x30, 0x30, 0x30, 0x78, 0x00, + 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xFC, 0x00, + 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0x78, 0x30, 0x00, + 0xC6, 0xC6, 0xC6, 0xD6, 0xFE, 0xEE, 0xC6, 0x00, + 0xC6, 0xC6, 0x6C, 0x38, 0x6C, 0xC6, 0xC6, 0x00, + 0xCC, 0xCC, 0xCC, 0x78, 0x30, 0x30, 0x78, 0x00, + 0xFE, 0xCC, 0x98, 0x30, 0x62, 0xC6, 0xFE, 0x00, + 0x78, 0x60, 0x60, 0x60, 0x60, 0x60, 0x78, 0x00, + 0xC0, 0x60, 0x30, 0x18, 0x0C, 0x06, 0x02, 0x00, + 0x78, 0x18, 0x18, 0x18, 0x18, 0x18, 0x78, 0x00, + 0x10, 0x38, 0x6C, 0xC6, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, + 0x30, 0x30, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x78, 0x0C, 0x7C, 0xCC, 0x76, 0x00, + 0xE0, 0x60, 0x7C, 0x66, 0x66, 0x66, 0xBC, 0x00, + 0x00, 0x00, 0x78, 0xCC, 0xC0, 0xCC, 0x78, 0x00, + 0x1C, 0x0C, 0x0C, 0x7C, 0xCC, 0xCC, 0x76, 0x00, + 0x00, 0x00, 0x78, 0xCC, 0xFC, 0xC0, 0x78, 0x00, + 0x38, 0x6C, 0x60, 0xF0, 0x60, 0x60, 0xF0, 0x00, + 0x00, 0x00, 0x76, 0xCC, 0xCC, 0x7C, 0x0C, 0xF8, + 0xE0, 0x60, 0x6C, 0x76, 0x66, 0x66, 0xE6, 0x00, + 0x30, 0x00, 0x70, 0x30, 0x30, 0x30, 0x78, 0x00, + 0x18, 0x00, 0x78, 0x18, 0x18, 0x18, 0xD8, 0x70, + 0xE0, 0x60, 0x66, 0x6C, 0x78, 0x6C, 0xE6, 0x00, + 0x70, 0x30, 0x30, 0x30, 0x30, 0x30, 0x78, 0x00, + 0x00, 0x00, 0xEC, 0xFE, 0xD6, 0xC6, 0xC6, 0x00, + 0x00, 0x00, 0xF8, 0xCC, 0xCC, 0xCC, 0xCC, 0x00, + 0x00, 0x00, 0x78, 0xCC, 0xCC, 0xCC, 0x78, 0x00, + 0x00, 0x00, 0xDC, 0x66, 0x66, 0x7C, 0x60, 0xF0, + 0x00, 0x00, 0x76, 0xCC, 0xCC, 0x7C, 0x0C, 0x1E, + 0x00, 0x00, 0xD8, 0x6C, 0x6C, 0x60, 0xF0, 0x00, + 0x00, 0x00, 0x7C, 0xC0, 0x78, 0x0C, 0xF8, 0x00, + 0x10, 0x30, 0x7C, 0x30, 0x30, 0x34, 0x18, 0x00, + 0x00, 0x00, 0xCC, 0xCC, 0xCC, 0xCC, 0x76, 0x00, + 0x00, 0x00, 0xCC, 0xCC, 0xCC, 0x78, 0x30, 0x00, + 0x00, 0x00, 0xC6, 0xC6, 0xD6, 0xFE, 0x6C, 0x00, + 0x00, 0x00, 0xC6, 0x6C, 0x38, 0x6C, 0xC6, 0x00, + 0x00, 0x00, 0xCC, 0xCC, 0xCC, 0x7C, 0x0C, 0xF8, + 0x00, 0x00, 0xFC, 0x98, 0x30, 0x64, 0xFC, 0x00, + 0x1C, 0x30, 0x30, 0xE0, 0x30, 0x30, 0x1C, 0x00, + 0x18, 0x18, 0x18, 0x00, 0x18, 0x18, 0x18, 0x00, + 0xE0, 0x30, 0x30, 0x1C, 0x30, 0x30, 0xE0, 0x00, + 0x76, 0xDC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x10, 0x38, 0x6C, 0xC6, 0xC6, 0xC6, 0xFE, 0x00, + 0x78, 0xCC, 0x0C, 0x18, 0x30, 0x00, 0x30, 0x00, + 0x78, 0xCC, 0x0C, 0x18, 0x30, 0x00, 0x30, 0x00, + 0x60, 0x60, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x78, 0xCC, 0x0C, 0x18, 0x30, 0x00, 0x30, 0x00, + 0x6C, 0x6C, 0x6C, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x30, 0x30, 0x00, 0x30, 0x30, 0x00, + 0x18, 0x18, 0x18, 0x18, 0xFF, 0x18, 0x18, 0x18, + 0x18, 0x18, 0xFF, 0x00, 0xFF, 0x18, 0x18, 0x18, + 0x78, 0xCC, 0x0C, 0x18, 0x30, 0x00, 0x30, 0x00, + 0x00, 0xC6, 0xCC, 0x18, 0x30, 0x66, 0xC6, 0x00, + 0x78, 0xCC, 0x0C, 0x18, 0x30, 0x00, 0x30, 0x00, + 0x18, 0x30, 0x60, 0xC0, 0x60, 0x30, 0x18, 0x00, + 0x78, 0xCC, 0x0C, 0x18, 0x30, 0x00, 0x30, 0x00, + 0x78, 0xCC, 0x0C, 0x18, 0x30, 0x00, 0x30, 0x00, + 0x78, 0xCC, 0x0C, 0x18, 0x30, 0x00, 0x30, 0x00, + 0x78, 0xCC, 0x0C, 0x18, 0x30, 0x00, 0x30, 0x00, + 0x78, 0xCC, 0x0C, 0x18, 0x30, 0x00, 0x30, 0x00, + 0x60, 0x60, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x60, 0x60, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x6C, 0x6C, 0x6C, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x6C, 0x6C, 0x6C, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x18, 0x3C, 0x3C, 0x18, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xFC, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xFC, 0x00, 0x00, 0x00, 0x00, + 0x78, 0xCC, 0x0C, 0x18, 0x30, 0x00, 0x30, 0x00, + 0xFC, 0xB4, 0x30, 0x30, 0x30, 0x30, 0x78, 0x00, + 0x78, 0xCC, 0x0C, 0x18, 0x30, 0x00, 0x30, 0x00, + 0x60, 0x30, 0x18, 0x0C, 0x18, 0x30, 0x60, 0x00, + 0x78, 0xCC, 0x0C, 0x18, 0x30, 0x00, 0x30, 0x00, + 0x78, 0xCC, 0x0C, 0x18, 0x30, 0x00, 0x30, 0x00, + 0x78, 0xCC, 0x0C, 0x18, 0x30, 0x00, 0x30, 0x00, + 0x78, 0xCC, 0x0C, 0x18, 0x30, 0x00, 0x30, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x30, 0x30, 0x00, 0xFC, 0x00, 0x30, 0x30, 0x00, + 0x00, 0x72, 0x9C, 0x00, 0x72, 0x9C, 0x00, 0x00, + 0x78, 0xCC, 0x0C, 0x18, 0x30, 0x00, 0x30, 0x00, + 0x78, 0x0C, 0x38, 0x60, 0x7C, 0x00, 0x00, 0x00, + 0x78, 0xCC, 0x0C, 0x18, 0x30, 0x00, 0x30, 0x00, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x7E, 0xC3, 0x78, 0xCC, 0xCC, 0x78, 0x8C, 0xF8, + 0x00, 0xFC, 0x00, 0xFC, 0x00, 0xFC, 0x00, 0x00, + 0x00, 0x00, 0x78, 0xCC, 0xC0, 0xCC, 0x78, 0x00, + 0x60, 0x30, 0x18, 0x30, 0x60, 0x00, 0xFC, 0x00, + 0x18, 0x30, 0x60, 0xC0, 0x60, 0x30, 0x18, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xF8, 0x18, 0x18, 0x18, + 0x00, 0x00, 0x00, 0xFC, 0x00, 0x00, 0x00, 0x00, + 0xFC, 0x66, 0x66, 0x7C, 0x78, 0x6C, 0xE6, 0x00, + 0x0E, 0x1B, 0x1B, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x38, 0x6C, 0x6C, 0x38, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x30, 0x30, 0xFC, 0x30, 0x30, 0x00, 0x00, + 0x78, 0x30, 0x30, 0x30, 0x30, 0x30, 0x78, 0x00, + 0x30, 0x00, 0x70, 0x30, 0x30, 0x30, 0x78, 0x00, + 0x78, 0xCC, 0x0C, 0x18, 0x30, 0x00, 0x30, 0x00, + 0x00, 0x76, 0xDC, 0x18, 0x18, 0x18, 0x18, 0x00, + 0x7F, 0xDB, 0xDB, 0x7B, 0x1B, 0x1B, 0x1B, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, + 0x30, 0x30, 0xFC, 0x30, 0x30, 0x00, 0xFC, 0x00, + 0x78, 0x6C, 0x6C, 0x6C, 0x6C, 0x00, 0x00, 0x00, + 0x18, 0x30, 0x60, 0x30, 0x18, 0x00, 0xFC, 0x00, + 0x60, 0x30, 0x18, 0x0C, 0x18, 0x30, 0x60, 0x00, + 0x78, 0xCC, 0x0C, 0x18, 0x30, 0x00, 0x30, 0x00, + 0x78, 0xCC, 0x0C, 0x18, 0x30, 0x00, 0x30, 0x00, + 0x78, 0xCC, 0x0C, 0x18, 0x30, 0x00, 0x30, 0x00, + 0x18, 0x18, 0x18, 0x18, 0x18, 0xD8, 0xD8, 0x70, + 0x78, 0xCC, 0xC0, 0xCC, 0x78, 0x18, 0x0C, 0x78, + 0x00, 0xCC, 0x00, 0xCC, 0xCC, 0xCC, 0x7E, 0x00, + 0x1C, 0x00, 0x78, 0xCC, 0xFC, 0xC0, 0x78, 0x00, + 0x7E, 0xC3, 0x3C, 0x06, 0x3E, 0x66, 0x3F, 0x00, + 0xCC, 0x00, 0x78, 0x0C, 0x7C, 0xCC, 0x7E, 0x00, + 0xE0, 0x00, 0x78, 0x0C, 0x7C, 0xCC, 0x7E, 0x00, + 0x30, 0x30, 0x78, 0x0C, 0x7C, 0xCC, 0x7E, 0x00, + 0x00, 0x00, 0x7C, 0xC0, 0xC0, 0x7C, 0x06, 0x3C, + 0x7E, 0xC3, 0x3C, 0x66, 0x7E, 0x60, 0x3C, 0x00, + 0xCC, 0x00, 0x78, 0xCC, 0xFC, 0xC0, 0x78, 0x00, + 0xE0, 0x00, 0x78, 0xCC, 0xFC, 0xC0, 0x78, 0x00, + 0xCC, 0x00, 0x70, 0x30, 0x30, 0x30, 0x78, 0x00, + 0x7C, 0xC6, 0x38, 0x18, 0x18, 0x18, 0x3C, 0x00, + 0xE0, 0x00, 0x70, 0x30, 0x30, 0x30, 0x78, 0x00, + 0xCC, 0x30, 0x78, 0xCC, 0xCC, 0xFC, 0xCC, 0x00, + 0x30, 0x30, 0x00, 0x78, 0xCC, 0xFC, 0xCC, 0x00, + 0x1C, 0x00, 0xFC, 0x60, 0x78, 0x60, 0xFC, 0x00, + 0x00, 0x00, 0x7F, 0x0C, 0x7F, 0xCC, 0x7F, 0x00, + 0x3E, 0x6C, 0xCC, 0xFE, 0xCC, 0xCC, 0xCE, 0x00, + 0x78, 0xCC, 0x00, 0x78, 0xCC, 0xCC, 0x78, 0x00, + 0x00, 0xCC, 0x00, 0x78, 0xCC, 0xCC, 0x78, 0x00, + 0x00, 0xE0, 0x00, 0x78, 0xCC, 0xCC, 0x78, 0x00, + 0x78, 0xCC, 0x00, 0xCC, 0xCC, 0xCC, 0x7E, 0x00, + 0x00, 0xE0, 0x00, 0xCC, 0xCC, 0xCC, 0x7E, 0x00, + 0x00, 0xCC, 0x00, 0xCC, 0xCC, 0xFC, 0x0C, 0xF8, + 0xC6, 0x38, 0x7C, 0xC6, 0xC6, 0x7C, 0x38, 0x00, + 0xCC, 0x00, 0xCC, 0xCC, 0xCC, 0xCC, 0x78, 0x00, + 0x18, 0x18, 0x7E, 0xC0, 0xC0, 0x7E, 0x18, 0x18, + 0x38, 0x6C, 0x64, 0xF0, 0x60, 0xE6, 0xFC, 0x00, + 0xCC, 0xCC, 0x78, 0xFC, 0x30, 0xFC, 0x30, 0x00, + 0xF0, 0xD8, 0xD8, 0xF4, 0xCC, 0xDE, 0xCC, 0x0E, + 0x0E, 0x1B, 0x18, 0x7E, 0x18, 0x18, 0xD8, 0x70, + 0x1C, 0x00, 0x78, 0x0C, 0x7C, 0xCC, 0x7E, 0x00, + 0x38, 0x00, 0x70, 0x30, 0x30, 0x30, 0x78, 0x00, + 0x00, 0x1C, 0x00, 0x78, 0xCC, 0xCC, 0x78, 0x00, + 0x00, 0x1C, 0x00, 0xCC, 0xCC, 0xCC, 0x7E, 0x00, + 0x00, 0xF8, 0x00, 0xF8, 0xCC, 0xCC, 0xCC, 0x00, + 0xFC, 0x00, 0xCC, 0xEC, 0xFC, 0xDC, 0xCC, 0x00, + 0x3C, 0x6C, 0x6C, 0x3E, 0x00, 0x7E, 0x00, 0x00, + 0x3C, 0x66, 0x66, 0x3C, 0x00, 0x7E, 0x00, 0x00, + 0x30, 0x00, 0x30, 0x60, 0xC0, 0xCC, 0x78, 0x00, + 0x00, 0x00, 0x00, 0xFC, 0xC0, 0xC0, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xFC, 0x0C, 0x0C, 0x00, 0x00, + 0xC6, 0xCC, 0xD8, 0x3E, 0x63, 0xCE, 0x98, 0x1F, + 0xC6, 0xCC, 0xD8, 0xF3, 0x67, 0xCF, 0x9F, 0x03, + 0x00, 0x18, 0x00, 0x18, 0x18, 0x3C, 0x3C, 0x18, + 0x00, 0x33, 0x66, 0xCC, 0x66, 0x33, 0x00, 0x00, + 0x00, 0xCC, 0x66, 0x33, 0x66, 0xCC, 0x00, 0x00, + 0x00, 0x00, 0x76, 0xDC, 0xC8, 0xDC, 0x76, 0x00, + 0x00, 0x78, 0xCC, 0xF8, 0xCC, 0xF8, 0xC0, 0xC0, + 0x00, 0xFE, 0xC6, 0xC0, 0xC0, 0xC0, 0xC0, 0x00, + 0x00, 0xFE, 0x6C, 0x6C, 0x6C, 0x6C, 0x6C, 0x00, + 0xFE, 0x66, 0x30, 0x18, 0x30, 0x66, 0xFE, 0x00, + 0x00, 0x00, 0x7E, 0xCC, 0xCC, 0xCC, 0x78, 0x00, + 0x00, 0x66, 0x66, 0x66, 0x66, 0x7C, 0x60, 0xC0, + 0x00, 0x76, 0xDC, 0x18, 0x18, 0x18, 0x18, 0x00, + 0xFC, 0x30, 0x78, 0xCC, 0xCC, 0x78, 0x30, 0xFC, + 0x38, 0x6C, 0xC6, 0xFE, 0xC6, 0x6C, 0x38, 0x00, + 0x38, 0x6C, 0xC6, 0xC6, 0x6C, 0x6C, 0xEE, 0x00, + 0x1C, 0x30, 0x18, 0x7C, 0xCC, 0xCC, 0x78, 0x00, + 0x00, 0x00, 0x7E, 0xDB, 0xDB, 0x7E, 0x00, 0x00, + 0x06, 0x0C, 0x7E, 0xDB, 0xDB, 0x7E, 0x60, 0xC0, + 0x3C, 0x60, 0xC0, 0xFC, 0xC0, 0x60, 0x3C, 0x00, + 0x78, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0x00, +}; + + +unsigned char font8x16[] = +{ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x7E, 0x81, 0xA5, 0x81, 0x81, 0xA5, + 0x99, 0x81, 0x81, 0x7E, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x7E, 0xFF, 0xDB, 0xFF, 0xFF, 0xDB, + 0xE7, 0xFF, 0xFF, 0x7E, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x6C, 0xFE, 0xFE, 0xFE, + 0xFE, 0x7C, 0x38, 0x10, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x10, 0x38, 0x7C, 0xFE, + 0x7C, 0x38, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x18, 0x3C, 0x3C, 0xE7, 0xE7, + 0xE7, 0x18, 0x18, 0x3C, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x18, 0x3C, 0x7E, 0xFF, 0xFF, + 0x7E, 0x18, 0x18, 0x3C, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x3C, + 0x3C, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE7, 0xC3, + 0xC3, 0xE7, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x3C, 0x66, 0x42, + 0x42, 0x66, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC3, 0x99, 0xBD, + 0xBD, 0x99, 0xC3, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0x1E, 0x06, 0x0E, 0x1A, 0x78, 0xCC, + 0xCC, 0xCC, 0xCC, 0x78, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x3C, 0x66, 0x66, 0x66, 0x66, 0x3C, + 0x18, 0x7E, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x3F, 0x33, 0x3F, 0x30, 0x30, 0x30, + 0x30, 0x70, 0xF0, 0xE0, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x7F, 0x63, 0x7F, 0x63, 0x63, 0x63, + 0x63, 0x67, 0xE7, 0xE6, 0xC0, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x18, 0x18, 0xDB, 0x3C, 0xE7, + 0x3C, 0xDB, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x80, 0xC0, 0xE0, 0xF0, 0xF8, 0xFE, 0xF8, + 0xF0, 0xE0, 0xC0, 0x80, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x02, 0x06, 0x0E, 0x1E, 0x3E, 0xFE, 0x3E, + 0x1E, 0x0E, 0x06, 0x02, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x18, 0x3C, 0x7E, 0x18, 0x18, 0x18, + 0x7E, 0x3C, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, + 0x66, 0x00, 0x66, 0x66, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x7F, 0xDB, 0xDB, 0xDB, 0x7B, 0x1B, + 0x1B, 0x1B, 0x1B, 0x1B, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x7C, 0xC6, 0x60, 0x38, 0x6C, 0xC6, 0xC6, + 0x6C, 0x38, 0x0C, 0xC6, 0x7C, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFE, 0xFE, 0xFE, 0xFE, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x18, 0x3C, 0x7E, 0x18, 0x18, 0x18, + 0x7E, 0x3C, 0x18, 0x7E, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x18, 0x3C, 0x7E, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x7E, 0x3C, 0x18, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x0C, 0xFE, + 0x0C, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x60, 0xFE, + 0x60, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xC0, 0xC0, + 0xC0, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x6C, 0xFE, + 0x6C, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x10, 0x38, 0x38, 0x7C, + 0x7C, 0xFE, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xFE, 0xFE, 0x7C, 0x7C, + 0x38, 0x38, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x18, 0x3C, 0x3C, 0x3C, 0x18, 0x18, + 0x18, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x66, 0x66, 0x66, 0x24, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x6C, 0x6C, 0xFE, 0x6C, 0x6C, + 0x6C, 0xFE, 0x6C, 0x6C, 0x00, 0x00, 0x00, 0x00, + 0x18, 0x18, 0x7C, 0xC6, 0xC2, 0xC0, 0x7C, 0x06, + 0x06, 0x86, 0xC6, 0x7C, 0x18, 0x18, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xC2, 0xC6, 0x0C, 0x18, + 0x30, 0x60, 0xC6, 0x86, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x38, 0x6C, 0x6C, 0x38, 0x76, 0xDC, + 0xCC, 0xCC, 0xCC, 0x76, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x30, 0x30, 0x30, 0x60, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x0C, 0x18, 0x30, 0x30, 0x30, 0x30, + 0x30, 0x30, 0x18, 0x0C, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x30, 0x18, 0x0C, 0x0C, 0x0C, 0x0C, + 0x0C, 0x0C, 0x18, 0x30, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0x3C, 0xFF, + 0x3C, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x7E, + 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x18, 0x18, 0x18, 0x30, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x06, 0x0C, 0x18, + 0x30, 0x60, 0xC0, 0x80, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x38, 0x6C, 0xC6, 0xC6, 0xD6, 0xD6, + 0xC6, 0xC6, 0x6C, 0x38, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x18, 0x38, 0x78, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x7E, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x7C, 0xC6, 0x06, 0x0C, 0x18, 0x30, + 0x60, 0xC0, 0xC6, 0xFE, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x7C, 0xC6, 0x06, 0x06, 0x3C, 0x06, + 0x06, 0x06, 0xC6, 0x7C, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x0C, 0x1C, 0x3C, 0x6C, 0xCC, 0xFE, + 0x0C, 0x0C, 0x0C, 0x1E, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xFE, 0xC0, 0xC0, 0xC0, 0xFC, 0x06, + 0x06, 0x06, 0xC6, 0x7C, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x38, 0x60, 0xC0, 0xC0, 0xFC, 0xC6, + 0xC6, 0xC6, 0xC6, 0x7C, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xFE, 0xC6, 0x06, 0x06, 0x0C, 0x18, + 0x30, 0x30, 0x30, 0x30, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x7C, 0xC6, 0xC6, 0xC6, 0x7C, 0xC6, + 0xC6, 0xC6, 0xC6, 0x7C, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x7C, 0xC6, 0xC6, 0xC6, 0x7E, 0x06, + 0x06, 0x06, 0x0C, 0x78, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, + 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, + 0x00, 0x18, 0x18, 0x30, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x06, 0x0C, 0x18, 0x30, 0x60, + 0x30, 0x18, 0x0C, 0x06, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x7E, 0x00, 0x00, + 0x7E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x60, 0x30, 0x18, 0x0C, 0x06, + 0x0C, 0x18, 0x30, 0x60, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x7C, 0xC6, 0xC6, 0x0C, 0x18, 0x18, + 0x18, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x7C, 0xC6, 0xC6, 0xDE, 0xDE, + 0xDE, 0xDC, 0xC0, 0x7C, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x10, 0x38, 0x6C, 0xC6, 0xC6, 0xFE, + 0xC6, 0xC6, 0xC6, 0xC6, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xFC, 0x66, 0x66, 0x66, 0x7C, 0x66, + 0x66, 0x66, 0x66, 0xFC, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x3C, 0x66, 0xC2, 0xC0, 0xC0, 0xC0, + 0xC0, 0xC2, 0x66, 0x3C, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xF8, 0x6C, 0x66, 0x66, 0x66, 0x66, + 0x66, 0x66, 0x6C, 0xF8, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xFE, 0x66, 0x62, 0x68, 0x78, 0x68, + 0x60, 0x62, 0x66, 0xFE, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xFE, 0x66, 0x62, 0x68, 0x78, 0x68, + 0x60, 0x60, 0x60, 0xF0, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x3C, 0x66, 0xC2, 0xC0, 0xC0, 0xDE, + 0xC6, 0xC6, 0x66, 0x3A, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xC6, 0xC6, 0xC6, 0xC6, 0xFE, 0xC6, + 0xC6, 0xC6, 0xC6, 0xC6, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x3C, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x3C, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x1E, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, + 0xCC, 0xCC, 0xCC, 0x78, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xE6, 0x66, 0x66, 0x6C, 0x78, 0x78, + 0x6C, 0x66, 0x66, 0xE6, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xF0, 0x60, 0x60, 0x60, 0x60, 0x60, + 0x60, 0x62, 0x66, 0xFE, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xC6, 0xEE, 0xFE, 0xFE, 0xD6, 0xC6, + 0xC6, 0xC6, 0xC6, 0xC6, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xC6, 0xE6, 0xF6, 0xFE, 0xDE, 0xCE, + 0xC6, 0xC6, 0xC6, 0xC6, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x7C, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, + 0xC6, 0xC6, 0xC6, 0x7C, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xFC, 0x66, 0x66, 0x66, 0x7C, 0x60, + 0x60, 0x60, 0x60, 0xF0, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x7C, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, + 0xC6, 0xD6, 0xDE, 0x7C, 0x0C, 0x0E, 0x00, 0x00, + 0x00, 0x00, 0xFC, 0x66, 0x66, 0x66, 0x7C, 0x6C, + 0x66, 0x66, 0x66, 0xE6, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x7C, 0xC6, 0xC6, 0x60, 0x38, 0x0C, + 0x06, 0xC6, 0xC6, 0x7C, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x7E, 0x7E, 0x5A, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x3C, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, + 0xC6, 0xC6, 0xC6, 0x7C, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, + 0xC6, 0x6C, 0x38, 0x10, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xC6, 0xC6, 0xC6, 0xC6, 0xD6, 0xD6, + 0xD6, 0xFE, 0xEE, 0x6C, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xC6, 0xC6, 0x6C, 0x7C, 0x38, 0x38, + 0x7C, 0x6C, 0xC6, 0xC6, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x66, 0x66, 0x66, 0x66, 0x3C, 0x18, + 0x18, 0x18, 0x18, 0x3C, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xFE, 0xC6, 0x86, 0x0C, 0x18, 0x30, + 0x60, 0xC2, 0xC6, 0xFE, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x3C, 0x30, 0x30, 0x30, 0x30, 0x30, + 0x30, 0x30, 0x30, 0x3C, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x80, 0xC0, 0xE0, 0x70, 0x38, + 0x1C, 0x0E, 0x06, 0x02, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x3C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, + 0x0C, 0x0C, 0x0C, 0x3C, 0x00, 0x00, 0x00, 0x00, + 0x10, 0x38, 0x6C, 0xC6, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, + 0x30, 0x30, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x0C, 0x7C, + 0xCC, 0xCC, 0xCC, 0x76, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xE0, 0x60, 0x60, 0x78, 0x6C, 0x66, + 0x66, 0x66, 0x66, 0x7C, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x7C, 0xC6, 0xC0, + 0xC0, 0xC0, 0xC6, 0x7C, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x1C, 0x0C, 0x0C, 0x3C, 0x6C, 0xCC, + 0xCC, 0xCC, 0xCC, 0x76, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x7C, 0xC6, 0xFE, + 0xC0, 0xC0, 0xC6, 0x7C, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x38, 0x6C, 0x64, 0x60, 0xF0, 0x60, + 0x60, 0x60, 0x60, 0xF0, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x76, 0xCC, 0xCC, + 0xCC, 0xCC, 0xCC, 0x7C, 0x0C, 0xCC, 0x78, 0x00, + 0x00, 0x00, 0xE0, 0x60, 0x60, 0x6C, 0x76, 0x66, + 0x66, 0x66, 0x66, 0xE6, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x18, 0x18, 0x00, 0x38, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x3C, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x06, 0x06, 0x00, 0x0E, 0x06, 0x06, + 0x06, 0x06, 0x06, 0x06, 0x66, 0x66, 0x3C, 0x00, + 0x00, 0x00, 0xE0, 0x60, 0x60, 0x66, 0x6C, 0x78, + 0x78, 0x6C, 0x66, 0xE6, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x38, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x3C, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xEC, 0xFE, 0xD6, + 0xD6, 0xD6, 0xD6, 0xC6, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xDC, 0x66, 0x66, + 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x7C, 0xC6, 0xC6, + 0xC6, 0xC6, 0xC6, 0x7C, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xDC, 0x66, 0x66, + 0x66, 0x66, 0x66, 0x7C, 0x60, 0x60, 0xF0, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x76, 0xCC, 0xCC, + 0xCC, 0xCC, 0xCC, 0x7C, 0x0C, 0x0C, 0x1E, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xDC, 0x76, 0x66, + 0x60, 0x60, 0x60, 0xF0, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x7C, 0xC6, 0x60, + 0x38, 0x0C, 0xC6, 0x7C, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x10, 0x30, 0x30, 0xFC, 0x30, 0x30, + 0x30, 0x30, 0x36, 0x1C, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xCC, 0xCC, 0xCC, + 0xCC, 0xCC, 0xCC, 0x76, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x66, 0x66, 0x66, + 0x66, 0x66, 0x3C, 0x18, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xC6, 0xC6, 0xD6, + 0xD6, 0xD6, 0xFE, 0x6C, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xC6, 0x6C, 0x38, + 0x38, 0x38, 0x6C, 0xC6, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xC6, 0xC6, 0xC6, + 0xC6, 0xC6, 0xC6, 0x7E, 0x06, 0x0C, 0xF8, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, 0xCC, 0x18, + 0x30, 0x60, 0xC6, 0xFE, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x0E, 0x18, 0x18, 0x18, 0x70, 0x18, + 0x18, 0x18, 0x18, 0x0E, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x18, 0x18, 0x18, 0x18, 0x00, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x70, 0x18, 0x18, 0x18, 0x0E, 0x18, + 0x18, 0x18, 0x18, 0x70, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x76, 0xDC, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x10, 0x38, 0x6C, 0xC6, + 0xC6, 0xC6, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x7C, 0xC6, 0xC6, 0x0C, 0x18, 0x18, + 0x18, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x7C, 0xC6, 0xC6, 0x0C, 0x18, 0x18, + 0x18, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x30, 0x30, 0x30, 0x60, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x7C, 0xC6, 0xC6, 0x0C, 0x18, 0x18, + 0x18, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x66, 0x66, 0x66, 0x24, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, + 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0xFF, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0xFF, 0x18, 0xFF, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x00, 0x00, 0x7C, 0xC6, 0xC6, 0x0C, 0x18, 0x18, + 0x18, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xC2, 0xC6, 0x0C, 0x18, + 0x30, 0x60, 0xC6, 0x86, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x7C, 0xC6, 0xC6, 0x0C, 0x18, 0x18, + 0x18, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x06, 0x0C, 0x18, 0x30, 0x60, + 0x30, 0x18, 0x0C, 0x06, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x7C, 0xC6, 0xC6, 0x0C, 0x18, 0x18, + 0x18, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x7C, 0xC6, 0xC6, 0x0C, 0x18, 0x18, + 0x18, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x7C, 0xC6, 0xC6, 0x0C, 0x18, 0x18, + 0x18, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x7C, 0xC6, 0xC6, 0x0C, 0x18, 0x18, + 0x18, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x7C, 0xC6, 0xC6, 0x0C, 0x18, 0x18, + 0x18, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x30, 0x30, 0x30, 0x60, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x30, 0x30, 0x30, 0x60, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x66, 0x66, 0x66, 0x24, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x66, 0x66, 0x66, 0x24, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x3C, + 0x3C, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x7C, 0xC6, 0xC6, 0x0C, 0x18, 0x18, + 0x18, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x7E, 0x7E, 0x5A, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x3C, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x7C, 0xC6, 0xC6, 0x0C, 0x18, 0x18, + 0x18, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x60, 0x30, 0x18, 0x0C, 0x06, + 0x0C, 0x18, 0x30, 0x60, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x7C, 0xC6, 0xC6, 0x0C, 0x18, 0x18, + 0x18, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x7C, 0xC6, 0xC6, 0x0C, 0x18, 0x18, + 0x18, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x7C, 0xC6, 0xC6, 0x0C, 0x18, 0x18, + 0x18, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x7C, 0xC6, 0xC6, 0x0C, 0x18, 0x18, + 0x18, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x42, 0x7E, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x7E, + 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x76, 0xDC, 0x00, + 0x76, 0xDC, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x7C, 0xC6, 0xC6, 0x0C, 0x18, 0x18, + 0x18, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x70, 0xD8, 0x30, 0x60, 0xC8, 0xF8, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x7C, 0xC6, 0xC6, 0x0C, 0x18, 0x18, + 0x18, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x00, 0x7C, 0xC6, 0x60, 0x38, 0x6C, 0xC6, 0xC6, + 0x6C, 0x38, 0x0C, 0xC6, 0x7C, 0x00, 0x00, 0x00, + 0x00, 0x6C, 0x00, 0xFE, 0x62, 0x60, 0x68, 0x78, + 0x68, 0x60, 0x62, 0xFE, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x7C, 0xC6, 0xC0, + 0xC0, 0xC0, 0xC6, 0x7C, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x30, 0x18, 0x0C, 0x06, 0x0C, + 0x18, 0x30, 0x00, 0x7E, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x06, 0x0C, 0x18, 0x30, 0x60, + 0x30, 0x18, 0x0C, 0x06, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xFC, 0x66, 0x66, 0x66, 0x7C, 0x6C, + 0x66, 0x66, 0x66, 0xE6, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x0E, 0x1B, 0x1B, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x00, 0x38, 0x6C, 0x6C, 0x38, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x18, 0x7E, + 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x3C, 0x18, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x3C, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x18, 0x18, 0x00, 0x38, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x3C, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x7C, 0xC6, 0xC6, 0x0C, 0x18, 0x18, + 0x18, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xC6, 0xC6, 0xC6, + 0xC6, 0x7E, 0x06, 0x06, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x7F, 0xDB, 0xDB, 0xDB, 0x7B, 0x1B, + 0x1B, 0x1B, 0x1B, 0x1B, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x6C, 0x00, 0x7C, 0xC6, 0xFE, + 0xC0, 0xC0, 0xC6, 0x7C, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x3C, 0x42, 0x99, 0xA5, 0xA1, + 0xA5, 0x99, 0x42, 0x3C, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x0C, 0x18, 0x30, 0x60, 0x30, + 0x18, 0x0C, 0x00, 0x7E, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x60, 0x30, 0x18, 0x0C, 0x06, + 0x0C, 0x18, 0x30, 0x60, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x7C, 0xC6, 0xC6, 0x0C, 0x18, 0x18, + 0x18, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x7C, 0xC6, 0xC6, 0x0C, 0x18, 0x18, + 0x18, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x7C, 0xC6, 0xC6, 0x0C, 0x18, 0x18, + 0x18, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x00, + 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, + 0xD8, 0xD8, 0xD8, 0x70, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x1E, 0x36, 0x66, 0xC6, 0xC6, 0xFE, + 0xC6, 0xC6, 0xC6, 0xC6, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xFE, 0x62, 0x62, 0x60, 0x7C, 0x66, + 0x66, 0x66, 0x66, 0xFC, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xFC, 0x66, 0x66, 0x66, 0x7C, 0x66, + 0x66, 0x66, 0x66, 0xFC, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xFE, 0x62, 0x62, 0x60, 0x60, 0x60, + 0x60, 0x60, 0x60, 0xF0, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x1E, 0x36, 0x66, 0x66, 0x66, 0x66, + 0x66, 0x66, 0x66, 0xFF, 0xC3, 0x81, 0x00, 0x00, + 0x00, 0x00, 0xFE, 0x66, 0x62, 0x68, 0x78, 0x68, + 0x60, 0x62, 0x66, 0xFE, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xD6, 0xD6, 0x54, 0x54, 0x7C, 0x7C, + 0x54, 0xD6, 0xD6, 0xD6, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x7C, 0xC6, 0x06, 0x06, 0x3C, 0x06, + 0x06, 0x06, 0xC6, 0x7C, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xC6, 0xC6, 0xCE, 0xCE, 0xD6, 0xE6, + 0xE6, 0xC6, 0xC6, 0xC6, 0x00, 0x00, 0x00, 0x00, + 0x38, 0x38, 0xC6, 0xC6, 0xCE, 0xCE, 0xD6, 0xE6, + 0xE6, 0xC6, 0xC6, 0xC6, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xE6, 0x66, 0x6C, 0x6C, 0x78, 0x78, + 0x6C, 0x6C, 0x66, 0xE6, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x1E, 0x36, 0x66, 0xC6, 0xC6, 0xC6, + 0xC6, 0xC6, 0xC6, 0xC6, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xC6, 0xEE, 0xFE, 0xFE, 0xD6, 0xC6, + 0xC6, 0xC6, 0xC6, 0xC6, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xC6, 0xC6, 0xC6, 0xC6, 0xFE, 0xC6, + 0xC6, 0xC6, 0xC6, 0xC6, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x7C, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, + 0xC6, 0xC6, 0xC6, 0x7C, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xFE, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, + 0xC6, 0xC6, 0xC6, 0xC6, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xFC, 0x66, 0x66, 0x66, 0x7C, 0x60, + 0x60, 0x60, 0x60, 0xF0, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x3C, 0x66, 0xC2, 0xC0, 0xC0, 0xC0, + 0xC0, 0xC2, 0x66, 0x3C, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x7E, 0x5A, 0x18, 0x18, 0x18, 0x18, + 0x18, 0x18, 0x18, 0x3C, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0x7E, + 0x06, 0x06, 0xC6, 0x7C, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x3C, 0x18, 0x7E, 0xDB, 0xDB, 0xDB, 0xDB, + 0xDB, 0x7E, 0x18, 0x3C, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xC6, 0xC6, 0x6C, 0x7C, 0x38, 0x38, + 0x7C, 0x6C, 0xC6, 0xC6, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, + 0xCC, 0xCC, 0xCC, 0xFE, 0x06, 0x06, 0x00, 0x00, + 0x00, 0x00, 0xC6, 0xC6, 0xC6, 0xC6, 0xC6, 0x7E, + 0x06, 0x06, 0x06, 0x06, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xDB, 0xDB, 0xDB, 0xDB, 0xDB, 0xDB, + 0xDB, 0xDB, 0xDB, 0xFF, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xDB, 0xDB, 0xDB, 0xDB, 0xDB, 0xDB, + 0xDB, 0xDB, 0xDB, 0xFF, 0x03, 0x03, 0x00, 0x00, + 0x00, 0x00, 0xF8, 0xB0, 0x30, 0x30, 0x3E, 0x33, + 0x33, 0x33, 0x33, 0x7E, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xC3, 0xC3, 0xC3, 0xC3, 0xF3, 0xDB, + 0xDB, 0xDB, 0xDB, 0xF3, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xF0, 0x60, 0x60, 0x60, 0x7C, 0x66, + 0x66, 0x66, 0x66, 0xFC, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x7C, 0xC6, 0x06, 0x26, 0x3E, 0x26, + 0x06, 0x06, 0xC6, 0x7C, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0xCE, 0xDB, 0xDB, 0xDB, 0xFB, 0xDB, + 0xDB, 0xDB, 0xDB, 0xCE, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x3F, 0x66, 0x66, 0x66, 0x3E, 0x3E, + 0x66, 0x66, 0x66, 0xE7, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x0C, 0x7C, + 0xCC, 0xCC, 0xCC, 0x76, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x02, 0x06, 0x3C, 0x60, 0x60, 0x7C, 0x66, + 0x66, 0x66, 0x66, 0x3C, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0x66, 0x66, + 0x7C, 0x66, 0x66, 0xFC, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x7E, 0x32, 0x32, + 0x30, 0x30, 0x30, 0x78, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x1E, 0x36, 0x36, + 0x66, 0x66, 0x66, 0xFF, 0xC3, 0xC3, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x7C, 0xC6, 0xFE, + 0xC0, 0xC0, 0xC6, 0x7C, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xD6, 0xD6, 0x54, + 0x7C, 0x54, 0xD6, 0xD6, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x3C, 0x66, 0x06, + 0x0C, 0x06, 0x66, 0x3C, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xC6, 0xC6, 0xCE, + 0xD6, 0xE6, 0xC6, 0xC6, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x38, 0x38, 0xC6, 0xC6, 0xCE, + 0xD6, 0xE6, 0xC6, 0xC6, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xE6, 0x6C, 0x78, + 0x78, 0x6C, 0x66, 0xE6, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x1E, 0x36, 0x66, + 0x66, 0x66, 0x66, 0x66, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xC6, 0xEE, 0xFE, + 0xFE, 0xD6, 0xD6, 0xC6, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xC6, 0xC6, 0xC6, + 0xFE, 0xC6, 0xC6, 0xC6, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x7C, 0xC6, 0xC6, + 0xC6, 0xC6, 0xC6, 0x7C, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, 0xC6, 0xC6, + 0xC6, 0xC6, 0xC6, 0xC6, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xDC, 0x66, 0x66, + 0x66, 0x66, 0x66, 0x7C, 0x60, 0x60, 0xF0, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x7C, 0xC6, 0xC0, + 0xC0, 0xC0, 0xC6, 0x7C, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x7E, 0x5A, 0x18, + 0x18, 0x18, 0x18, 0x3C, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xC6, 0xC6, 0xC6, + 0xC6, 0xC6, 0x7E, 0x06, 0x06, 0xC6, 0x7C, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x3C, 0x18, 0x7E, 0xDB, + 0xDB, 0xDB, 0xDB, 0x7E, 0x18, 0x18, 0x3C, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xC6, 0x6C, 0x38, + 0x38, 0x38, 0x6C, 0xC6, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xCC, 0xCC, 0xCC, + 0xCC, 0xCC, 0xCC, 0xFE, 0x06, 0x06, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xC6, 0xC6, 0xC6, + 0xC6, 0x7E, 0x06, 0x06, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xD6, 0xD6, 0xD6, + 0xD6, 0xD6, 0xD6, 0xFE, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xD6, 0xD6, 0xD6, + 0xD6, 0xD6, 0xD6, 0xFE, 0x03, 0x03, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0xB0, 0x30, + 0x3E, 0x33, 0x33, 0x7E, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xC6, 0xC6, 0xC6, + 0xF6, 0xDE, 0xDE, 0xF6, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xF0, 0x60, 0x60, + 0x7C, 0x66, 0x66, 0xFC, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x3C, 0x66, 0x06, + 0x1E, 0x06, 0x66, 0x3C, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0xCE, 0xDB, 0xDB, + 0xFB, 0xDB, 0xDB, 0xCE, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x7E, 0xCC, 0xCC, + 0xFC, 0x6C, 0xCC, 0xCE, 0x00, 0x00, 0x00, 0x00, +}; diff --git a/src/gui/font.cpp b/src/gui/font.cpp new file mode 100644 index 0000000..b398764 --- /dev/null +++ b/src/gui/font.cpp @@ -0,0 +1,467 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - font & font manager class impl. + * \file font.cpp + * \author bombur + * \version 0.1 + * \date 4.10.2006 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#include +#include + +#include +#include +#include + +#include +#include + +FontManager *guifonts = NULL; + +#define DFF_FIXED 0x0001 // font is fixed pitch +#define DFF_PROPORTIONAL 0x0002 // font is proportional pitch +#define DFF_ABCFIXED 0x0004 // font is an ABC fixed font +#define DFF_ABCPROPORTIONAL 0x0008 // font is an ABC pro-portional font +#define DFF_1COLOR 0x0010 // font is one color +#define DFF_16COLOR 0x0020 // font is 16 color +#define DFF_256COLOR 0x0040 // font is 256 color +#define DFF_RGBCOLOR 0x0080 // font is RGB color + +#ifdef WIN32 +#pragma pack(1) +#endif + +typedef struct FntHeader +{ + WORD ver; + DWORD size; + BYTE copy[60]; + WORD type; + WORD points; + WORD vres, hres; + WORD ascent; + WORD ilead, elead; + BYTE italic, underl, strike; + WORD weight; + BYTE charset; + WORD pixwidth, pixheight; + BYTE pitchfamily; + WORD avgwidth; + WORD maxwidth; + BYTE first, last, def, brk; + WORD widthbytes; + DWORD device, face, bitsptr, bits; + BYTE flags; +} ATTRIBUTE_PACKED FntHeader; + +#ifdef WIN32 +#pragma pack() +#endif + + +const int get_ith_font = 1; + +///////////////////////////////////////////////////////////////////////// + +Font::Font() +{ + num = 0; + data = NULL; + widths = NULL; + height = 0; + baseline = 0; + + unloaded = false; +} + +Font::~Font() +{ + Unload(); +} + +BOOL Font::Load() +{ + Unload(); + + if (name == NULL) + return FALSE; + FILE *fp = fopen(name, "rb"); + if (fp == FALSE) + { + msg_error("Font file '%s' not found.\n", name); + return FALSE; + } + WORD magic; + if (fread(&magic, sizeof(WORD), 1, fp) != 1) + { + msg_error("Cannot read from font file '%s'.\n", name); + return FALSE; + } + int base = 0; + if (magic == 0x5a4d) ///////////////////// .FON + { + fseek(fp, 0, SEEK_END); + DWORD siz = ftell(fp); + fseek(fp, 0x3c, SEEK_SET); + DWORD offs = 0; + int ret = fread(&offs, sizeof(DWORD), 1, fp); + if (ret != 1 || offs < 0x40 || offs >= siz) + { + msg_error("Cannot read .FON header for '%s'.\n", name); + return FALSE; + } + fseek(fp, offs, SEEK_SET); + WORD ne; + fread(&ne, sizeof(WORD), 1, fp); + if (ne != 0x454e) + { + msg_error("Unknown .FON format for '%s'.\n", name); + return FALSE; + } + fseek(fp, offs + 0x24, SEEK_SET); + WORD res_offs; + fread(&res_offs, sizeof(WORD), 1, fp); + if (res_offs >= siz) + { + return FALSE; + } + fseek(fp, offs + res_offs, SEEK_SET); + WORD res_shift; + fread(&res_shift, sizeof(WORD), 1, fp); + if (res_shift > 32) + { + return FALSE; + } + bool found = false; + for (int r = 0, f = 0; ; r++) + { + fseek(fp, offs + res_offs + 2 + r * 0x14, SEEK_SET); + WORD type; + fread(&type, sizeof(WORD), 1, fp); + if (type == 0) + break; + if (type == 0x8008) // get first font + { + fseek(fp, 6, SEEK_CUR); + WORD dir_offs; + fread(&dir_offs, sizeof(WORD), 1, fp); + base = dir_offs << res_shift; + found = true; + if (++f == get_ith_font) + { + break; + } + } + } + if (!found) + { + msg_error("No fonts found in .FON file '%s'.\n", name); + return FALSE; + } + } + ///////////////////// .FNT + FntHeader h; + fseek(fp, base, SEEK_SET); + if (fread((void *)&h, sizeof(h), 1, fp) != 1) + { + msg_error("Cannot read font header for '%s'\n", name); + return FALSE; + } + + int first = 0, last = 0, ver = 3, avgwidth = 0; + if (h.ver < 0x0100 || h.ver > 0x0300) + { + msg_error("Unknown font format for '%s'\n", name); + return FALSE; + } + if ((h.type & 1) == 1) + { + msg_error("Vector fonts not supported ('%s')\n", name); + return FALSE; + } + if (h.maxwidth > 64) + { + msg_error("Max. width for raster fonts supported is 64. ('%s')\n", name); + return FALSE; + } + /* + if ((h.flags & DFF_ABCFIXED) || (h.flags & DFF_ABCPROPORTIONAL)) + { + // skip ABC + fseek(fp, 3 * 2, SEEK_CUR); + } + if ((h.flags & DFF_1COLOR) != DFF_1COLOR) + { + // skip color offset + fseek(fp, 4, SEEK_CUR); + } + + //fseek(fp, 16, SEEK_CUR); + if ((h.flags & DFF_16COLOR) == DFF_16COLOR || + (h.flags & DFF_256COLOR) == DFF_256COLOR || + (h.flags & DFF_RGBCOLOR) == DFF_RGBCOLOR) + { + msg_error("Only black-white fonts supported."); + return FALSE; + } + */ + + num = h.last + 1; + if (num < 1) + { + msg_error("No characters found in font '%s'\n", name); + return FALSE; + } + if (num > 256) + { + msg_error("Font '%s' contains more than 256 characters (%d). Truncating...\n", + name, num); + num = 256; + } + + if (h.pixheight < 1 || h.pixheight > 255) + { + msg_error("Wrong font height %d for '%s'.\n", h.pixheight, name); + return FALSE; + } + height = h.pixheight; + baseline = h.ascent + 1; // we add 1 for underline distance! + if (baseline < 1) + baseline = 1; + if (baseline >= height) + baseline = height - 1; + first = h.first; + last = h.last; + ver = h.ver; + avgwidth = h.avgwidth; + + // reading chartable + int i; + widths = new int [num]; + if (widths == NULL) + return FALSE; + data = new ULONGLONG* [num]; + if (data == NULL) + return FALSE; + + data[0] = (ULONGLONG *)SPmalloc(num * height * sizeof(ULONGLONG)); + if (data[0] == NULL) + return FALSE; + + // temp allocs + int *off = new int [num]; + if (off == NULL) + return FALSE; + BYTE *tmpdata = new BYTE [height * 8]; + if (tmpdata == NULL) + { + delete [] off; + return FALSE; + } + + int num_allocs = 0; + for (i = 0; i < num; i++) + { + DWORD w = 0; + if (i >= first && i <= last) + { + if (fread(&w, 2, 1, fp) != 1) + w = 0; + if (ver == 0x0200) + { + WORD of; + if (fread(&of, 2, 1, fp) != 1) + off[i] = -1; + else + off[i] = of; + } else + { + if (fread(&(off[i]), 4, 1, fp) != 1) + off[i] = -1; + } + } else + off[i] = -1; + // skip too wide characters + if (w > 64 || off[i] <= 0) + w = 0; + widths[i] = w; + if (w > 0) + { + data[i] = data[0] + num_allocs * height; + num_allocs++; + + memset(data[i], 0, sizeof(ULONGLONG) * height); + } else if (i > 0) + data[i] = NULL; + } + + // now read symbols + for (i = 0; i < num; i++) + { + if (widths[i] < 1 || off[i] < 0) + continue; + ULONGLONG *d = data[i]; + if (d == NULL) + continue; + int numbytes = ((widths[i] + 7) / 8 + 1) / 2 * 2; + if (fseek(fp, off[i] + base, SEEK_SET) != 0) + continue; + if (fread(tmpdata, height * numbytes, 1, fp) != 1) + continue; + for (int j = 0; j < numbytes; j++) + { + for (int k = 0; k < height; k++) + { + ULONGLONG tmpd = tmpdata[j * height + k]; + for (int l = 0; l < 8; l++) + d[k] |= ((tmpd >> (7 - l)) & 1) << (j * 8 + l); + } + } + } + + delete [] tmpdata; + delete [] off; + + // fix space symbol + if (widths[32] == 0) + { + widths[32] = avgwidth; + data[32] = data[0] + num_allocs * height; + num_allocs++; + + for (i = 0; i < height; i++) + { + data[32][i] = 0; + } + } + + data[0] = (ULONGLONG *)SPrealloc(data[0], (num_allocs + 1) * height * sizeof(ULONGLONG)); + if (data[0] == NULL) + return FALSE; + for (int k = 1, na = 0; k < num; k++) + { + if (data[k] != NULL) + data[k] = data[0] + (na++) * height; + } + + +// msg("FONT %s loaded OK.\n", name); + + unloaded = false; + return TRUE; +} + +BOOL Font::Unload() +{ + if (data != NULL) + { + //for (int i = 0; i < num; i++) + // SPSafeDeleteArray(data[i]); + SPSafeFree(data[0]); + } + SPSafeDeleteArray(data); + SPSafeDeleteArray(widths); + num = 0; + + unloaded = true; + return TRUE; +} + + +/////////////////////////////////////////////////////////////////// + +FontManager::FontManager() +{ + fonthash.SetN(resource_num_hash); + def = NULL; +} + +FontManager::~FontManager() +{ + SPSafeDelete(def); +} + +Font *FontManager::GetFont(char *fname) +{ + if (fname == NULL || fname[0] == '\0') + return GetDefaultFont(); + + static Font testfont; + testfont.SetConstName(fname); + Font *fnt = fonthash.Get(testfont); + testfont.name = NULL; + if (fnt != NULL) + return fnt; + + fnt = new Font(); + fnt->SetName(fname); + if (!fnt->Load()) + { + delete fnt; + return NULL; + } + + fonthash.Add(fnt); + return fnt; +} + +Font *FontManager::GetDefaultFont() +{ + if (def != NULL) + return def; + if (console == NULL || console->cur_fnt == NULL) + { + msg_error("Default font not found.\n"); + return NULL; + } + def = new Font(); + if (def == NULL) + return NULL; + + def->num = 256; + def->height = console->font_height; + def->widths = new int [def->num]; + def->data = new ULONGLONG* [def->num]; + int chsiz = ((console->font_width + 7) / 8); + for (int i = 0; i < def->num; i++) + { + BYTE *d = console->cur_fnt + i * chsiz * def->height; + def->data[i] = new ULONGLONG [def->height]; + def->widths[i] = console->font_width; + int fw = console->font_width - 1; + for (int x = 0; x < def->height; x++) + { + def->data[i][x] = 0; + for (int k = 0; k < console->font_width; k++) + def->data[i][x] |= ((d[x] >> (fw - k)) & 1) << k; + } + } + return def; +} + +BOOL FontManager::ClearFontData() +{ + Font *cur = fonthash.GetFirst(); + while (cur != NULL) + { + cur->Unload(); + cur = fonthash.GetNext(*cur); + } + return TRUE; +} diff --git a/src/gui/font.h b/src/gui/font.h new file mode 100644 index 0000000..8e40465 --- /dev/null +++ b/src/gui/font.h @@ -0,0 +1,85 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - font & font manager class header file + * \file gui/font.h + * \author bombur + * \version 0.1 + * \date 4.10.2006 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#ifndef SP_FONT_H +#define SP_FONT_H + +#include + +/// Font object +class Font : public Resource +{ +public: + /// ctor + Font(); + /// dtor + ~Font(); + + BOOL Load(); + BOOL Unload(); + +public: + /// number of characters + int num; + /// bit data (width=64 max.) [num][height] + ULONGLONG **data; + /// font char widths array + int *widths; + /// font height + int height; + /// font baseline + int baseline; + + bool unloaded; +}; + +/// Font manager +class FontManager +{ +public: + /// ctor + FontManager(); + /// dtor + ~FontManager(); + + Font *GetDefaultFont(); + + Font *GetFont(char *fname); + + // Clear font cache + BOOL ClearFontData(); + +public: + SPHashListAbstract fonthash; + Font *def; + +protected: + Font *LoadFont(char *fname); +}; + + +extern FontManager *guifonts; + + +#endif // of SP_FONT_H diff --git a/src/gui/giflib.cpp b/src/gui/giflib.cpp new file mode 100644 index 0000000..efcb277 --- /dev/null +++ b/src/gui/giflib.cpp @@ -0,0 +1,881 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - GIF (ani-gif) library source file + * \file giflib.cpp + * \author bombur + * \version 0.1 + * \date 4.07.2004 + * + * Parts of the code taken from GIFLib, (C) Scott Heiman + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#include + +#include +#include +#include + +#include +#include + + +const BYTE IMAGESEP = 0x2C; +const BYTE EXTENSIONINTRODUCER = 0x21; +const BYTE EOGF = 0x3B; + +const BYTE GRAPHIC_CONTROL_LABEL = 0xF9; +const BYTE COMMENT_LABEL = 0xFE; +const BYTE PLAIN_TEXT_LABEL = 0x01; +const BYTE APPLICATION_EXTENSION_LABEL = 0xFF; + +char errormsg[256]; + +static void err(const char *str) +{ + if (str != errormsg) + strcpy(errormsg, str); + msg_error("Gif error: %s\n", str); +} + +/// inequality operator for ColorStruct +inline bool operator != (const ColorStruct &a, const ColorStruct &b) +{ + return (a.Red != b.Red || a.Green != b.Green || a.Blue != b.Blue); +} + +/// a nice, generic read function for binary files +template +bool SafeRead (T *data, unsigned int NumItems, FILE *file) +{ + if (fread((void *)data, sizeof(T), NumItems, file) != NumItems) + { + if (feof(file)) + err("Unexpected end-of-file in SafeRead"); + else + err("Failed to read data in SafeRead"); + return false; + } + return true; +} + +#if defined(__GNUC__) +#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 95) +#define ATTRIBUTE_PACKED __attribute__ ((packed)) +#endif +#endif +#ifdef WIN32 +#pragma pack(1) +#define ATTRIBUTE_PACKED +#endif + + +typedef struct GraphicControlStruct +{ + BYTE BlockSize; + BYTE PackedField; + WORD DelayTime; + BYTE TransparentColorIndex; + BYTE BlockTerminator; +} ATTRIBUTE_PACKED GraphicControl; + +typedef struct LogicalScreenDescriptorStruct +{ + WORD Width; + WORD Height; + BYTE PackedField; // see comments above in LSDField + BYTE BackgroundColorIndex; + BYTE PixelAspectRatio; +} ATTRIBUTE_PACKED LSD; + +typedef struct ImageDescriptorStruct +{ + WORD Left; + WORD Top; + WORD Width; + WORD Height; + BYTE Field; // see comments above in IDField +} ATTRIBUTE_PACKED ImageDescriptor; + +typedef struct ApplicationExtensionStruct +{ + BYTE BlockSize; //should always be 11 + char Identifier[8]; + BYTE AuthenticationCode[3]; +} ATTRIBUTE_PACKED ApplicationExtension; + +typedef struct PlainTextStruct +{ + BYTE BlockSize; // should always be 12 + WORD TextGridLeftPosition; + WORD TextGridTopPosition; + WORD TextGridTopWidth; + WORD TextGridTopHeight; + BYTE CharacterCellWidth; + BYTE CharacterCellHeight; + BYTE TextForegroundColorIndex; + BYTE TextBackgroundColorIndex; +} ATTRIBUTE_PACKED PlainTextExtension; + + +#ifdef WIN32 +#pragma pack() +#endif + + +char *fname = NULL; +/// ReadFile opens a GIF and stores the information in memory +bool GIFData::ReadFile(const char *filename) +{ + FILE *file; + { + file = fopen(filename, "rb"); + if (file == NULL) + { + sprintf(errormsg, "Could not open %s", filename); + err(errormsg); + goto err; + } + + fname = (char *)filename; + // check for signature + if (!SafeRead(Version, 3, file)) + return false; + Version[3] = 0; + if (strcmp(Version, "GIF") != 0) + { + sprintf(errormsg, "%s is not a .GIF file", filename); + err(errormsg); + return false; + } + + // get Version + if (!SafeRead(Version, 3, file)) + return false; + + // extract LogicalScreenDescriptor + LSD lsd; + if (!SafeRead(&lsd, 1, file)) + return false; + GlobalWidth = lsd.Width; + GlobalHeight = lsd.Height; + PixelDepth = (BYTE)(((lsd.PackedField & 0x70)>>4) + 1); + + // get the global color table if 1 is present + if ((lsd.PackedField & 0x80) > 0) + { + GlobalNumColors = (WORD)(1 << (int)((lsd.PackedField & 0x07) + 1)); + GlobalColorTable = new ColorStruct[GlobalNumColors]; + if (GlobalColorTable == NULL) + { + err("Could not allocate memory for color table"); + return false; + } + + if (!SafeRead(GlobalColorTable, GlobalNumColors, file)) + return false; + BackgroundColorIndex = lsd.BackgroundColorIndex; + } + else + { + BackgroundColorIndex = 0;//-1; + GlobalNumColors = 0; + } + + // practice has shown that if (1 << PixelDepth) is < GlobalNumColors + // then PixelDepth needs to be corrected + if ((1 << PixelDepth) < GlobalNumColors) + { + if (GlobalNumColors < 5) + PixelDepth = 2; + else if (GlobalNumColors < 9) + PixelDepth = 3; + else if (GlobalNumColors < 17) + PixelDepth = 4; + else if (GlobalNumColors < 33) + PixelDepth = 5; + else if (GlobalNumColors < 65) + PixelDepth = 6; + else if (GlobalNumColors < 129) + PixelDepth = 7; + else + PixelDepth = 8; + } + // read images + BYTE DataByte; + if (!SafeRead(&DataByte, 1, file)) + return false; + while (DataByte != EOGF) + { + switch (DataByte) + { + case EXTENSIONINTRODUCER: + ReadExtension(file); + break; + case IMAGESEP: + if (!ReadImage(file)) + goto err; + break; + default: + /*err("Unknown instruction")*/; + } + if (!SafeRead(&DataByte, 1, file)) + // [bombur]: the file is broken but let's give it a chance... + break; + } + + + // if more than 1 image exists then remove unneeded images + /*vector::iterator image = Images.begin(); + + while (image != Images.end() && Images.size() > 1) + { + if (image->DelayTime == 0) + image = Images.erase(image); + else + image++; + }*/ + + if (numImages < 1) + goto err; + fclose(file); + return true; + } +err: + if (file != NULL) + { + fclose(file); + } + return false; +} + + +bool GIFData::ReadExtension(FILE *file) +{ + BYTE block; + BYTE data[256]; + + BYTE Label; + if (!SafeRead(&Label, 1, file)) + return false; + + switch (Label) + { + case GRAPHIC_CONTROL_LABEL: + { + GraphicControl GC; + UseGC = true; + if (!SafeRead(&GC, 1, file)) + return false; + TransparentColorFlag = (GC.PackedField & 0x01) > 0; + if (TransparentColorFlag) + TransparentColorIndex = GC.TransparentColorIndex; + DelayTime = (WORD)(10 * GC.DelayTime); + // [bombur]: I guess it's better... + if (DelayTime < 10) + DelayTime = 100; + DisposalMethod = (WORD)((GC.PackedField & 0x1C) >> 2); + break; + } + // comment labels are read, but ignored. + case COMMENT_LABEL: + if (!SafeRead(&block, 1, file)) + return false; + while (block != 0) + { + if (!SafeRead(data, (unsigned)block, file)) + return false; + if (!SafeRead(&block, 1, file)) + return false; + } + break; + // plain text labels are read, but ignored. + case PLAIN_TEXT_LABEL: + { + PlainTextExtension pte; + if (!SafeRead(&pte, 1, file)) + return false; + if (!SafeRead(&block, 1, file)) + return false; + while (block > 0) + { + //memset((void *)data, 0, 256); + if (!SafeRead(data, (unsigned)block, file)) + return false; + if (!SafeRead(&block, 1, file)) + return false; + } + break; + } + // application extension labels are read, but ignored. + case APPLICATION_EXTENSION_LABEL: + { + ApplicationExtension AE; + if (!SafeRead(&AE, 1, file)) + return false; + if (!SafeRead(&block, 1, file)) + return false; + while (block != 0) + { + if (!SafeRead(data, (unsigned)block, file)) + return false; + if (!SafeRead(&block, 1, file)) + return false; + } + break; + } + default: + /*err("Unknown Extension")*/; + } + return true; +} + + +GIFImage *GIFData::GetImage(int index) +{ + if (index < 0 || index >= (int)numImages) + { + err("Invalid index in GetImage()"); + return NULL; + } + + return Images[index]; +} + + +bool GIFData::ReadImage(FILE *file) +{ + ImageDescriptor id; + if (!SafeRead(&id, 1, file)) + return false; + + // [bombur]: check if file is corrupt + if (id.Left > 1000 || id.Top > 1000 || id.Width > 5000 || id.Height > 5000) + return false; + + GIFImage *image = new GIFImage(this, id.Left, id.Top, id.Width, id.Height, + PixelDepth, (id.Field & 0x40) > 0, + TransparentColorFlag && UseGC ? TransparentColorIndex : -1, + UseGC ? DelayTime : 0, DisposalMethod); + + //if a local color table is present, then read it in, otherwise copy + // the global color table. + if (id.Field & 0x80) + { + DWORD NC = 1 << ((id.Field & 0x07) + 1); + image->ReadColorTable(NC, file); + msg_error("Gif: Multiple palettes not supported for file %s\n", fname); + } + else + image->CopyColorTable(GlobalNumColors, GlobalColorTable); + + // decode the bits + if (image->ReadBits(file)) + { + + // save the image in the array + Images = (GIFImage **)SPrealloc((void *)Images, (numImages + 1) * sizeof(GIFImage *)); + Images[numImages] = image; + numImages++; + } + UseGC = false; + return true; +} + + +// GIFImage functions +GIFImage::GIFImage(const GIFImage &g) +{ + typedef BYTE *pUChar; + + ColorTable = NULL; + ppBits = NULL; + + Parent = g.Parent; + Left = g.Left; + Top = g.Top; + Height = g.Height; + Width = g.Width; + ImageHeight = g.ImageHeight; + ImageWidth = g.ImageWidth; + PixelDepth = g.PixelDepth; + ImagePixelDepth = g.ImagePixelDepth; + NumColors = g.NumColors; + ScanLineWidth = g.ScanLineWidth; + Interlaced = g.Interlaced; + DelayTime = g.DelayTime; + DisposalMethod = g.DisposalMethod; + BackgroundColorIndex = g.BackgroundColorIndex; + TransparentColorIndex = g.TransparentColorIndex; + + // copy color table if 1 exists + if (g .ColorTable) + { + ColorTable = new ColorStruct[NumColors]; + if (ColorTable == NULL) + err("Could not allocate memory for color table in copy ctor"); + else + memcpy((void *)ColorTable, (void *)g.ColorTable, + NumColors*sizeof(ColorStruct)); + } + + // copy bits + // assumes complete memory has been allocated for g.ppBits + if (g.ppBits) + { + ppBits = new pUChar[ImageHeight]; + if (ppBits != NULL) + { + MakeBitmap(); + memcpy((void *)ppBits[0], (void *)g.ppBits[0], ImageWidth*ImageHeight); + } + } + else + { + ppBits = NULL; + } +} + + +GIFImage::GIFImage(GIFData *parent, int l, int t, int w, int h, int pd, + bool inter, int TCIndex, int dt, WORD dm, + bool /*bmp*/) +{ + Parent = parent; + Left = (WORD)l; + Top = (WORD)t; + Width = (WORD)w; + Height = (WORD)h; + PixelDepth = (BYTE)pd; + ImageWidth = Parent->GlobalWidth; + ImageHeight = Parent->GlobalHeight; + Interlaced = inter; + ppBits = 0; + ColorTable = 0; + DelayTime = (WORD)dt; + TransparentColorIndex = (short)TCIndex; + BackgroundColorIndex = 0;//-1; + DisposalMethod = dm; + + NumColors = (WORD)(1 << pd); + ColorTable = new ColorStruct[NumColors]; + if (ColorTable == NULL) + err("Could not allocate memory for color table in ctor"); + else + memset((void *)ColorTable, 0, NumColors*sizeof(ColorStruct)); + + // calculate the width of a scan line in bytes + switch (PixelDepth) + { + case 1: + ImagePixelDepth = 1; + break; + case 2: + case 3: + case 4: + ImagePixelDepth = 4; + break; + default: + ImagePixelDepth = 8; + break; + } + + ScanLineWidth = (ImageWidth * ImagePixelDepth + 7) / 8; + // [bombur]: we don't need pitch align + /* + if (ScanLineWidth % 4) + ScanLineWidth += (4 - ScanLineWidth % 4); + */ + + // allocate memory for bitmap pixels + typedef BYTE *pUChar; + ppBits = new pUChar[ImageHeight]; + if (ppBits == NULL) + err("Could not allocate memory for bitmap in ctor"); +} + +/// dtor +GIFImage::~GIFImage() +{ + if (ColorTable) + delete [] ColorTable; + + if (ppBits) + { + if (ppBits[0] != NULL) + delete [] ppBits[0]; + delete [] ppBits; + } +} + +BYTE *GIFImage::GetScanLine(int index) const +{ + if (index < 0 || index >= (int)Height) + { + // err("Illegal index in GIFImage::GetScanLine"); + return 0; + } + + return ppBits[index]; +} + +// taken out from ReadBits() to fit the stack... +#define MaxStackSize 4096 +#define NullCode (-1) + +static short prefix[MaxStackSize]; + +static BYTE block_size, data_size, first, packet[256], + pixel_stack[MaxStackSize+1], suffix[MaxStackSize], *top_stack, + *q_buffer; + + +/// This function decodes the GIF data. The logic was taken from +/// the ImageMagick source code. ;-) +bool GIFImage::ReadBits(FILE *file) +{ + DWORD dbgcnt; + int index, available, bits, code, clear, code_mask, code_size, + count, end_of_information, in_code, old_code; + + register DWORD i; + + register BYTE *c, *q; + + register DWORD datum; + + // [bombur]: + int pitch = ImageWidth;// (ImageWidth + 7)/8*8; + + // Initialize GIF data stream decoder. + q_buffer = new BYTE[Height*pitch]; + if (q_buffer == NULL) + { + err("Memory Allocation Error in ReadBits"); + return false; + } + + dbgcnt = 0; + if (!SafeRead(&data_size, 1, file)) + return false; + clear = 1 << data_size; + end_of_information = clear + 1; + available = clear + 2; + old_code = NullCode; + code_size = data_size + 1; + code_mask = (1 << code_size) - 1; + for (code = 0; code < clear; code++) + { + prefix[code] = 0; + suffix[code] = (BYTE) code; + } + // Decode GIF pixel stream. + datum = 0; + bits = 0; + c = 0; + count = 0; + first = 0; + top_stack = pixel_stack; + q = q_buffer; + + int qline = 0; + while (q < q_buffer + Height*pitch) + { + if (top_stack == pixel_stack) + { + if (bits < code_size) + { + // Load bytes until there is enough bits for a code. + if (count == 0) + { + // Read a new data block. + if (!SafeRead(&block_size, 1, file)) + return false; + count = block_size; + if (!SafeRead(packet, (unsigned)block_size, file)) + return false; + if (count <= 0) + break; + c = packet; + } + datum += (*c) << bits; + bits += 8; + c++; + count--; + continue; + } + // Get the next code. + code = datum & code_mask; + datum >>= code_size; + bits -= code_size; + // Interpret the code + if ((code > available) || (code == end_of_information)) + break; + if (code == clear) + { + // Reset decoder. + code_size = data_size + 1; + code_mask = (1 << code_size) - 1; + available = clear + 2; + old_code = NullCode; + continue; + } + if (old_code == NullCode) + { + *top_stack++ = suffix[code]; + old_code = code; + first = (BYTE) code; + continue; + } + in_code = code; + if (code >= available) + { + *top_stack++ = first; + code = old_code; + } + while (code >= clear) + { + *top_stack++ = suffix[code]; + code = prefix[code]; + } + first = suffix[code]; + // Add a new string to the string table, + if (available >= MaxStackSize) + break; + *top_stack++ = first; + prefix[available] = (short)old_code; + suffix[available] = first; + available++; + if (((available & code_mask) == 0) && (available < MaxStackSize)) + { + code_size++; + code_mask+=available; + } + old_code = in_code; + } + // Pop a pixel off the pixel stack. + top_stack--; + index = (*top_stack); + *q = (BYTE)index; + q++; + qline++; + if (qline >= (int)Width) + { + q += pitch - Width; + qline = 0; + } + dbgcnt++; + } + + if (dbgcnt < Width * Height) + { + delete [] q_buffer; + err("Corrupt .GIF image"); + return false; + } + + if (Interlaced) + { + static const int + interlace_rate[4] = { 8, 8, 4, 2 }, + interlace_start[4] = { 0, 4, 2, 1 }; + + BYTE *i_buffer = new BYTE[Height*pitch]; + if (!i_buffer) + { + delete [] q_buffer; + err("Memory allocation error"); + return false; + } + + memcpy((void *)i_buffer, (void *)q_buffer, Height*pitch); + memset((void *)q_buffer, 0, Height*pitch); + + int row = 0; + for (int pass = 0; pass < 4; pass++) + { + DWORD j = interlace_start[pass]; + while (j < Height) + { + BYTE *p = i_buffer + row * pitch; + q = q_buffer + j * pitch; + memcpy((void *)q, (void *)p, Width); + row++; + j += interlace_rate[pass]; + } + } + + delete [] i_buffer; + } + + // create the array to store the entire unpacked image + BYTE **unpackedBits = ppBits; + + // if this is not the 1st image... + if (Parent->numImages > 0) + { + int last; + for (last = Parent->numImages - 1; last >= 0 && Parent->Images[last]->DisposalMethod == 3; last--) + ; + GIFImage *lastImage = Parent->Images[last]; + + // unpackedBits is initialized to the previous image + lastImage->GetUnpackedBitArray(unpackedBits); + if (lastImage->DisposalMethod == 2) + { + for (DWORD i = lastImage->Top; i < lastImage->Height + lastImage->Top; i++) + { + BYTE *p = unpackedBits[i]; + memset((void *)(p+lastImage->Left), BackgroundColorIndex, + lastImage->Width); + } + } + } + else /*if (UseBitmap)*/ + { + memset((void *)unpackedBits[0], BackgroundColorIndex, pitch * ImageHeight); + } + + // add the subimage + q = q_buffer; + // if no transparent pixels are present then overwrite the existing image... + if (TransparentColorIndex == -1) + { + for (i = Top; i < Top + Height; i++) + { + memcpy((void *)(unpackedBits[i]+Left), (void *)q, Width); + q += pitch; + } + } + // ... otherwise, only overwrite non-transparent pixels + else + { + for (i = Top; i < Top + Height; i++) + { + BYTE *oldImage = unpackedBits[i]+Left; + /* + if (UseBitmap)*/ + { + for (BYTE *newImage = q; newImage < q + Width; newImage++, oldImage++) + { + if ((short)(*newImage) != TransparentColorIndex) + *oldImage = *newImage; + } + } + /*else + memcpy((void *)oldImage, (void *)q, Width); + */ + q += pitch; + } + } + + delete [] q_buffer; + + // read the terminator + SafeRead(&block_size, 1, file); + /* + if (block_size != 0) + err("Terminator was not found in the expected location"); + */ + return true; +} + +/// Read the color table from the GIF file and copy it to the bitmap (if one is present) +void GIFImage::ReadColorTable(DWORD NC, FILE *file) +{ + NumColors = (WORD)NC; + SafeRead(ColorTable, NC, file); +/* + // mess with transparency & background if we are creating a bitmap + // set the transparent color to the screen color if this is the first image + if (TransparentColorIndex > -1 && Parent->numImages == 0) + ColorTable[TransparentColorIndex] = Parent->ScreenColor; + + // if the color table contains the screen color then set the + // background color index to the appropriate value; otherwise, + // set it to the parent's background color index + bool NotFound = true; + + while (++BackgroundColorIndex < NumColors && NotFound) + NotFound = ColorTable[BackgroundColorIndex] != Parent->ScreenColor; + + if (NotFound) + BackgroundColorIndex = (int)Parent->BackgroundColorIndex; + else + BackgroundColorIndex--; +*/ + MakeBitmap(); +} + + +/// This function copies the global color table to the image color table and +/// create the bitmap. +void GIFImage::CopyColorTable(WORD GlobalNumColors, ColorStruct *GlobalColorTable) +{ + NumColors = GlobalNumColors; + memcpy((void *)ColorTable, (void *)GlobalColorTable, + GlobalNumColors*sizeof(ColorStruct)); + + // mess with transparency & background if we are creating a bitmap + // set the transparent color to the screen color if this is the first image + // [bombur]: + if (TransparentColorIndex > -1 ) // && Parent->numImages == 0 + ColorTable[TransparentColorIndex] = Parent->ScreenColor; +/* + // if the color table contains the screen color then set the + // background color index to the appropriate value; otherwise, + // set it to the parent's background color index + bool NotFound = true; + while (++BackgroundColorIndex < NumColors && NotFound) + NotFound = ColorTable[BackgroundColorIndex] != Parent->ScreenColor; + + if (NotFound) + BackgroundColorIndex = (int)Parent->BackgroundColorIndex; + else + BackgroundColorIndex--; +*/ + + MakeBitmap(); +} + +/// This function creates the bitmap. This function assumes that the color +/// table has already been stored in memory. +bool GIFImage::MakeBitmap() +{ + ppBits[0] = new BYTE[ImageWidth*ImageHeight]; + if (ppBits[0] == NULL) + { + err("Memory Allocation Error"); + return false; + } + + for (DWORD i = 1; i < ImageHeight; i++) + ppBits[i] = ppBits[i-1] + ImageWidth; + + return true; +} + +void GIFImage::GetUnpackedBitArray(BYTE **bits) +{ + if (bits == NULL) + return; + for (DWORD i = 0; i < ImageHeight; i++) + { + memcpy((void *)(bits[i]), (void *)(ppBits[i]), ImageWidth); + } +} + diff --git a/src/gui/giflib.h b/src/gui/giflib.h new file mode 100644 index 0000000..c083836 --- /dev/null +++ b/src/gui/giflib.h @@ -0,0 +1,245 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - GIF (ani-gif) library header file + * \file giflib.h + * \author bombur + * \version 0.1 + * \date 4.07.2004 + * + * Parts of the code taken from GIFLib, (C) Scott Heiman + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#ifndef SP_GIFLIB_H +#define SP_GIFLIB_H + +#include + +#ifdef WIN32 +#pragma pack(1) +#endif + +typedef struct ColorStruct +{ + BYTE Red; + BYTE Green; + BYTE Blue; + +} ATTRIBUTE_PACKED ColorStruct; + +#ifdef WIN32 +#pragma pack() +#endif + +class GIFData; + +/// Contains GIF image +class GIFImage +{ +public: + friend class GIFData; + /// ctor + GIFImage() + { + Parent = 0; + Left = Top = 0; + Height = Width = 0; + ImagePixelDepth = PixelDepth = 0; + NumColors = 0; + ScanLineWidth = 0; + ColorTable = 0; + ppBits = 0; + Interlaced = false; + TransparentColorIndex = BackgroundColorIndex = -1; + DelayTime = 0; + DisposalMethod = 0; + } + + /// ctor used by GIFData to create a GIFImage + GIFImage (GIFData *parent, int l, int t, int w, int h, int pd, + bool inter, int TCIndex = -1, int dt = 0, WORD dm = 0, + bool bmp = true); + /// copy ctor + GIFImage (const GIFImage &g); + /// dtor + ~GIFImage(); + + /// Access functions + WORD GetWidth() const { return (WORD)ImageWidth; } + WORD GetHeight() const { return (WORD)ImageHeight; } + BYTE BitsPerPixel() const { return PixelDepth; } + BYTE ImageBitsPerPixel() const { return ImagePixelDepth; } + WORD GetNumColors() const { return NumColors; } + ColorStruct *GetColorTable() const { return ColorTable; } + + BYTE *GetData() { BYTE *d = ppBits[0]; ppBits[0] = NULL; return d; } + + BYTE *GetScanLine (int index) const; + DWORD GetScanLineWidth() const { return ScanLineWidth; } + WORD GetTimeDelay() const { return DelayTime; } + + void GetUnpackedBitArray (BYTE **bits); + + short GetBackgroundColorIndex() const { return BackgroundColorIndex; } + short GetTransparentColorIndex() const { return TransparentColorIndex; } + + /// Access functions used by the multimedia timer callback function + BOOL EraseBkgnd() const { return DisposalMethod == 2 ? TRUE : FALSE; } + +protected: + /// the GIFData object that owns this image + GIFData *Parent; + /// bytes needed to store 1 line of data in an HBITMAP + DWORD ScanLineWidth; + /// Width and height of the subimage in pixels + DWORD Height; + DWORD Width; + /// Width and height of the merged image in pixels + DWORD ImageHeight; + DWORD ImageWidth; + /// the location of the upper, left corner of the image relative to Image[0] + WORD Left; + WORD Top; + /// the number of bits needed to define all colors in the image + BYTE PixelDepth; + /// the number of bits needed to define all of the colors in the HBITMAP. + /// must be 1, 4, or 8 (I have not seen any 24 or 32 bit GIFs) + BYTE ImagePixelDepth; + /// number of colors in the local color table + WORD NumColors; + /// the local color table + ColorStruct *ColorTable; + /// an 2-D array of bits that define the image. If UseBitmap == true then + /// the array is dimensioned as required by an HBITMAP; otherwise, the + /// dimensions are ppBits[ImageHeight][ImageWidth] + BYTE **ppBits; + /// true if the image is interlaced + bool Interlaced; + /// the index in the local color table that is transparent. + /// set to -1 if there is no transparent color + short TransparentColorIndex; + /// background color index + /// set to -1 if there is no background color + short BackgroundColorIndex; + /// the time that this image is displayed if it is an animated GIF + WORD DelayTime; + /// the disposal method. Determines how the image is drawn if the GIF + /// is animated. + /// DisposalMethod Action + /// 0 Nothing special. Treat as DisposalMethod == 1 + /// 1 image remains in place + /// 2 background is restored before merging w/ nxt img + /// 3 image is retained and merged with next image + WORD DisposalMethod; + + /// create hBitmap + bool MakeBitmap(); + /// used to copy the GIFData's global color table into the local color table + void CopyColorTable (WORD GlobalNumColors, + ColorStruct *GlobalColorTable); + /// reads the local color table from the GIF file + void ReadColorTable (DWORD NC, FILE *file); + /// decodes the bit data from the GIF file + bool ReadBits (FILE *file); +}; + +/// Contains entire GIF data (from file) +class GIFData +{ +public: + /// ctor + GIFData() + { + Images = (GIFImage **)SPmalloc(sizeof(GIFImage *)); + numImages = 0; + GlobalWidth = GlobalHeight = 0; + PixelDepth = 0; + GlobalNumColors = 0; + GlobalColorTable = 0; + TransparentColorIndex = BackgroundColorIndex = 0; + TransparentColorFlag = false; + UseGC = false; + DelayTime = 0; + ScreenColor.Red = 0/*GetRValue( color )*/; + ScreenColor.Green = 0/*GetGValue( color )*/; + ScreenColor.Blue = 0/*GetBValue( color )*/; + } + + /// dtor + ~GIFData() + { + if (GlobalColorTable != NULL) + delete [] GlobalColorTable; + for (int i = 0; i < numImages; i++) + delete Images[i]; + SPfree(Images); + } + + /// access functions + WORD GetWidth() const { return GlobalWidth; } + WORD GetHeight() const { return GlobalHeight; } + BYTE BitsPerPixel() const { return PixelDepth; } + BYTE GetNumColors() const { return (char)GlobalNumColors; } + ColorStruct *GetColorTable() const { return GlobalColorTable; } + + GIFImage *GetImage(int index); + DWORD GetNumImages() const { return numImages; } + + /// reads a GIF and stores the contents in memory + bool ReadFile (const char *filename); + + friend class GIFImage; + +protected: + /// the height and width of the largest image in the group + WORD GlobalHeight; + WORD GlobalWidth; + /// the number of pixels needed to access all colors used by the image + BYTE PixelDepth; + /// the number of colors in the global color table + WORD GlobalNumColors; + /// the global color table + ColorStruct *GlobalColorTable; + /// the version number + char Version[4]; + /// the index to the background color + BYTE BackgroundColorIndex; + /// an array to hold all of the images in the GIF + GIFImage **Images; + int numImages; + /// --- the following variables are used to hold graphic control information + /// true if a graphic control extension was be used with the next image + bool UseGC; + /// the index to the transparent color + BYTE TransparentColorIndex; + /// true if a transparent color is present + bool TransparentColorFlag; + /// the time that an image will be displayed + WORD DelayTime; + /// the disposal method for the next GIF image + WORD DisposalMethod; + /// screen color + ColorStruct ScreenColor; + + /// reads GIF extensions + bool ReadExtension (FILE *file); + /// reads the color table and bits for each image + bool ReadImage (FILE *file); +}; + + +#endif // of SP_GIFLIB_H diff --git a/src/gui/image.cpp b/src/gui/image.cpp new file mode 100644 index 0000000..41e463c --- /dev/null +++ b/src/gui/image.cpp @@ -0,0 +1,304 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - image class impl. + * \file image.cpp + * \author bombur + * \version 0.1 + * \date 4.10.2006 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#include +#include +#include + +#include +#include + +ImageManager *guiimg = NULL; + + +ImageData::ImageData() +{ + data = NULL; + dt = NULL; + num_frames = 0; + width = height = 0; + total_time = 0; + + unloaded = false; +} + +ImageData::~ImageData() +{ + Unload(); +} + +BOOL ImageData::Load() +{ + Unload(); + GIFData *gifdata = new GIFData(); + if (gifdata == NULL) + return FALSE; + if (!gifdata->ReadFile(name)) + { + delete gifdata; + return FALSE; + } + + data = (BYTE **)SPmalloc(sizeof(BYTE *) * (gifdata->GetNumImages() + 1)); + +#if 0 + msg("+LOAD %s...\n", name); + SPMemoryManager &memManager = SPMemoryManager::GetHandle(); + FILE *fp = fopen("sp_log.txt", "at"); + memManager.DumpLastAlloc(fp); + fclose(fp); +#endif + + + if (data == NULL || gifdata->GetNumImages() < 1) + { + msg_error("Image: image data not found for %s.\n", name); + delete gifdata; + return FALSE; + } + num_frames = gifdata->GetNumImages(); + width = gifdata->GetWidth(); + height = gifdata->GetHeight(); + dt = new int [num_frames]; + total_time = 0; + for (int i = 0; i < (int)gifdata->GetNumImages(); i++) + { + GIFImage *img = gifdata->GetImage(i); + if (img == NULL) + { + delete gifdata; + return FALSE; + } + if (img->GetWidth() != gifdata->GetWidth() || img->GetHeight() != gifdata->GetHeight()) + { + delete gifdata; + return FALSE; + } + data[i] = img->GetData(); + dt[i] = img->GetTimeDelay(); + total_time += dt[i]; + } + delete gifdata; + +// msg("GIF %s loaded OK.\n", name); + + unloaded = false; + return TRUE; +} + +BOOL ImageData::Unload() +{ + if (data != NULL) + { +#if 0 +msg("-UNLOAD %s...\n", name); +#endif + for (int i = 0; i < num_frames; i++) + { + SPSafeDeleteArray(data[i]); + } + } + SPSafeFree(data); + SPSafeDeleteArray(dt); + unloaded = true; + return TRUE; +} + +////////////////////////////////////////////////////////////////////////// + +Image::Image() +{ + img = NULL; + flipx = flipy = false; + cur_frame = 0; + last_time = -1; + + updateobj = NULL; +} + +Image::~Image() +{ +} + +bool Image::SetSource(char *fname) +{ + img = guiimg->GetImageData(fname); + if (img == NULL) + return false; + SetWidth(img->width); + SetHeight(img->height); + auto_width = img->width; + auto_height = img->height; + dirty = true; + return true; +} + +void Image::SetFlipX(bool fx) +{ + flipx = fx; + dirty = true; +} + +void Image::SetFlipY(bool fy) +{ + flipy = fy; + dirty = true; +} + +bool Image::Update(int curtime) +{ + int oldcf = cur_frame; + if (last_time < 0 || img == NULL || img->total_time == 0) + { + cur_frame = 0; + last_time = curtime; + } else + { + int dt = (curtime - last_time) % img->total_time; + for (int i = 0, cf = cur_frame; i < img->num_frames; i++) + { + cf = (cf + 1) % img->num_frames; + int d = img->dt[cf]; + if (d > dt) + break; + cur_frame = cf; + last_time = curtime; + dt -= d; + if (dt <= 0) + break; + } + } + + if (cur_frame != oldcf) + dirty = true; + return true; +} + +bool Image::Update(Context *context, int x1, int y1, int x2, int y2) +{ + if (img == NULL || context == NULL) + return false; + if (img->unloaded) + { + if (!img->Load()) + return false; + } + if (img->data == NULL || img->num_frames < 1) + return false; + if (cur_frame < 0) + cur_frame = 0; + cur_frame %= img->num_frames; + + if (img->width < 1 || img->height < 1) + return false; + if (x1 >= img->width) + return false; + if (y1 >= img->height) + return false; + if (x2 >= img->width) x2 = img->width - 1; + if (y2 >= img->height) y2 = img->height - 1; + + BYTE *sd = img->data[cur_frame] + (flipy ? img->height-y1-1 : y1) * img->width + (flipx ? x2 : x1); + BYTE *dd = context->data + y1 * context->pitch + x1; + int len = x2 - x1 + 1; + int iw = (flipy) ? -img->width : img->width; + + if (flipx) + { + for (int iy = y1; iy <= y2; iy++) + { + register BYTE *sdi = sd; + register BYTE *ddi = dd; + for (int ix = len - 1; ix >= 0; ix--) + *ddi++ = *--sdi; + dd += context->pitch; + sd += iw; + } + } else + { + for (int iy = y1; iy <= y2; iy++) + { + memcpy(dd, sd, len); + dd += context->pitch; + sd += iw; + } + } + + return true; +} + +////////////////////////////////////////////////////////////////////////// + +ImageManager::ImageManager() +{ + datahash.SetN(resource_num_hash); +} + +ImageManager::~ImageManager() +{ +} + +BOOL ImageManager::ClearImageData() +{ + ImageData *cur = datahash.GetFirst(); + while (cur != NULL) + { + cur->Unload(); + cur = datahash.GetNext(*cur); + } + return TRUE; +} + +ImageData *ImageManager::GetImageData(char *fname) +{ + // first, search the hash + static ImageData testdata; + testdata.SetConstName(fname); + ImageData *dat = datahash.Get(testdata); + testdata.name = NULL; + if (dat != NULL) + return dat; + ImageData *newdat = new ImageData(); + newdat->SetName(fname); + if (!newdat->Load()) + { + delete newdat; + return NULL; + } + + datahash.Add(newdat); + + return newdat; +} + +void ImageManager::DumpImages() +{ + ImageData *cur = datahash.GetFirst(); + while (cur != NULL) + { + if (!cur->unloaded) + msg("IMAGE %s\n", cur->name); + cur = datahash.GetNext(*cur); + } +} diff --git a/src/gui/image.h b/src/gui/image.h new file mode 100644 index 0000000..8dd70c7 --- /dev/null +++ b/src/gui/image.h @@ -0,0 +1,111 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - image class header file + * \file image.h + * \author bombur + * \version 0.1 + * \date 4.10.2006 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#ifndef SP_IMAGE_H +#define SP_IMAGE_H + +#include +#include + +/// Image data class (incl. animation frames) +class ImageData : public Resource +{ +public: + /// ctor + ImageData(); + /// dtor + ~ImageData(); + + BOOL Load(); + BOOL Unload(); + +public: + BYTE **data; // BYTE *data[num_frames]; + int num_frames; + int width, height; // all frames have the same size + + /// delta time for each frame, in milliseconds + int *dt; + int total_time; + + bool unloaded; +}; + +/// Image window class +class Image : public Window +{ +public: + /// ctor + Image(); + /// dtor + virtual ~Image(); + + virtual bool Update(int curtime); + + /// Update part of image (rect in LOCAL window coords) + virtual bool Update(Context *context, int x1, int y1, int x2, int y2); + +public: + + bool SetSource(char *fname); + + void SetFlipX(bool); + void SetFlipY(bool); + + /// Pointer to the image data used + ImageData *img; + /// Current frame index + int cur_frame; + int last_time; + bool flipx, flipy; + + /// update queue object (used by script) + friend class ScriptTimerObject; + ScriptTimerObject *updateobj; +}; + +/// Image manager class (contains all image data and other info) +class ImageManager +{ +public: + /// ctor + ImageManager(); + /// dtor + ~ImageManager(); + +public: + + // Clear image cache + BOOL ClearImageData(); + + ImageData *GetImageData(char *fname); + + SPHashListAbstract datahash; + + void DumpImages(); +}; + +extern ImageManager *guiimg; + +#endif // of SP_IMAGE_H diff --git a/src/gui/jpeg.cpp b/src/gui/jpeg.cpp new file mode 100644 index 0000000..11b22a2 --- /dev/null +++ b/src/gui/jpeg.cpp @@ -0,0 +1,819 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - JPEG file display source file + * \file jpeg.cpp + * \author bombur + * \version 0.1 + * \date 4.07.2004 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include +#include "jpeg.h" + +//#define JPEG_USE_MMAP + +const int YUV_BUFFER_MARGIN = 16; +const int EXIF_JPEG_MARKER = JPEG_APP0 + 1; +const char *EXIF_IDENT_STRING = "Exif\000\000"; +enum +{ + JPEG_BIG_ENDIAN = 0, + JPEG_LITTLE_ENDIAN +}; + +static BYTE *Y = NULL, *UV = NULL; +static int cur_w = 0, cur_h = 0; +static jmp_buf setjmp_buffer; +static FILE *jpegfile = NULL; + +const int frame_width = 720, frame_height = 480; + +/// \WARNING! Works only on little-endian systems! +static inline WORD jpeg_exif_get16(BYTE *ptr, DWORD endian) +{ + if (endian == JPEG_BIG_ENDIAN) + return (WORD)((ptr[0] << 8) | ptr[1]); + return (WORD)((ptr[1] << 8) | ptr[0]); +} + +/// \WARNING! Works only on little-endian systems! +static inline DWORD jpeg_exif_get32(BYTE *ptr, DWORD endian) +{ + if (endian == JPEG_BIG_ENDIAN) + return (ptr[0] << 24) | (ptr[1] << 16) | (ptr[2] << 8) | ptr[3]; + return (ptr[3] << 24) | (ptr[2] << 16) | (ptr[1] << 8) | ptr[0]; +} + + +void jpeg_dealloc() +{ + if (Y != NULL) + { +#ifdef JPEG_USE_MMAP + munmap(Y - YUV_BUFFER_MARGIN, cur_w * cur_h + YUV_BUFFER_MARGIN); +#else + SPfree(Y - YUV_BUFFER_MARGIN); +#endif + Y = NULL; + } + if (UV != NULL) + { +#ifdef JPEG_USE_MMAP + munmap(UV - YUV_BUFFER_MARGIN, cur_w * cur_h / 2 + YUV_BUFFER_MARGIN); +#else + SPfree(UV - YUV_BUFFER_MARGIN); +#endif + UV = NULL; + } +} + +BOOL jpeg_alloc_yuv(int w, int h) +{ + if (w != cur_w || h != cur_h || Y == NULL || UV == NULL) + { + jpeg_dealloc(); +#ifdef JPEG_USE_MMAP + Y = (BYTE *)mmap(0, (w * h) + YUV_BUFFER_MARGIN, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0); + UV = (BYTE *)mmap(0, (w * h / 2) + YUV_BUFFER_MARGIN, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0); +#else + Y = (BYTE *)SPmalloc((w * h) + YUV_BUFFER_MARGIN); + UV = (BYTE *)SPmalloc((w * h / 2) + YUV_BUFFER_MARGIN); +#endif + if (Y == NULL || UV == NULL) + return FALSE; + Y += YUV_BUFFER_MARGIN; + UV += YUV_BUFFER_MARGIN; + cur_w = w; + cur_h = h; + } + return TRUE; +} + +BOOL jpeg_init() +{ + return TRUE; +} + +BOOL jpeg_deinit() +{ + return TRUE; +} + +//jmp_buf jpg_err; +bool was_error = false; + +void jpeg_error_exit (j_common_ptr cinfo) +{ + char buffer[JMSG_LENGTH_MAX]; + + // Create the message + (*cinfo->err->format_message) (cinfo, buffer); + msg_error("Jpeg error: %s\n", buffer); + was_error = true; + longjmp(setjmp_buffer, 1); +} + +BOOL jpeg_show (char *filename, int hscale, int vscale, int offsx, int offsy, int *param_rot) +{ + struct jpeg_decompress_struct cinfo; + struct jpeg_error_mgr jerr; + + JSAMPARRAY scanline; + int nb_lines_buffer, nb_lines_read, line_size; + int i, j, k; + +#ifdef WIN32 + if (filename[0] == '/') filename++; +#endif + + bool is_external_img = (memcmp(filename, "/img/", 5) != 0 && memcmp(filename, "img/", 4) != 0); + + int frame_left, frame_top, frame_right, frame_bottom; + khwl_get_window_frame(&frame_left, &frame_top, &frame_right, &frame_bottom); + int frame_width, frame_height; + + KHWL_WINDOW maxdst; + khwl_getproperty(KHWL_VIDEO_SET, evMaxDisplayWindow, sizeof(maxdst), &maxdst); + + frame_width = ::frame_width; + frame_height = ::frame_height; + + int frame_w = frame_right - frame_left + 1; + int frame_h = frame_bottom - frame_top + 1; + + bool no_rescale = frame_w == frame_width && frame_h == frame_height, partial_rescale = false; +#if 0 + if (no_rescale && is_external_img) + { + khwl_display_clear(); + } +#endif + + was_error = false; + + jpegfile = fopen(filename, "rb"); + if (jpegfile == NULL) + { + printf("Can't open source file %s\n", filename); + return FALSE; + } + + cinfo.err = jpeg_std_error(&jerr); + cinfo.err->error_exit = jpeg_error_exit; + + if (setjmp(setjmp_buffer)) + { + fclose(jpegfile); + return FALSE; + } + + jpeg_create_decompress(&cinfo); + jpeg_save_markers(&cinfo, EXIF_JPEG_MARKER, 0xffff); + jpeg_stdio_src(&cinfo, jpegfile); + jpeg_read_header(&cinfo, TRUE); + + const WORD orientation_tag_id = 0x112; + + // get Exif data (orientation) + int rot = param_rot != NULL ? *param_rot : 0; + for (jpeg_saved_marker_ptr mark = cinfo.marker_list; mark != NULL; mark = mark->next) + { + switch (mark->marker) + { + case EXIF_JPEG_MARKER: + if (mark->data_length < (6+4*3+2+12)) // 1 tag at least... + break; + if (memcmp (mark->data, EXIF_IDENT_STRING, 6) != 0) + break; + + BYTE *d = mark->data + 6; + for (DWORD i = 6; i < 16; d++, i++) + { + DWORD endian = 0; + static const char leth[] = {0x49, 0x49, 0x2a, 0x00}; + static const char beth[] = {0x4d, 0x4d, 0x00, 0x2a}; + + if (memcmp (d, leth, 4) == 0) + endian = JPEG_LITTLE_ENDIAN; + else if (memcmp (d, beth, 4) == 0) + endian = JPEG_BIG_ENDIAN; + else + continue; + + i += jpeg_exif_get32(d + 4, endian); + d = mark->data + i; + if ((i + 2) > mark->data_length) + return 0; + WORD tags = jpeg_exif_get16(d, endian); + if ((i + 2 + tags * 12) > mark->data_length) + return 0; + for (d += 2; tags--; d += 12) + { + WORD tag_id = jpeg_exif_get16(d, endian); + if (tag_id == orientation_tag_id) + { + WORD type = jpeg_exif_get16(d + 2, endian); + DWORD count = jpeg_exif_get32(d + 4, endian); + + if (type == 3 && count == 1) + { + int orientation = jpeg_exif_get16(d + 8, endian); + if (rot < 0) // set only if rotation is undefined + { + switch (orientation) + { + case 1: rot = 0; break; + case 3: rot = 2; break; + case 6: rot = 1; break; + case 8: rot = 3; break; + } + } + break; + } + } + } + break; + } + break; + } + } + if (rot < 0 || rot > 3) + rot = 0; + + cinfo.out_color_space = JCS_YCbCr; + cinfo.dct_method = JDCT_IFAST; + cinfo.scale_num = 1; + cinfo.quantize_colors = FALSE; + + // Set it to 2 to use down-scaling for all big JPEGs (more quality), or set to 1 for default up-scaling. + int mul = settings_get(SETTING_HQ_JPEG) ? 2 : 1; + //if (rot == 0) mul = 1; + + int rotated_frame_w = (rot == 0 || rot == 2) ? frame_w : frame_h; + int rotated_frame_h = (rot == 0 || rot == 2) ? frame_h : frame_w; + int rotated_frame_width = (rot == 0 || rot == 2) ? frame_width : frame_height; + int rotated_frame_height = (rot == 0 || rot == 2) ? frame_height : frame_width; + + int scalefac = 1; + while (scalefac < 32 && (((int)cinfo.image_width / (mul*scalefac)) > rotated_frame_w + || ((int)cinfo.image_height / (mul*scalefac)) > rotated_frame_h)) + { + scalefac = scalefac * 2; + } + if (scalefac > 8) + { + scalefac = 8; + if ((int)cinfo.image_width / scalefac > rotated_frame_width || + (int)cinfo.image_height / scalefac > rotated_frame_height) + { + return FALSE; + } + } + + cinfo.scale_denom = scalefac; + + jpeg_start_decompress(&cinfo); + + if (was_error) + return FALSE; + + if ((int)cinfo.output_width > rotated_frame_width * mul || (int)cinfo.output_height > rotated_frame_height * mul + || (int)cinfo.output_width < 1 || (int)cinfo.output_height < 1) + { + return FALSE; + } + if ((int)cinfo.output_width > rotated_frame_width || (int)cinfo.output_height > rotated_frame_height) + { + no_rescale = false; + partial_rescale = true; + } + + int scaled_width = (int)cinfo.image_width; + int scaled_height = (int)cinfo.image_height; + + int rescale_width, rescale_height; + if (!no_rescale) + { + if ((int)cinfo.output_width < 3 || (int)cinfo.output_height < 3) + return FALSE; + rescale_width = rotated_frame_h * scaled_width / scaled_height; + rescale_height = rotated_frame_w * scaled_height / scaled_width; + + if (rescale_width > rotated_frame_w) + rescale_width = rotated_frame_w; + else + rescale_height = rotated_frame_h; + + while (scaled_width < rescale_width / 5 || scaled_height < rescale_height / 5) + { + rescale_width /= 2; + rescale_height /= 2; + } + + + scaled_width = rescale_width; + scaled_height = rescale_height; + } else + { + rescale_width = cinfo.output_width; + rescale_height = cinfo.output_height; + } + + if (!jpeg_alloc_yuv(frame_width, frame_height)) + { + msg("jpeg: Memory ERROR!\n"); + msg_sysinfo(); + jpeg_dealloc(); + return FALSE; + } + + line_size = cinfo.output_width * cinfo.output_components; + nb_lines_buffer = 1; + + scanline = (JSAMPARRAY) SPmalloc(nb_lines_buffer * sizeof(JSAMPROW)); + if (scanline == NULL) + return FALSE; + for (i = 0; i < nb_lines_buffer; i++) + { + scanline[i] = (JSAMPROW) SPmalloc((line_size + 1) * sizeof(JSAMPLE)); + if (scanline[i] == NULL) + return FALSE; + } + + int jmul, imgwidth, imgheight, imglf = 0, imgtp = 0; + int maxj = rotated_frame_h; + + imgtp = MAX((rotated_frame_w - rescale_width) / 2, 0); + imglf = MAX((rotated_frame_h - rescale_height) / 2, 0); + + if (partial_rescale) + { + imgwidth = (int)cinfo.output_width; + imgheight = (int)cinfo.output_height; + } else + { + if (rot == 0 || rot == 2) + { + imgwidth = MIN((int)cinfo.output_width, frame_width); + imgheight = MIN((int)cinfo.output_height, frame_height); + } else + { + imgwidth = MIN((int)cinfo.output_width, frame_height); + imgheight = MIN((int)cinfo.output_height, frame_width); + } + } + + if (rot == 0 || rot == 3) + { + j = 0; + jmul = 1; + } + else + { + j = maxj - 1; + jmul = -1; + } + + int jd = 0, oldj = -1, jstep = 1; + bool read_scan = true; + + for (i = 0; j >= 0 && j < maxj; ) + { + int imgw = 0; + if (i >= imglf && i < imgheight + imglf) + { + if (read_scan) + { + nb_lines_read = jpeg_read_scanlines(&cinfo, scanline, nb_lines_buffer); + if (nb_lines_read < 1) + break; + } + imgw = imgwidth; + } + if (read_scan) + i++; + + BYTE *y; + BYTE *uv; + JSAMPROW sl = *scanline; + + if (j != oldj) + { + if (rot == 0) + { + y = Y + j * frame_width; + uv = UV + j / 2 * frame_width; + BYTE *scl = sl; + for (k = 0; k < imgtp; k++) + { + *y++ = 0; + if (((j & 1) == 0) && ((k & 1) == 0)) + { + *uv++ = 128; + *uv++ = 128; + } + } + if (no_rescale) + { + for (int kk = 0; kk < imgw; kk++, k++) + { + *y++ = *scl; + if (((j & 1) == 0) && ((k & 1) == 0)) + { + *uv++ = scl[1]; + *uv++ = scl[2]; + } + scl += 3; + } + } else + { + int d = 0; + for (int kk = 0; kk < imgw; k++) + { + *y++ = *scl; + if (((j & 1) == 0) && ((k & 1) == 0)) + { + *uv++ = scl[1]; + *uv++ = scl[2]; + } + d += imgw; + while (d >= rescale_width) + { + scl += 3; + kk++; + d -= rescale_width; + } + } + } + for ( ; k < frame_w; k++) + { + *y++ = 0; + if (((j & 1) == 0) && ((k & 1) == 0)) + { + *uv++ = 128; + *uv++ = 128; + } + } + } + else if (rot == 1) + { + y = Y + j; + uv = UV + j; + for (k = 0; k < imgtp; k++) + { + *y = 0; + y += frame_width; + if (((j & 1) == 0) && ((k & 1) == 0)) + { + *uv = 128; + *(uv+1) = 128; + uv += frame_width; + } + } + if (no_rescale) + { + for (int kk = 0; kk < imgw; kk++, k++) + { + BYTE *scl = sl + kk * 3; + *y = *scl; + y += frame_width; + if (((j & 1) == 0) && ((k & 1) == 0)) + { + *uv = scl[1]; + *(uv+1) = scl[2]; + uv += frame_width; + } + } + } else + { + BYTE *scl = sl; + int d = 0; + for (int kk = 0; kk < imgw; k++) + { + *y = *scl; + y += frame_width; + if (((j & 1) == 0) && ((k & 1) == 0)) + { + *uv = scl[1]; + *(uv+1) = scl[2]; + uv += frame_width; + } + d += imgw; + while (d >= rescale_width) + { + scl += 3; + kk++; + d -= rescale_width; + } + } + } + for ( ; k < frame_h; k++) + { + *y = 0; + y += frame_width; + if (((j & 1) == 0) && ((k & 1) == 0)) + { + *uv = 128; + *(uv+1) = 128; + uv += frame_width; + } + } + } + else if (rot == 2) + { + y = Y + (j) * frame_width + frame_w; + uv = UV + (j / 2) * frame_width + frame_w; + + k = frame_w; + + for ( ; k > frame_w - imgtp; ) + { + k--; + *--y = 0; + if (((j & 1) == 0) && ((k & 1) == 0)) + { + *--uv = 128; + *--uv = 128; + } + } + + if (no_rescale) + { + BYTE *scl = sl; + for (int kk = 0; kk < imgw; kk++ ) + { + k--; + *--y = *scl; + if (((j & 1) == 0) && ((k & 1) == 0)) + { + *--uv = scl[2]; + *--uv = scl[1]; + } + scl += 3; + } + } else + { + BYTE *scl = sl; + int d = 0; + for (int kk = 0; kk < imgw; ) + { + *--y = *scl; + k--; + if (((j & 1) == 0) && ((k & 1) == 0)) + { + *--uv = scl[2]; + *--uv = scl[1]; + } + d += imgw; + while (d >= rescale_width) + { + scl += 3; + kk++; + d -= rescale_width; + } + } + } + + for (; k > 0; k--) + { + *--y = 0; + if (((j & 1) == 0) && ((k & 1) == 0)) + { + *--uv = 128; + *--uv = 128; + } + } + + } + else if (rot == 3) + { + y = Y + j; + uv = UV + j; + int iw = (int)cinfo.output_width; + for (k = frame_h; k > frame_h - imgtp; ) + { + *y = 0; + y += frame_width; + k--; + if (((j & 1) == 0) && ((k & 1) == 0)) + { + *uv = 128; + *(uv+1) = 128; + uv += frame_width; + } + } + if (no_rescale) + { + BYTE *scl = sl + (iw - 1) * 3; + for (int kk = iw - 1; kk >= (iw - imgw); kk--) + { + *y = *scl; + y += frame_width; + k--; + if (((j & 1) == 0) && ((k & 1) == 0)) + { + *uv = scl[1]; + *(uv+1) = scl[2]; + uv += frame_width; + } + scl -= 3; + } + } else + { + BYTE *scl = sl + (iw - 1) * 3; + int d = 0; + for (int kk = iw - 1; kk >= (iw - imgw); ) + { + *y = *scl; + y += frame_width; + k--; + if (((j & 1) == 0) && ((k & 1) == 0)) + { + *uv = scl[1]; + *(uv+1) = scl[2]; + uv += frame_width; + } + d += imgw; + while (d >= rescale_width) + { + scl -= 3; + kk--; + d -= rescale_width; + } + } + } + for (; k > 0; ) + { + *y = 0; + y += frame_width; + k--; + if (((j & 1) == 0) && ((k & 1) == 0)) + { + *uv = 128; + *(uv+1) = 128; + uv += frame_width; + } + } + } + oldj = j; + } + + if (!no_rescale) + { + if (imgw == 0) + jstep = 1; + else + { + jstep--; + read_scan = false; + if (jstep <= 0) + { + jstep = 0; + jd += rescale_height; + while (jd >= imgheight) + { + jd -= imgheight; + jstep++; + } + } + if (jstep <= 1) + read_scan = true; + } + } else + { + // if no rescale, then we use hardware scaling + //if (i >= imgheight) + // break; + } + + if (jstep > 0) + { + j += jmul; + } + } + + for (i = 0; i < nb_lines_buffer; i++) + SPfree(scanline[i]); + SPfree(scanline); + + KHWL_YUV_FRAME f; + + if (no_rescale || partial_rescale) + { + int scaleto_width, scaleto_height; + if (partial_rescale) + { + scaleto_width = frame_w; + scaleto_height = frame_h; + } else + { + scaleto_width = MAX((int)cinfo.output_width, frame_width / 5); + scaleto_height = MAX((int)cinfo.output_height, frame_height / 4); +#if 0 + if (scaleto_width > scaleto_height) + scaleto_height = scaleto_width * frame_height / frame_width; + else + scaleto_width = scaleto_height * frame_width / frame_height; +#endif + if (rot == 1 || rot == 3) + Swap(scaleto_width, scaleto_height); + + } + + KHWL_VIDEOMODE vmode = KHWL_VIDEOMODE_NORMAL; + // for external images, use aspect-ratio correction + if (is_external_img) + { + int tvtype = settings_get(SETTING_TVTYPE); + if (tvtype == 2 || tvtype == 3) + vmode = KHWL_VIDEOMODE_WIDE; + } else + vmode = KHWL_VIDEOMODE_NONE; + khwl_setvideomode(vmode, FALSE); + khwl_set_window_zoom(KHWL_ZOOMMODE_YUV); + khwl_set_window(scaleto_width, scaleto_height, + frame_width, frame_height, hscale, vscale, offsx, offsy); + } + + int maxi = frame_width * (frame_bottom - frame_top + 1); + int chunk_n = (frame_right - frame_left + 1); + int chunk_width = frame_width; + if (no_rescale) + { + chunk_n *= 32; + chunk_width *= 32; + } + + int offs = frame_width * frame_top + frame_left; + int offs2 = frame_width / 2 * frame_top + frame_left; + int maxi2 = maxi / 2; + + for (i = 0; i < maxi; i += chunk_width) + { + f.y_buf = Y + i; + f.y_offs = offs + i; + f.y_num = maxi - i < chunk_n ? maxi - i : chunk_n; + f.uv_buf = NULL; + f.uv_offs = 0; + f.uv_num = 0; + khwl_displayYUV(&f); + + if (i < maxi2) + { + f.y_buf = NULL; + f.y_offs = 0; + f.y_num = 0; + f.uv_buf = UV + i; + f.uv_offs = offs2 + i; + f.uv_num = maxi2 - i < chunk_n ? maxi2 - i : chunk_n; + khwl_displayYUV(&f); + } + + } + +#ifdef WIN32 + khwl_osd_update(); +#endif + + jpeg_finish_decompress(&cinfo); + jpeg_destroy_decompress(&cinfo); + + fclose(jpegfile); + + fflush(stdout); + + jpeg_dealloc(); + + if (param_rot != NULL) + *param_rot = rot; + + return TRUE; +} diff --git a/src/gui/jpeg.h b/src/gui/jpeg.h new file mode 100644 index 0000000..d3639d9 --- /dev/null +++ b/src/gui/jpeg.h @@ -0,0 +1,47 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - JPEG file display header file + * \file jpeg.h + * \author bombur + * \version 0.1 + * \date 4.07.2004 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#ifndef SP_JPEG_H +#define SP_JPEG_H + +#ifdef __cplusplus +extern "C" { +#endif + +/// Initialize (allocate buffers) +BOOL jpeg_init(); + +/// Deinitialize (release buffers) +BOOL jpeg_deinit(); + +/// Display JPEG file +/// rotation is auto-detected and returned, if Exif data is present. +BOOL jpeg_show (char *filename, int hscale = 0, int vscale = 0, int offsx = 0, int offsy = 0, int *rot = NULL); + + +#ifdef __cplusplus +} +#endif + +#endif // of SP_JPEG_H diff --git a/src/gui/rect.cpp b/src/gui/rect.cpp new file mode 100644 index 0000000..0ba69a5 --- /dev/null +++ b/src/gui/rect.cpp @@ -0,0 +1,417 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - rectangle (incl. rounded) class impl. + * \file rect.cpp + * \author bombur + * \version 0.1 + * \date 4.10.2006 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#include +#include +#include + +#include + + +//////////////////////////////////////////////////////// + +// some inline helper functions for painting the 'context': + +static inline void Scanline(Context *context, int x1, int x2, int y, int bkcolor) +{ + if (x2 >= x1) + memset(context->data + y * context->pitch + x1, bkcolor, x2 - x1 + 1); +} + +static inline void SmallPoint1(Context *context, int x, int y, int color, int) +{ + *(context->data + y * context->pitch + x) = (char)color; +} + +// right-bottom +static inline void BigPoint1(Context *context, int x, int y, int color, int width) +{ + for (int i = 0; i <= width; i++) + Scanline(context, x, x + width, y++, color); +} + +// right-top +static inline void BigPoint2(Context *context, int x, int y, int color, int width) +{ + for (int i = 0; i <= width; i++) + Scanline(context, x, x + width, y--, color); +} + +// left-bottom +static inline void BigPoint3(Context *context, int x, int y, int color, int width) +{ + for (int i = 0; i <= width; i++) + Scanline(context, x - width, x, y++, color); +} + +// left-top +static inline void BigPoint4(Context *context, int x, int y, int color, int width) +{ + for (int i = 0; i <= width; i++) + Scanline(context, x - width, x, y--, color); +} + +static inline void Rect(Context *context, int x1, int y1, int x2, int y2, int bkcolor) +{ + if (x2 >= x1) + { + for (int i = y1; i <= y2; i++) + Scanline(context, x1, x2, i, bkcolor); + } +} + +///////////////////////////////////////////////////////////////////////// + +// modified painting function for clipped case: + +static inline void ClippedScanline(Context *context, int x1, int x2, int y, int bkcolor, + int cx1, int cy1, int cx2, int cy2) +{ + if (y >= cy1 && y <= cy2) + { + x1 = MAX(x1, cx1); + x2 = MIN(x2, cx2); + if (x2 >= x1) + memset(context->data + y * context->pitch + x1, bkcolor, x2 - x1 + 1); + } +} + +static inline void ClippedSmallPoint1(Context *context, int x, int y, int color, int, + int cx1, int cy1, int cx2, int cy2) +{ + if (x >= cx1 && y >= cy1 && x <= cx2 && y <= cy2) + *(context->data + y * context->pitch + x) = (char)color; +} + +// right-bottom +static inline void ClippedBigPoint1(Context *context, int x, int y, int color, int width, + int cx1, int cy1, int cx2, int cy2) +{ + for (int i = 0; i <= width; i++) + ClippedScanline(context, x, x + width, y++, color, cx1, cy1, cx2, cy2); +} + +// right-top +static inline void ClippedBigPoint2(Context *context, int x, int y, int color, int width, + int cx1, int cy1, int cx2, int cy2) +{ + for (int i = 0; i <= width; i++) + ClippedScanline(context, x, x + width, y--, color, cx1, cy1, cx2, cy2); +} + +// left-bottom +static inline void ClippedBigPoint3(Context *context, int x, int y, int color, int width, + int cx1, int cy1, int cx2, int cy2) +{ + for (int i = 0; i <= width; i++) + ClippedScanline(context, x - width, x, y++, color, cx1, cy1, cx2, cy2); +} + +// left-top +static inline void ClippedBigPoint4(Context *context, int x, int y, int color, int width, + int cx1, int cy1, int cx2, int cy2) +{ + for (int i = 0; i <= width; i++) + ClippedScanline(context, x - width, x, y--, color, cx1, cy1, cx2, cy2); +} + +////////////////////////////////////////////////////////////// + +/// Paints filled circle slices for rounded rect. +static inline void DrawRounds(Context *context, int center_x1, int center_y1, int center_x2, int center_y2, + int radius, int linewidth, int color, int bkcolor) +{ + int x; + int y = radius; + // circle decision variable + int d = 3 - (radius << 1); + // treat the line width as the additive to the scan coordinates + linewidth--; + + // draw filled part (generate scan sections of the circle) + if (bkcolor >= 0) + { + for (x = 0; x <= y; x++) + { + // draw the y-dependent scans + if (bkcolor >= 0) + { + Scanline(context, center_x1 - y, center_x1, center_y2 + x, bkcolor); + Scanline(context, center_x2, center_x2 + y, center_y2 + x, bkcolor); + if (x >= 0) + { + Scanline(context, center_x1 - y, center_x1, center_y1 - x, bkcolor); + Scanline(context, center_x2, center_x2 + y, center_y1 - x, bkcolor); + } + } + + // iterate the differential circle drawing code + if (d < 0) + { + d += (x << 2) + 6; + } + else + { + d += ((x - y) << 2) + 10; + + // draw the x-dependent scans - done not so frequently + if (x != y && bkcolor >= 0) + { + Scanline(context, center_x1 - x, center_x1, center_y1 - y, bkcolor); + Scanline(context, center_x2, center_x2 + x, center_y1 - y, bkcolor); + + Scanline(context, center_x1 - x, center_x1, center_y2 + y, bkcolor); + Scanline(context, center_x2, center_x2 + x, center_y2 + y, bkcolor); + } + + y--; + } + } + // restore values + y = radius; + d = 3 - (radius << 1); + } + + // draw border frame + if (color >= 0) + { + for (x = 0; x <= y; x++) // Generate scan sections of the circle + { + +#define CIRCLE_POINTS(size, dir1, dir2, dir3, dir4) \ + size##Point##dir1(context, center_x1 - x, center_y1 - y, color, linewidth); \ + size##Point##dir1(context, center_x1 - y, center_y1 - x, color, linewidth); \ + size##Point##dir2(context, center_x1 - x, center_y2 + y, color, linewidth); \ + size##Point##dir2(context, center_x1 - y, center_y2 + x, color, linewidth); \ + size##Point##dir3(context, center_x2 + x, center_y1 - y, color, linewidth); \ + size##Point##dir3(context, center_x2 + y, center_y1 - x, color, linewidth); \ + size##Point##dir4(context, center_x2 + x, center_y2 + y, color, linewidth); \ + size##Point##dir4(context, center_x2 + y, center_y2 + x, color, linewidth); + + if (linewidth < 1) + { + CIRCLE_POINTS(Small,1,1,1,1); + } else + { + CIRCLE_POINTS(Big,1,2,3,4); + } + + // Iterate the differential circle drawing code + if (d < 0) + d += (x << 2) + 6; + else + { + d += ((x - y) << 2) + 10; + y--; + } + } + } +} + +/// Paints clipped filled circle slices for rounded rect. +static inline void DrawClippedRounds(Context *context, int center_x1, int center_y1, int center_x2, int center_y2, + int radius, int linewidth, int color, int bkcolor, + int x1, int y1, int x2, int y2) +{ + int x; + int y = radius; + + // circle decision variable + int d = 3 - (radius << 1); + // treat the line width as the additive to the scan coordinates + linewidth--; + + // draw filled part (generate scan sections of the circle) + if (bkcolor >= 0) + { + for (x = 0; x <= y; x++) + { + // draw the y-dependent scans + if (bkcolor >= 0) + { + ClippedScanline(context, center_x1 - y, center_x1, center_y2 + x, bkcolor, x1, y1, x2, y2); + ClippedScanline(context, center_x2, center_x2 + y, center_y2 + x, bkcolor, x1, y1, x2, y2); + if (x >= 0) + { + ClippedScanline(context, center_x1 - y, center_x1, center_y1 - x, bkcolor, x1, y1, x2, y2); + ClippedScanline(context, center_x2, center_x2 + y, center_y1 - x, bkcolor, x1, y1, x2, y2); + } + } + + // iterate the differential circle drawing code + if (d < 0) + { + d += (x << 2) + 6; + } + else + { + d += ((x - y) << 2) + 10; + + // draw the x-dependent scans - done not so frequently + if (x != y && bkcolor >= 0) + { + ClippedScanline(context, center_x1 - x, center_x1, center_y1 - y, bkcolor, x1, y1, x2, y2); + ClippedScanline(context, center_x2, center_x2 + x, center_y1 - y, bkcolor, x1, y1, x2, y2); + + ClippedScanline(context, center_x1 - x, center_x1, center_y2 + y, bkcolor, x1, y1, x2, y2); + ClippedScanline(context, center_x2, center_x2 + x, center_y2 + y, bkcolor, x1, y1, x2, y2); + } + + y--; + } + } + // restore values + y = radius; + d = 3 - (radius << 1); + } + + // draw border frame + if (color >= 0) + { + for (x = 0; x <= y; x++) // Generate scan sections of the circle + { + +#define CLIPPED_CIRCLE_POINTS(size, dir1, dir2, dir3, dir4) \ + Clipped##size##Point##dir1(context, center_x1 - x, center_y1 - y, color, linewidth, x1, y1, x2, y2); \ + Clipped##size##Point##dir1(context, center_x1 - y, center_y1 - x, color, linewidth, x1, y1, x2, y2); \ + Clipped##size##Point##dir2(context, center_x1 - x, center_y2 + y, color, linewidth, x1, y1, x2, y2); \ + Clipped##size##Point##dir2(context, center_x1 - y, center_y2 + x, color, linewidth, x1, y1, x2, y2); \ + Clipped##size##Point##dir3(context, center_x2 + x, center_y1 - y, color, linewidth, x1, y1, x2, y2); \ + Clipped##size##Point##dir3(context, center_x2 + y, center_y1 - x, color, linewidth, x1, y1, x2, y2); \ + Clipped##size##Point##dir4(context, center_x2 + x, center_y2 + y, color, linewidth, x1, y1, x2, y2); \ + Clipped##size##Point##dir4(context, center_x2 + y, center_y2 + x, color, linewidth, x1, y1, x2, y2); + + if (linewidth < 1) + { + CLIPPED_CIRCLE_POINTS(Small,1,1,1,1); + } else + { + CLIPPED_CIRCLE_POINTS(Big,1,2,3,4); + } + + // Iterate the differential circle drawing code + if (d < 0) + d += (x << 2) + 6; + else + { + d += ((x - y) << 2) + 10; + y--; + } + } + } +} + +///////////////////////////////////////////////////////////////////////// + +Rectangle::Rectangle() +{ + round = 0; + linewidth = 1; + + color = gui.GetColor(); + bkcolor = gui.GetBkColor(); +} + +void Rectangle::SetLineWidth(int lw) +{ + linewidth = lw; + dirty = true; +} + +void Rectangle::SetRound(int r) +{ + round = r; + dirty = true; +} + +void Rectangle::SetColor(int c) +{ + if (c > 255) + c = -1; + color = c; + dirty = true; +} +void Rectangle::SetBkColor(int bc) +{ + if (bc > 255) + bc = -1; + bkcolor = bc; + dirty = true; +} + +bool Rectangle::Update(Context *context, int x1, int y1, int x2, int y2) +{ + if (context == NULL) + return false; + // fix some values for invalid rects + int lw = color >= 0 ? MIN(linewidth, MIN(width, height) / 2) : 0; + int clr = lw > 0 ? color : -1; + int rnd = MIN(round, lw <= 4 ? MIN(width, height) / 2 : MIN(width, height) / 2 - lw); + + // draw rounded parts + if (rnd > 1) + { + if (x1 == 0 && y1 == 0 && x2 == width - 1 && y2 == height - 1) + DrawRounds(context, rnd, rnd, width - 1 - rnd, + height - 1 - rnd, rnd, lw, clr, bkcolor); + else + DrawClippedRounds(context, rnd, rnd, width - 1 - rnd, + height - 1 - rnd, rnd, lw, clr, bkcolor, + x1, y1, x2, y2); + + // draw upper and lower filled rect. parts + if (bkcolor >= 0) + { + Rect(context, MAX(rnd, x1), MAX(lw, y1), + MIN(width - 1 - rnd, x2), MIN(rnd - 1, y2), bkcolor); + Rect(context, MAX(rnd, x1), MAX(height - rnd, y1), + MIN(width - 1 - rnd, x2), MIN(height - 1 - lw, y2), bkcolor); + } + } + + // draw top-left borders + if (clr >= 0) + { + Rect(context, MAX(rnd, x1), MAX(0, y1), + MIN(width - 1 - rnd, x2), MIN(lw - 1, y2), clr); + Rect(context, MAX(0, x1), MAX(MAX(lw, rnd), y1), + MIN(lw - 1, x2), MIN(height - 1 - MAX(lw, rnd), y2), clr); + } + + // draw main filled rect + if (bkcolor >= 0) + Rect(context, MAX(lw, x1), MAX(MAX(lw, rnd), y1), + MIN(width - 1 - lw, x2), MIN(height - 1 - MAX(lw, rnd), y2), bkcolor); + + // draw bottom-right borders + if (clr >= 0) + { + Rect(context, MAX(width - lw, x1), MAX(MAX(lw, rnd), y1), + MIN(width - 1, x2), MIN(height - 1 - MAX(lw, rnd), y2), clr); + Rect(context, MAX(rnd, x1), MAX(height - lw, y1), + MIN(width - 1 - rnd, x2), MIN(height - 1, y2), clr); + } + + return true; +} diff --git a/src/gui/rect.h b/src/gui/rect.h new file mode 100644 index 0000000..b80752a --- /dev/null +++ b/src/gui/rect.h @@ -0,0 +1,52 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - rectangle (incl. rounded) class header file + * \file rect.h + * \author bombur + * \version 0.1 + * \date 4.10.2006 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#ifndef SP_RECT_H +#define SP_RECT_H + +#include + +/// Rectangle (incl. rounded) window class +class Rectangle : public Window +{ +public: + /// ctor + Rectangle(); + /// dtor + virtual ~Rectangle() {} + + /// Update part of rect in LOCAL coords + virtual bool Update(Context *context, int x1, int y1, int x2, int y2); + + void SetLineWidth(int); + void SetRound(int); + void SetColor(int); + void SetBkColor(int); + +public: + int round, linewidth; + int color, bkcolor; +}; + +#endif // of SP_RECT_H diff --git a/src/gui/res.cpp b/src/gui/res.cpp new file mode 100644 index 0000000..efd1690 --- /dev/null +++ b/src/gui/res.cpp @@ -0,0 +1,58 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - abstract resource class impl. + * \file res.cpp + * \author bombur + * \version 0.1 + * \date 14.10.2005 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#define _BSD_SOURCE // needed for strdup() +#include + +#include +#include + +void Resource::SetName(char *n) +{ + SPSafeFree(name); + if (n == NULL) + { + name = NULL; + namehash = 0; + return; + } + name = SPstrdup(n); + constname = false; + namehash = SPStringHashFunc(name, resource_num_hash); +} + +void Resource::SetConstName(const char *n) +{ + SPSafeFree(name); + constname = true; + if (n == NULL) + { + name = NULL; + namehash = 0; + return; + } + name = (char *)n; + namehash = SPStringHashFunc(name, resource_num_hash); +} + diff --git a/src/gui/res.h b/src/gui/res.h new file mode 100644 index 0000000..eb25b24 --- /dev/null +++ b/src/gui/res.h @@ -0,0 +1,93 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - abstract resource class header file + * \file res.h + * \author bombur + * \version 0.1 + * \date 14.10.2005 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#ifndef SP_RES_H +#define SP_RES_H + + +const int resource_num_hash = 347; + +/// Loadable resource +class Resource +{ +public: + /// ctor + Resource() + { + prev = NULL; + next = NULL; + + name = NULL; + namehash = 0; + + index = -1; + temporary = true; + constname = true; + } + + /// dtor + virtual ~Resource() + { + if (!constname) + SPSafeFree(name); + } + + /// compare + template + bool operator == (const T & r) + { + if (name == NULL || r.name == NULL) + return false; + return strcmp(name, r.name) == 0; + } + + inline operator DWORD () const + { + return namehash; + } + + const Resource * const GetItem() const + { + return this; + } + + /// Set variable name & calc. hash + void SetName(char *n); + /// Set constant name & calc. hash + /// Use this for const.strings to avoid strdup(). + void SetConstName(const char *n); + +public: + bool temporary, constname; + char *name; + DWORD namehash; + + int index; + + // linked list support + Resource *prev, *next; +}; + + +#endif // of SP_RES_H diff --git a/src/gui/text.cpp b/src/gui/text.cpp new file mode 100644 index 0000000..f974637 --- /dev/null +++ b/src/gui/text.cpp @@ -0,0 +1,305 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - text object class impl. + * \file rect.cpp + * \author bombur + * \version 0.1 + * \date 4.10.2006 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#include +#include +#include + +#include +#include + + +static BYTE row_buf[720]; + +///////////////////////////////////////////////////////////////////////// + +#define TEXT_OUTLINE_IF(rdd, c, wdd, bc) { if (*(rdd) != c) *(wdd) = bc; } +#define TEXT_OUTLINE(wdd, bc) { *(wdd) = bc; } + +Text::Text() +{ + font = NULL; + style = TEXT_STYLE_NORMAL; + color = gui.GetColor(); + bkcolor = gui.GetBkColor(); + text_align = TEXT_ALIGN_LEFT; + transparent = true; +} + +bool Text::Update(Context *context, int x1, int y1, int x2, int y2) +{ + if (context == NULL || font == NULL) + return false; + + if (font->unloaded) + { + if (!font->Load()) + return false; + } + + BYTE *d, *pd, *nd, *rd; + int clrs[2]; + BYTE clrs2; + if (style == TEXT_STYLE_OUTLINE) + { + if (x1 > 0) + x1--; + if (y1 > 0) + y1--; + x2--; + y2--; + d = context->data + (y1 + 1) * context->pitch + (x1 + 1); + + pd = d - context->pitch; + nd = d + context->pitch; + rd = row_buf; + + clrs[0] = -1; + clrs2 = (BYTE)bkcolor; + } + else + { + d = context->data + y1 * context->pitch + x1; + + clrs[0] = bkcolor; + clrs2 = '\0'; + } + clrs[1] = color; + + for (int iy = y1; iy <= y2; iy++) + { + int row = iy / font->height; + if (row < 0 || row >= rows.GetN()) + continue; + int offy = iy % font->height; + int ix, w = 0, cw = 0, ich = -1; + BYTE ch = 0; + ULONGLONG *s = NULL; + int row_offs, xx1 = x1; + if (text_align == TEXT_ALIGN_LEFT) + row_offs = 0; + else if (text_align == TEXT_ALIGN_CENTER) + { + row_offs = MAX(((x2 - x1 + 1) - rows[row].width) / 2, 0); + if (clrs[0] >= 0) + memset(d, clrs[0], row_offs); + xx1 = MAX(x1 - row_offs, 0); + } + else + { + row_offs = MAX(((x2 - x1 + 1) - rows[row].width), 0); + if (clrs[0] >= 0) + memset(d, clrs[0], row_offs); + xx1 = MAX(x1 - row_offs, 0); + } + int xx2 = x2 - row_offs; + BYTE *dd, *pdd, *ndd, *rdd; + BYTE prev_cc, prev_rdd; + dd = d + row_offs; + if (style == TEXT_STYLE_OUTLINE) + { + pdd = pd + row_offs; + ndd = nd + row_offs; + rdd = rd + row_offs; + prev_cc = 0; + prev_rdd = 0; + } + for (ix = 0; ix <= xx2; ix++) + { + if (ix >= w) + { +onemore: + ch = text[rows[row].off + (++ich)]; + if (ch == '\0' || ch == '\n') + break; + cw = font->widths[ch]; + if (cw < 1) + goto onemore; + w += cw; + s = font->data[ch]; + } + + if (ix >= xx1) + { + int c; + if (style == TEXT_STYLE_UNDERLINE && offy == font->baseline) + c = clrs[1]; + else + c = clrs[((s[offy] >> (cw - w + ix)) & 1)]; + if (c >= 0) + { + BYTE cc = (BYTE)c; + *dd = cc; + if (clrs2 > 0) // outline + { + if (prev_rdd != cc) + TEXT_OUTLINE(pdd-1, clrs2); + TEXT_OUTLINE_IF(rdd, cc, pdd, clrs2); + TEXT_OUTLINE_IF(rdd+1, cc, pdd+1, clrs2); + + TEXT_OUTLINE(ndd-1, clrs2); + TEXT_OUTLINE(ndd, clrs2); + TEXT_OUTLINE(ndd+1, clrs2); + + if (prev_cc != cc) + TEXT_OUTLINE(dd-1, clrs2); + TEXT_OUTLINE(dd+1, clrs2); + prev_cc = cc; + prev_rdd = *rdd; + *rdd = cc; + } + } + dd++; + if (clrs2 > 0) + { + if (c < 0) + { + prev_cc = 0; + prev_rdd = *rdd; + *rdd = 0; + } + rdd++; + pdd++; + ndd++; + } + } + } + if (ix < xx1) + ix = xx1; + if (clrs[0] >= 0 && ix < xx2) + memset(dd, clrs[0], xx2 - ix + 1); + + d += context->pitch; + if (clrs2 > 0) + { + pd += context->pitch; + nd += context->pitch; + } + } + + return true; +} + +bool Text::SetFont(char *fname) +{ + font = guifonts->GetFont(fname); + if (font == NULL) + return false; + return UpdateFormat(); +} + +bool Text::SetText(const SPString & txt) +{ + text = txt; + return UpdateFormat(); +} + +bool Text::SetStyle(TEXT_STYLE st) +{ + style = st; + return UpdateFormat(); +} + +void Text::SetTextAlign(TEXT_ALIGN ta) +{ + text_align = ta; + dirty = true; +} + +void Text::SetColor(int c) +{ + if (c >= 0 && c < 256) + { + color = c; + dirty = true; + } +} + +void Text::SetBkColor(int bc) +{ + if (bc > 255) + bc = -1; + bkcolor = bc; + dirty = true; +} + +bool Text::UpdateFormat() +{ + int new_width = 0; + int new_height = 0; + rows.Clear(); + + if (font != NULL) + { + if (font->unloaded) + { + if (!font->Load()) + return false; + } + + int rowidx = -1; + for (int i = 0; i < text.GetLength(); i++) + { + if (rowidx < 0) + { + TextRow r; + r.off = i; + r.len = 0; + r.width = 0; + rowidx = rows.Add(r); + } + if (text[i] == '\n') + { + if (rows[rowidx].width > new_width) + new_width = rows[rowidx].width; + new_height += font->height; + rowidx = -1; + continue; + } + rows[rowidx].width += font->widths[(BYTE)text[i]]; + rows[rowidx].len++; + } + if (rowidx >= 0) + { + if (rows[rowidx].width > new_width) + new_width = rows[rowidx].width; + new_height += font->height; + } + } + memset(row_buf, 0, MIN(new_width, 720)); + + if (style == TEXT_STYLE_OUTLINE) + { + new_width += 2; + new_height += 2; + } + + SetWidth(new_width); + SetHeight(new_height); + auto_width = new_width; + auto_height = new_height; + + dirty = true; + return true; +} diff --git a/src/gui/text.h b/src/gui/text.h new file mode 100644 index 0000000..65893c1 --- /dev/null +++ b/src/gui/text.h @@ -0,0 +1,92 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - text object class header file + * \file gui/text.h + * \author bombur + * \version 0.1 + * \date 4.10.2006 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#ifndef SP_TEXT_H +#define SP_TEXT_H + +#include +#include + +enum TEXT_STYLE +{ + TEXT_STYLE_NORMAL = 0, + TEXT_STYLE_UNDERLINE, + TEXT_STYLE_OUTLINE, +}; + +enum TEXT_ALIGN +{ + TEXT_ALIGN_LEFT = 0, + TEXT_ALIGN_CENTER, + TEXT_ALIGN_RIGHT +}; + +class TextRow +{ +public: + /// row text offset + int off; + /// row text length + int len; + /// row width, pixels + int width; +}; + +/// Text object window class +class Text : public Window +{ +public: + /// ctor + Text(); + /// dtor + virtual ~Text() {} + + /// Update part of text in LOCAL coords + virtual bool Update(Context *context, int x1, int y1, int x2, int y2); + + bool SetText(const SPString & txt); + bool SetFont(char *fname); + bool SetStyle(TEXT_STYLE); + void SetColor(int); + void SetBkColor(int); + void SetTextAlign(TEXT_ALIGN); + +public: + /// text + SPString text; + /// current font + Font *font; + /// formatted rows data + SPList rows; + + TEXT_STYLE style; + int color, bkcolor; + TEXT_ALIGN text_align; + +private: + // Update text dims for new font/text + bool UpdateFormat(); +}; + +#endif // of SP_TEXT_H diff --git a/src/gui/window.cpp b/src/gui/window.cpp new file mode 100644 index 0000000..cc0ac8c --- /dev/null +++ b/src/gui/window.cpp @@ -0,0 +1,733 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - abstract window class impl. + * \file window.cpp + * \author bombur + * \version 0.1 + * \date 4.07.2004 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include + +WindowManager gui; + + +//////////////////////////////////////////////////////////// + +Window::Window() +{ + x = 0; + y = 0; + width = 0; + height = 0; + auto_width = auto_height = 0; + + old_x = old_y = old_width = old_height = 0; + + dirty = false; + visible = true; + wasvisible = false; + transparent = false; + removing = false; + + group = NULL; + + halign = gui.halign; + valign = gui.valign; + + timer = 0; + timerobj = NULL; + + prev = next = NULL; +} + +bool Window::Update(int) +{ + return false; +} + +void Window::SetX(int newx) +{ + if (halign == WINDOW_ALIGN_RIGHT) + { + x = newx - width; + } + else if (halign == WINDOW_ALIGN_CENTER) + { + x = newx - width/2; + } + else + x = newx; + dirty = true; +} + +int Window::GetX() +{ + if (halign == WINDOW_ALIGN_RIGHT) + { + return x + width; + } + else if (halign == WINDOW_ALIGN_CENTER) + { + return x + width/2; + } + return x; +} + +void Window::SetY(int newy) +{ + if (valign == WINDOW_ALIGN_BOTTOM) + { + y = newy - height; + } + else if (valign == WINDOW_ALIGN_CENTER) + { + y = newy - height/2; + } + else + y = newy; + dirty = true; +} + +int Window::GetY() +{ + if (valign == WINDOW_ALIGN_BOTTOM) + { + return y + height; + } + else if (valign == WINDOW_ALIGN_CENTER) + { + return y + height/2; + } + return y; +} + +void Window::SetWidth(int newwidth) +{ + if (newwidth <= 0) // use auto-width + newwidth = auto_width; + + if (halign == WINDOW_ALIGN_RIGHT) + { + x = x + width - newwidth; + } + else if (halign == WINDOW_ALIGN_CENTER) + { + x = x + width/2 - newwidth/2; + } + + width = newwidth; + dirty = true; +} + +int Window::GetWidth() +{ + return width; +} + +void Window::SetHeight(int newheight) +{ + if (newheight < 0) // use auto-width + newheight = auto_height; + if (valign == WINDOW_ALIGN_BOTTOM) + { + y = y + height - newheight; + } + else if (valign == WINDOW_ALIGN_CENTER) + { + y = y + height/2 - newheight/2; + } + height = newheight; + dirty = true; +} + +int Window::GetHeight() +{ + return height; +} + +void Window::SetVisible(bool newv) +{ + visible = newv; + dirty = true; +} + +void Window::SetHAlign(WINDOW_ALIGN newha) +{ + int user_x = GetX(); + halign = newha; + SetX(user_x); + dirty = true; +} + +void Window::SetVAlign(WINDOW_ALIGN newva) +{ + int user_y = GetY(); + valign = newva; + SetY(user_y); + dirty = true; +} + +//////////////////////////////////////////////////////////// + +WindowManager::WindowManager() +{ + halign = WINDOW_ALIGN_LEFT; + valign = WINDOW_ALIGN_TOP; + + update_enabled = true; + display_switched = true; + + osd_fullscreen = false; +} + +WindowManager::~WindowManager() +{ + DeInitialize(); +} + +bool WindowManager::Initialize() +{ + if (!jpeg_init()) + return false; + + // init OSD + KHWL_OSDSTRUCT osd = { 0 }; + khwl_osd_switch(&osd, FALSE/*TRUE*/); + + // bpp is 8, i guess? + if (osd.bpp != 8) + return false; + + data = (BYTE *)osd.addr + 8 + 1024; + pal = (BYTE *)osd.addr + 8; + width = osd.width; + height = osd.height; + + int offsx = 40, offsy = 30; + left = offsx; + top = offsx; + right = width - 1 - offsy; + bottom = height - 1 - offsy; + + // set default 'b/w' pal + BYTE bw_colors[1024] = { 0, 0, 0, 0, 255, 255, 255, 255, + /////////// ------------ this is for test only! + 255, 55, 5, 255 }; + SetPalette(bw_colors, 0, 256); + def_color = 1; + def_bkcolor = 0; + + tr_color = 0; + w_color = 1; + + tvout = (WINDOW_TVOUT)settings_get(SETTING_TVOUT); + tvstandard = (WINDOW_TVSTANDARD)settings_get(SETTING_TVSTANDARD); + + Clear(); + + guiimg = new ImageManager(); + guifonts = new FontManager(); + + return true; +} + +bool WindowManager::DeInitialize() +{ + SPSafeDelete(guiimg); + SPSafeDelete(guifonts); + removed_windows.Delete(); + windows.Delete(); + jpeg_deinit(); + return true; +} + +//////////////////////////////////////////////////// + +bool WindowManager::IsOsdFullscreen() +{ + return osd_fullscreen; +} + +void WindowManager::SetOsdFullscreen(bool is) +{ + osd_fullscreen = is; + khwl_osd_setfullscreen(osd_fullscreen); +} + +int WindowManager::GetColor() +{ + return def_color; +} + +int WindowManager::GetBkColor() +{ + return def_bkcolor; +} + +int WindowManager::GetTransparentColor() +{ + return tr_color; +} + +int WindowManager::GetWhiteColor() +{ + return w_color; +} + +WINDOW_ALIGN WindowManager::GetHAlign() +{ + return halign; +} + +WINDOW_ALIGN WindowManager::GetVAlign() +{ + return valign; +} + +//////////////////////////////////////////////////// + +bool WindowManager::SetColor(int c) +{ + if (c >= 0 && c < 256) + { + def_color = c; + return true; + } + return false; +} + +bool WindowManager::SetBkColor(int c) +{ + if (c < 256) + { + def_bkcolor = c; + return true; + } + return false; +} + +bool WindowManager::SetTransparentColor(int c) +{ + if (c >= 0 && c < 256) + { + tr_color = c; + return true; + } + return false; +} + +void WindowManager::SetHAlign(WINDOW_ALIGN a) +{ + halign = a; +} + +void WindowManager::SetVAlign(WINDOW_ALIGN a) +{ + valign = a; +} + +//////////////////////////////////////////////////// + +bool WindowManager::SetPalette(BYTE *argb, int offset, int num_colors) +{ + // check if current tr_color is valid + if (tr_color >= offset && tr_color < offset + num_colors) + { + if (*(pal + (tr_color - offset) * 4) != 0) + tr_color = -1; + } + BYTE *p = pal + offset * 4; + int min_a = 255, max_a = 128, max_sum = 0; + for (int i = 0; i < num_colors; i++) + { + BYTE Y, U, V; + khwl_vgargbtotvyuv(argb[1], argb[2], argb[3], &Y, &U, &V); + *p++ = argb[0]; + *p++ = Y; + *p++ = U; + *p++ = V; + if (tr_color < 0 && argb[0] < min_a) + { + tr_color = i; + min_a = argb[0]; + } + int s = argb[1] + argb[2] + argb[3]; + if (argb[0] >= max_a && s > max_sum) + { + max_a = argb[0]; + max_sum = s; + w_color = i; + } + argb += 4; + } + + return true; +} + +bool WindowManager::LoadPalette(const SPString & fname) +{ + FILE *fp = fopen(fname, "rb"); + if (fp == NULL) + { + msg_error("Cannot load palette from %s.\n", *fname); + return false; + } + fseek(fp, 0, SEEK_END); + int siz = ftell(fp); + rewind(fp); + + if (siz < 256 * 3) + { + msg_error("Palette error: file %s is truncated.\n", *fname); + fclose(fp); + return false; + } + + int maxsiz = 256 * 4 + 24; + BYTE *pal = new BYTE [maxsiz]; + fread(pal, Min(siz, maxsiz), 1, fp); + fclose(fp); + + if (siz == 256*3 || siz == 256*3+4) + { + BYTE *p = pal + 256*4; + for (int i = 255; i >= 0; i--) + { + *--p = pal[i * 3 + 2]; + *--p = pal[i * 3 + 1]; + *--p = pal[i * 3]; + *--p = 0xff; + } + } + else if (pal[0] == 'R' && pal[1] == 'I' && pal[2] == 'F' && pal[3] == 'F') + { + BYTE *p = pal; + int i, numc = (pal[17] << 8) | pal[16]; + for (i = 6; i < numc+6; i++) + { + *p++ = 0xff;// pal[i * 4 + 3]; + *p++ = pal[i * 4 + 0]; + *p++ = pal[i * 4 + 1]; + *p++ = pal[i * 4 + 2]; + } + for (i = numc; i < 256; i++) + { + *p++ = 0; + *p++ = 0; + *p++ = 0; + *p++ = 0; + } + } + + SetPalette(pal, 0, 256); + + delete [] pal; + + return true; +} + +BYTE *WindowManager::GetPaletteEntry(int index) +{ + if (pal == NULL || index < 0 || index > 255) + return NULL; + return pal + index * 4; +} + +bool WindowManager::SetBackground(char *fname, int hscale, int vscale, int offsx, int offsy, int *rotate) +{ + if (fname == NULL || fname[0] == '\0') + { + msg("Clearing background.\n"); + khwl_display_clear(); + khwl_set_window_source(-1, -1); + return true; + } + msg("Setting background to %s\n", fname); + //khwl_setvideomode(KHWL_VIDEOMODE_NORMAL, TRUE); + if (!jpeg_show(fname, hscale, vscale, offsx, offsy, rotate)) + { + msg_error("Cannot show JPEG '%s'.\n", fname); + return false; + } + return true; +} + +//////////////////////////////////////////////////////////// + +bool WindowManager::AddWindow(Window *w) +{ + if (w == NULL) + return false; + if (w == console) + windows.Add(w); + else + windows.InsertBefore(console, w); +#if 0 +printf("Windows = %d\n", windows.GetNum()); +#endif + return true; +} + +bool WindowManager::RemoveWindow(Window *w) +{ + if (w == NULL) + return false; + if (w->removing) + return true; + w->removing = true; + windows.Remove(w); + removed_windows.Add(w); + return true; +} + +bool WindowManager::ShowWindow(Window *w, bool ison) +{ + if (w == NULL) + return false; + if (ison != w->visible) + { + w->SetVisible(ison); + } + return true; +} + +void WindowManager::UpdateEnable(bool ison) +{ + update_enabled = ison; +} + +bool WindowManager::IsUpdateEnabled() +{ + return update_enabled; +} + +bool WindowManager::SetTv(WINDOW_TVOUT tvo, WINDOW_TVSTANDARD tvs) +{ + if (tvo != WINDOW_TVOUT_UNKNOWN) + tvout = tvo; + if (tvs != WINDOW_TVSTANDARD_UNKNOWN) + tvstandard = tvs; + return SwitchDisplay(true); +} + +WINDOW_TVOUT WindowManager::GetTvOut() +{ + return tvout; +} + +WINDOW_TVSTANDARD WindowManager::GetTvStandard() +{ + return tvstandard; +} + +bool WindowManager::SwitchDisplay(bool ison) +{ + static const KHWL_TV_OUTPUT_FORMAT_TYPE khwl_tvout[] = { evTvOutputFormat_COMPOSITE, + evTvOutputFormat_COMPONENT_YUV, + evTvOutputFormat_COMPONENT_RGB_SCART }; + static const KHWL_TV_STANDARD_TYPE khwl_standard[] = + { + evTvStandard_NTSC, evTvStandard_PAL, + evTvStandard_480P, evTvStandard_576P, evTvStandard_720P, evTvStandard_1080I, + }; + + static int old_out = -1, old_standard = -1, old_tvtype = -1; + + display_switched = ison; + + if (display_switched) + { + int tvtype = settings_get(SETTING_TVTYPE); + if (tvout != old_out || tvstandard != old_standard || tvtype != old_tvtype) + { + khwl_setwide((tvtype == 2 || tvtype == 3) ? TRUE : FALSE); + khwl_setdisplay(khwl_tvout[tvout], khwl_standard[tvstandard]); + old_out = tvout; + old_standard = tvstandard; + old_tvtype = tvtype; + } + } + else + { + khwl_setdisplay(evTvOutputFormat_OUTPUT_OFF, khwl_standard[tvstandard]); + old_out = -1; old_standard = -1; + } + + return true; +} + +bool WindowManager::IsDisplaySwitchedOn() +{ + return display_switched; +} + +bool WindowManager::Update() +{ + if (!update_enabled) + return true; + + bool need_update = false; + // clean-up after deleted windows too + Window *win = removed_windows.GetFirst(); + while (win != NULL) + { + win->visible = false; + if (UpdateForWindow(win)) + need_update = true; + + Window *next = win->next; + removed_windows.Delete(win); + win = next; + } + + win = windows.GetFirst(); + while (win != NULL) + { + if (win->dirty && (win->visible || win->wasvisible)) + { + if (UpdateForWindow(win)) + need_update = true; + + win->old_x = win->x; + win->old_y = win->y; + win->old_width = win->width; + win->old_height = win->height; + win->dirty = false; + win->wasvisible = win->visible; + } + win = win->next; + } + if (need_update) + return khwl_osd_update() == TRUE; + return true; +} + +bool WindowManager::UpdateForWindow(Window *win) +{ + static Context c; + bool ret = false; + int upd_x1, upd_y1, upd_x2, upd_y2; + if (win != NULL) + { + upd_x1 = Min(win->old_x, win->x); + if (upd_x1 < 0) upd_x1 = 0; + + upd_y1 = Min(win->old_y, win->y); + if (upd_y1 < 0) upd_y1 = 0; + + upd_x2 = Max(win->old_x + win->old_width, win->x + win->width); + if (upd_x2 >= width) upd_x2 = width - 1; + + upd_y2 = Max(win->old_y + win->old_height, win->y + win->height); + if (upd_y2 >= height) upd_y2 = height - 1; + } else + { + upd_x1 = 0; + upd_y1 = 0; + upd_x2 = width - 1; + upd_y2 = height - 1; + } + + // clear background if window was moved/sized or transparent/hidden + if (win == NULL || win->x != win->old_x || win->y != win->old_y + || win->width != win->old_width || win->height != win->old_height + || win->transparent || !win->visible) + { + if (Clear(false, upd_x1, upd_y1, upd_x2, upd_y2)) + ret = true; + } + + Window *winj = windows.GetFirst(); + while (winj != NULL) + { + if (winj->visible) + { + c.data = data + winj->y * width + winj->x; + c.pitch = width; + c.width = winj->width; + c.height = winj->height; + // apply update region + int lupd_x1 = Max(upd_x1, winj->x); + int lupd_y1 = Max(upd_y1, winj->y); + int lupd_x2 = Min(upd_x2, winj->x + winj->width - 1); + int lupd_y2 = Min(upd_y2, winj->y + winj->height - 1); + if (lupd_x1 <= lupd_x2 && lupd_y1 <= lupd_y2) + { + lupd_x1 -= winj->x; + lupd_y1 -= winj->y; + lupd_x2 -= winj->x; + lupd_y2 -= winj->y; + if (winj->Update(&c, lupd_x1, lupd_y1, lupd_x2, lupd_y2)) + ret = true; + } + } + winj = winj->next; + } + return ret; +} + +bool WindowManager::TextOut(int x, int y, char *str, int nx) +{ + Context c; + c.data = data + y * width + x; + c.pitch = width; + c.width = width; + c.height = height; + return console->TextOut(&c, str, nx); +} + +bool WindowManager::Clear(bool updatenow, int x1, int y1, int x2, int y2) +{ + Context c; + c.data = data; + c.pitch = width; + c.width = width; + c.height = height; + if (x1 < 0) x1 = 0; + if (y1 < 0) y1 = 0; + if (x2 < 0 || x2 >= width) x2 = width - 1; + if (y2 < 0 || y2 >= height) y2 = height - 1; + + if (x2 < x1 || y2 < y1) + return false; + + BYTE *d = c.data + y1 * c.pitch + x1; + int len = (x2 - x1 + 1) * sizeof(BYTE); + for (int iy = y1; iy <= y2; iy++) + { + memset(d, tr_color, len); + d += c.pitch; + } + return updatenow ? (khwl_osd_update() == TRUE) : true; +} diff --git a/src/gui/window.h b/src/gui/window.h new file mode 100644 index 0000000..98d0b14 --- /dev/null +++ b/src/gui/window.h @@ -0,0 +1,248 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - abstract window class header file + * \file window.h + * \author bombur + * \version 0.1 + * \date 4.07.2004 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#ifndef SP_WINDOW_H +#define SP_WINDOW_H + +/// Drawing context +class Context +{ +public: + /// 8-bit color data + BYTE *data; + /// data pitch + int pitch; + /// context dims + int width, height; +}; + +////////////////////////////////////////////////////////////////// + +enum WINDOW_ALIGN +{ + WINDOW_ALIGN_TOP = 0, + WINDOW_ALIGN_LEFT = 0, + WINDOW_ALIGN_CENTER = 1, + WINDOW_ALIGN_RIGHT = 2, + WINDOW_ALIGN_BOTTOM = 2 +}; + +enum WINDOW_TVSTANDARD +{ + WINDOW_TVSTANDARD_UNKNOWN = -1, + WINDOW_TVSTANDARD_NTSC = 0, + WINDOW_TVSTANDARD_PAL, + WINDOW_TVSTANDARD_480P, + WINDOW_TVSTANDARD_576P, + WINDOW_TVSTANDARD_720P, + WINDOW_TVSTANDARD_1080I, +}; + +enum WINDOW_TVOUT +{ + WINDOW_TVOUT_UNKNOWN = -1, + WINDOW_TVOUT_COMPOSITE = 0, + WINDOW_TVOUT_YPBPR, + WINDOW_TVOUT_RGB, +}; + +class ScriptTimerObject; + +/// Abstract window class +class Window +{ +public: + /// ctor + Window(); + /// dtor + virtual ~Window() + { + SPSafeDelete(group); + } + + /// Update object's state for current time (in milliseconds) + virtual bool Update(int curtime); + + /// Update part of window in LOCAL coords + virtual bool Update(Context *context, int x1, int y1, int x2, int y2) = 0; + + /// Set window center using halign + void SetX(int); + /// Set window center using valign + void SetY(int); + /// Set window width using halign + void SetWidth(int); + /// Set window height using valign + void SetHeight(int); + + void SetHAlign(WINDOW_ALIGN); + void SetVAlign(WINDOW_ALIGN); + + /// Get window center using halign + int GetX(); + /// Get window center using valign + int GetY(); + int GetWidth(); + int GetHeight(); + + void SetVisible(bool); + +public: + /// window coords + int x, y; + /// window size + int width, height; + + WINDOW_ALIGN halign, valign; + /// window is visible? + bool visible, wasvisible; + /// set this to update window + bool dirty; + /// set if window is being removed + bool removing; + /// if window is transparent + bool transparent; + + /// saved coords & dims + int old_x, old_y, old_width, old_height, auto_width, auto_height; + + SPString *group; + + int timer; + /// update queue object (used by script) + ScriptTimerObject *timerobj; + + Window *prev, *next; +}; + +/// Window manager +class WindowManager +{ +public: + /// ctor + WindowManager(); + /// dtor + ~WindowManager(); + + /// initialize + bool Initialize(); + + /// deinitialize + bool DeInitialize(); + + /// Turn display on/off + bool SwitchDisplay(bool ison); + bool IsDisplaySwitchedOn(); + bool SetTv(WINDOW_TVOUT = WINDOW_TVOUT_UNKNOWN, WINDOW_TVSTANDARD = WINDOW_TVSTANDARD_UNKNOWN); + WINDOW_TVOUT GetTvOut(); + WINDOW_TVSTANDARD GetTvStandard(); + + /// Add window to the list + bool AddWindow(Window *); + + /// Remove window from the list + bool RemoveWindow(Window *); + + /// Show/Hide window + bool ShowWindow(Window *, bool); + + /// Update windows if needed + bool Update(); + + /// Enable/disable windows update + void UpdateEnable(bool ison); + bool IsUpdateEnabled(); + + /// Clear screen + bool Clear(bool updatenow = true, int x1 = 0, int y1 = 0, int x2 = -1, int y2 = -1); + + //////////////////////////////////////////// + bool TextOut(int x, int y, char *str, int nx = 0); + + //////////////////////////////////////////// + int GetColor(); + int GetBkColor(); + int GetTransparentColor(); + int GetWhiteColor(); + WINDOW_ALIGN GetHAlign(); + WINDOW_ALIGN GetVAlign(); + + bool IsOsdFullscreen(); + void SetOsdFullscreen(bool); + + bool SetColor(int); + bool SetBkColor(int); + bool SetTransparentColor(int); + void SetHAlign(WINDOW_ALIGN); + void SetVAlign(WINDOW_ALIGN); + + bool LoadPalette(const SPString & fname); + bool SetPalette(BYTE *argb, int offset, int num_colors); + + /// in ARGB form + BYTE *GetPaletteEntry(int index); + + bool SetBackground(char *fname, int hscale = 100, int vscale = 100, int offsx = 0, int offsy = 0, int *rotate = NULL); + +public: + /// Use these freely + int left, top, right, bottom; + WINDOW_ALIGN halign, valign; + +protected: + /// windows list + SPDLinkedListAbstract windows; + SPDLinkedListAbstract removed_windows; + +private: + BYTE *data; + BYTE *pal; + /// won't be changed + int width, height; + + // current def.object color & background color + int def_color, def_bkcolor; + // current transparent color + int tr_color; + // current white color + int w_color; + + bool update_enabled; + bool display_switched; + + bool osd_fullscreen; + + /// Update screen area for given window (or entire screen if NULL). + /// (Not thread-safe!) + bool UpdateForWindow(Window *win); + + /// Current values. See SETTING_TVOUT and SETTING_TVSTANDARD. + WINDOW_TVOUT tvout; + WINDOW_TVSTANDARD tvstandard; +}; + +/// User interface manager +extern WindowManager gui; + +#endif // of SP_WINDOW_H diff --git a/src/info/player_info.cpp b/src/info/player_info.cpp new file mode 100644 index 0000000..e1edb2f --- /dev/null +++ b/src/info/player_info.cpp @@ -0,0 +1,290 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - File info source file. + * \file player_info.cpp + * \author bombur + * \version 0.1 + * \date 22.10.2006 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "player_info.h" + +#ifdef PLAYER_INFO_EMBED +#include +#include "script.h" +#endif + +#include + +extern "C" +{ +#include +#include +#include +} + +#if 0 +int strcasecmp (char *dst, char *src) +{ + int f, l; + do + { + f = tolower((unsigned char)(*(dst++))); + l = tolower((unsigned char)(*(src++))); + } while (f != 0 && (f == l)); + return(f - l); +} +#endif + +/////////////////////////////////////////////////////// + +#define NetWordRotate(w) w = (WORD)((((w) & 0x00ffU) << 8) | (((w) & 0xff00U) >> 8)) +#define NetDwordRotate(dw) dw = ((DWORD)( \ + (((DWORD)(dw) & (DWORD)0x000000ffUL) << 24) | \ + (((DWORD)(dw) & (DWORD)0x0000ff00UL) << 8) | \ + (((DWORD)(dw) & (DWORD)0x00ff0000UL) >> 8) | \ + (((DWORD)(dw) & (DWORD)0xff000000UL) >> 24) )); + +#ifdef WIN32 +#pragma pack(1) +#endif + +typedef struct JfifMarkerSize +{ + WORD marker; + WORD size; + +} ATTRIBUTE_PACKED JfifMarkerSize; + +typedef struct JfifFrameMarker +{ + BYTE precision; + WORD height; + WORD width; + BYTE numc; + +} ATTRIBUTE_PACKED JfifFrameSize; + +#ifdef WIN32 +#pragma pack() +#endif + +BOOL player_get_jpginfo(const char *fname) +{ +#ifdef WIN32 + if (fname[0] == '/') fname++; +#endif + + FILE *fp = fopen(fname, "rb"); + if (fp == NULL) + { +#ifndef PLAYER_INFO_EMBED + printf("Errr\n"); +#endif + return FALSE; + } + + WORD soi; + fread(&soi, sizeof(soi), 1, fp); + JfifMarkerSize marker; + + int width = -1, height = -1; + int numc = -1; + + if (soi == (WORD)0xd8ff) + { + for (;;) + { + if (fread(&marker, sizeof(marker), 1, fp) != 1) + break; + // corrupted file... give it a chance... + if ((marker.marker & 0xff) != 0xff) + { + fseek(fp, -1, SEEK_CUR); + continue; + } + // SOF + if ((marker.marker & 0xf0ff) == 0xc0ff + && marker.marker != 0xc4ff && marker.marker != 0xccff) + { + JfifFrameMarker frame; + if (fread(&frame, sizeof(frame), 1, fp) == 1) + { + NetWordRotate(frame.width); + NetWordRotate(frame.height); + width = frame.width; + height = frame.height; + numc = frame.numc; + } + break; + } + NetWordRotate(marker.size); + if (marker.size < 2) + break; + if (marker.size > 2) + fseek(fp, marker.size - 2, SEEK_CUR); + } + } + bool wasany = false; + if (width >= 0 && height >= 0) + { +#ifdef PLAYER_INFO_EMBED + script_framesize_callback(width, height); +#else + printf("Dims\n%d\n%d\n", width, height); +#endif + wasany = true; + } + if (numc == 1 || numc == 3) + { +#ifdef PLAYER_INFO_EMBED + script_colorspace_callback(numc); +#else + printf("Clrs\n%d\n", numc); +#endif + wasany = true; + } + if (!wasany) + { +#ifdef PLAYER_INFO_EMBED + printf("None\n"); +#endif + } + fclose(fp); + + return TRUE; +} + +BOOL player_get_id3(const char *fname, const char *charset) +{ + bool wasany = false; + PLAYER_INFO_CHARSET info_charset = PLAYER_INFO_CHARSET_LATIN1; + if (strcasecmp(charset, "cp1251") == 0) + info_charset = PLAYER_INFO_CHARSET_CP1251; + +#ifdef WIN32 + if (fname[0] == '/') fname++; +#endif + struct id3_file *id3 = id3_file_open(fname, ID3_FILE_MODE_READONLY); + if (id3 == NULL) + { +#ifndef PLAYER_INFO_EMBED + printf("Errr\n"); +#endif + return FALSE; + } + struct id3_tag *id3tag = id3_file_tag(id3); + if (id3tag != NULL) + { + static char const *frms[] = { ID3_FRAME_TITLE, ID3_FRAME_ARTIST }; + for (int i = 0; i < 2; i++) + { + struct id3_frame *id3frame = id3_tag_findframe(id3tag, frms[i], 0); + if (id3frame != NULL) + { + union id3_field field = id3frame->fields[1]; + id3_ucs4_t const *tmpstr = id3_field_getstrings(&field, 0); + if (tmpstr != NULL) + { + int slen = id3_ucs4_latin1size(tmpstr); + id3_latin1_t *str = new id3_latin1_t[slen + 1]; + if (str != NULL) + { + if (info_charset == PLAYER_INFO_CHARSET_CP1251) + id3_cp1251_encode(str, tmpstr); + else + id3_latin1_encode(str, tmpstr); + + if (slen > 250) + str[250] = '\0'; +#ifdef PLAYER_INFO_EMBED + if (i == 0) + script_name_callback((char *)str); + else if (i == 1) + script_artist_callback((char *)str); +#else + static char const *hdrs[] = { "Titl", "Atst" }; + printf("%s\n%s\n", hdrs[i], str); +#endif + SPSafeDeleteArray(str); + wasany = true; + } + } + } + } + } +#ifndef PLAYER_INFO_EMBED + if (!wasany) + printf("None\n"); +#endif + if (id3 != NULL) + { + id3_file_close(id3); + id3 = NULL; + } + + return FALSE; +} + +BOOL player_getinfo(const char *fname, const char *charset) +{ + const char *ext = strrchr(fname, '.'); + if (ext != NULL) + { + if (strcasecmp(ext, ".jpg") == 0 || strcasecmp(ext, ".jpeg") == 0) + { + return player_get_jpginfo(fname); + } + else if (strcasecmp(ext, ".mp3") == 0 || strcasecmp(ext, ".wav") == 0 || strcasecmp(ext, ".mp2") == 0 || strcasecmp(ext, ".mp1") == 0 || strcasecmp(ext, ".mpa") == 0 || strcasecmp(ext, ".mus") == 0) + { + return player_get_id3(fname, charset); + } + } + return FALSE; +} + + +#ifndef PLAYER_INFO_EMBED + +int main(int argc, char *argv[]) +{ + if (argc < 3) + { + printf("Use: fileinfo.bin fname charset.\n"); + return 1; + } + + player_getinfo(argv[1], argv[2]); + + fflush(stdout); + return 0; +} + +#endif diff --git a/src/info/player_info.h b/src/info/player_info.h new file mode 100644 index 0000000..a49fbd6 --- /dev/null +++ b/src/info/player_info.h @@ -0,0 +1,39 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - file info header file + * \file player_info.h + * \author bombur + * \version 0.1 + * \date 2.08.2004 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#ifndef SP_PLAYER_INFO_H +#define SP_PLAYER_INFO_H + +enum PLAYER_INFO_CHARSET +{ + PLAYER_INFO_CHARSET_LATIN1 = 0, + PLAYER_INFO_CHARSET_CP1251 = 1, +}; + +BOOL player_getinfo(const char *fname, const char *charset); + +BOOL player_get_id3(const char *fname, const char *charset); +BOOL player_get_jpginfo(const char *fname); + +#endif // of SP_PLAYER_INFO_H diff --git a/src/info/player_info.make b/src/info/player_info.make new file mode 100644 index 0000000..9fb8fd4 --- /dev/null +++ b/src/info/player_info.make @@ -0,0 +1,52 @@ +######################################################################### +# +# SigmaPlayer source project - player_info makefile +# \file player_info.make +# \author bombur +# \version 0.1 +# \date 15.2.2007 +# +########################################################################## + + +MAIN_SRC := info/player_info.cpp + +PREBUILD := cd contrib/libid3tag && make && cd ../.. + +EXTERNAL_STATIC_LINKS_WITH := \ + contrib/libid3tag/.libs/libid3tag.a \ + contrib/libid3tag/fakezlib/libz.a + +# contrib/memcpy.o \ +# contrib/memset.o \ + + +SPINCLUDE = libsp/ +JPEGINCLUDE = contrib/libjpeg/ +DVDINCLUDE = contrib/libdvdnav/src/ +DVDINCLUDE1 = contrib/libdvdnav/src/dvdread/ +DVDINCLUDE2 = contrib/libdvdnav/src/vm/ +DVDINCLUDE3 = contrib/libdvdcss/ +DVDINCLUDE4 = contrib/libdvdcss/src/ +ID3V2INCLUDE = contrib/libid3tag/ + +SPCFLAGS += -msoft-float -Iinfo -I$(SPINCLUDE) -I$(JPEGINCLUDE) -I$(DVDINCLUDE) -I$(DVDINCLUDE1) -I$(DVDINCLUDE2) -I$(DVDINCLUDE3) -I$(DVDINCLUDE4) -I$(ID3V2INCLUDE) -DDVDNAV_COMPILE=1 +#SPCFLAGS += -I$(SPINCLUDE) -I$(ID3V2INCLUDE) + +LOCAL_MAKEFILE := Makefile + +TARGET_TYPE := EXECUTABLE + +USE_STD_LIB := 1 + +COMPILKIND = release + +PREPROCESSORFLAGS += -D__STDC_LIMIT_MACROS + +MAKE_CLEAN = -rm -fr info/player_info.gdb && cd contrib/libid3tag && make clean && cd ../.. + +CROSS = arm-elf- +LDFLAGS = -elf2flt="-s262144" -s --static +EXEFLAGS = -msoft-float -lutil -lstdc++ + +include Makefile.inc diff --git a/src/init b/src/init new file mode 100644 index 0000000000000000000000000000000000000000..05ab4432694c3171e8e16bcbda07a4f758dd751e GIT binary patch literal 637364 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 Qp!LJnxmVm`>&U~H>z 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 +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +//#define SHOW_CONSOLE_AT_STARTUP + + +#ifdef WIN32 +//#define SHOW_CONSOLE_AT_STARTUP +#endif + +bool is_sleeping = false, do_sleep = false, need_to_stop = false; +int steps = 0; + +void gui_update() +{ + script_update(true); + gui.Update(); +} + +int cycle(bool gfx_only) +{ + int but = fip_read_button(FALSE); + if (but != 0) + { + if (but == -1) // for win32 exit + return -1; + + if (but == FIP_KEY_POWER) + { + is_sleeping = !is_sleeping; + do_sleep = true; + if (is_sleeping) + { + if (!stop_all()) + need_to_stop = true; + } + } + + else if (!is_sleeping) + { + if (but == FIP_KEY_PN) + { + if (msg_get_output() == MSG_OUTPUT_SHOW) + { + msg_set_output(MSG_OUTPUT_FREEZE); + } + else if (msg_get_output() == MSG_OUTPUT_FREEZE) + { + msg_set_output(MSG_OUTPUT_HIDE); + } + else if (msg_get_output() == MSG_OUTPUT_HIDE) + { + msg_set_output(MSG_OUTPUT_SHOW); + } + } + // PBC shows sysinfo in console mode + else if (but == FIP_KEY_PBC) + { + if (msg_get_output() == MSG_OUTPUT_SHOW) + { +#if 0 + SPMemoryManager &memManager = SPMemoryManager::GetHandle(); + memManager.TEST_DUMP(); + + guiimg->DumpImages(); +#endif + msg_sysinfo(); + } + else + msg_shell(); + } + script_key_callback(but); + } + } + + if (do_sleep) + { + if (!need_to_stop || stop_all()) + { + msg("sleeping = %s\n", is_sleeping ? "true" : "false"); + player_turn_onoff(!is_sleeping); + + if (!is_sleeping) + { + disc_changed(CDROM_STATUS_UNKNOWN); + script_skiptime(); + } + need_to_stop = false; + do_sleep = false; + } + } + + if (!is_sleeping) + { + script_update(gfx_only); + + if (mmsl != NULL) + { + if (!mmsl->Run()) + { + if (!is_internal_playing()) + usleep(10000); + } + } + + if (!gui.Update()) + { + // win32 exit + return -1; + } + + if (!is_playing()) + { + if ((steps++) % 15 == 0) // every 750 ms + disc_changed(); + } + } else + usleep(50000); + + return 0; +} + +int main(int /*argc*/, char * /*argv*/[]) +{ + if (!fip_init(TRUE)) + exit(1); + + // needed for some drives which do not eject at startup + if (fip_read_button(FALSE) == FIP_KEY_FRONT_EJECT) + { + if (!cdrom_init()) + exit(4); + cdrom_eject(TRUE); // open tray + usleep(2000000); + } + + fip_write_string("LoAd"); + + if (!khwl_init(TRUE)) + exit(2); + + if (!settings_init()) + exit(3); + + if (!cdrom_init()) + exit(4); + + if (!gui.Initialize()) + exit(5); + + console = new Console(60, 20); + gui.AddWindow(console); + gui.SwitchDisplay(true); + + msg_init(); + +#ifdef SHOW_CONSOLE_AT_STARTUP + msg_set_output(MSG_OUTPUT_SHOW); + msg("Starting debug (see sp_log.txt)...\n"); +#endif + + if (msg_get_output() == MSG_OUTPUT_HIDE) + gui.ShowWindow(console, false); + + script_init(); + + disc_changed(); + +//debug_init(); + + for (;;) + { + if (cycle() < 0) + break; + } + + // probably we won't get here in firmware... + + SPSafeDelete(mmsl); + script_deinit(); + + cdrom_deinit(); + khwl_deinit(); + fip_deinit(); + + return 0; +} diff --git a/src/libsp/KISS/todo b/src/libsp/KISS/todo new file mode 100644 index 0000000..8571307 --- /dev/null +++ b/src/libsp/KISS/todo @@ -0,0 +1 @@ +Not ready yet. \ No newline at end of file diff --git a/src/libsp/MG35/arch-jasper/hardware.h b/src/libsp/MG35/arch-jasper/hardware.h new file mode 100644 index 0000000..c8cdec0 --- /dev/null +++ b/src/libsp/MG35/arch-jasper/hardware.h @@ -0,0 +1,798 @@ +/* + * linux/include/asm-arm/arch-jasper/hardware.h + * [bombur]: this is a part of uClinux (GPL) headers. + * for JASPER + * Created 12/12/2001 Fabrice Gautier + * Copyright 2001, Sigma Desings, Inc + */ + +#ifndef __ASM_ARCH_HARDWARE_H +#define __ASM_ARCH_HARDWARE_H + +#define IO_ADDRESS(x) (x) + +// PLL input clock, typically 27 Mhz +#define JASPER_EXT_CLOCK 27000000 + +/* 0=TC0, 1=TC1, 2=TC2 */ + +//------------------------------------------------------ +// SYSTEM CONTROLLER 0x0050_0000 +//------------------------------------------------------ +#define JASPER_SYSCTRL_BASE 0x00500000 + +#define SYSCTRL_CHIP_ID 0x00000000 +#define SYSCTRL_REVISION_ID 0x00000008 +#define SYSCTRL_CPUCFG 0x0000000C +#define SYSCTRL_TESTSTAT 0x00000010 +#define SYSCTRL_ERRSTAT 0x00000014 +#define SYSCTRL_BADADDR 0x00000018 +#define SYSCTRL_RSTCTL 0x00000020 +#define SYSCTRL_CPUTIMESLOT 0x00000024 + +//------------------------------------------------------ +// TIMER 0 AND 1 0x0050_0100 +//-------------------------TIMER_----------------------- +#define JASPER_TIMER_BASE 0x00500100 + +#define TIMER_TMRSTAT 0x00000000 +#define TIMER_TMR0LOAD 0x00000010 +#define TIMER_TMR0VAL 0x00000014 +#define TIMER_TMR0CTL 0x00000018 +#define TIMER_TMR1LOAD 0x00000020 +#define TIMER_TMR1VAL 0x00000024 +#define TIMER_TMR1CTL 0x00000028 + +//------------------------------------------------------ +// INT CONTROLLER 0x0050_0200 +//------------------------------------------------------ +#define JASPER_INT_CONTROLLER_BASE 0x00500200 + +#define INT_IRQSTAT 0x00000000 +#define INT_FIQSTAT 0x00000004 +#define INT_INTTYPE 0x00000010 +#define INT_INTPOLL 0x00000020 +#define INT_INTEN 0x00000024 + +//------------------------------------------------------ +// MAC Registers 0x00500300 +//------------------------------------------------------ +#define JASPER_MAC_BASE 0x00500300 + +#define MAC_REFTIMER 0x00000000 +#define MAC_FLASH_CFG 0x00000014 +#define MAC_FLASH_ST 0x0000001c +#define MAC_SDRAMCTL 0x00000020 +#define MAC_SDRAMCFG 0x00000024 +#define MAC_SDRAMDATA 0x00000028 +#define MAC_SDRAMST 0x0000002c +#define MAC_ARBITERCTRL 0x00000090 +#define MAC_ARBITERSTATE 0x00000094 +#define MAC_WATCHDOG_CTRL 0x000000A0 +#define MAC_WATCHDOG_TMO0 0x000000A4 +#define MAC_WATCHDOG_TMO1 0x000000A8 +#define MAC_WATCHDOG_INT 0x000000Ac +#define MAC_TIMESLOTCNT 0x000000B0 + + +//------------------------------------------------------ +// UART REGISTERs +// UART0 0050_0500 +// UART1 0050_1300 +//------------------------------------------------------ +#define JASPER_UART0_BASE 0x00500500 +#define JASPER_UART1_BASE 0x00501300 +#define UART_NR 2 + +#define UART_RBR 0x00 +#define UART_TBR 0x04 +#define UART_IER 0x08 +#define UART_IIR 0x0C +#define UART_FCR 0x10 +#define UART_LCR 0x14 +#define UART_MCR 0x18 +#define UART_LSR 0x1C +#define UART_MSR 0x20 +#define UART_SCRATCH 0x24 +#define UART_CLKDIV 0x28 +#define UART_CLKSEL 0x2C + +//------------------------------------------------------ +// PIO0 block 0x0050_0600 +// PIO1 block 0x0050_0A00 +//------------------------------------------------------ +#define JASPER_PIO0_BASE 0x00500600 +#define JASPER_PIO1_BASE 0x00500A00 + +#define PIO_INT_STATUS 0x00000000 +#define PIO_DATA 0x00000004 +#define PIO_DIR 0x00000008 +#define PIO_POL 0x0000000C +#define PIO_INT_ENABLE 0x00000010 + + +//------------------------------------------------------ +// I2C MASTER REGISTERs 0X0050_0800 +//------------------------------------------------------ +#define JASPER_I2C_MASTER_BASE 0x00500800 + +#define I2C_MASTER_CONFIG 0x00 +#define I2C_MASTER_CLK_DIV 0x04 +#define I2C_MASTER_DEV_ADDR 0x08 +#define I2C_MASTER_ADR 0x0C +#define I2C_MASTER_DATAOUT 0x10 +#define I2C_MASTER_DATAIN 0x14 +#define I2C_MASTER_STATUS 0x18 +#define I2C_MASTER_STARTXFER 0x1C +#define I2C_MASTER_BYTE_COUNT 0x20 +#define I2C_MASTER_INTEN 0x24 +#define I2C_MASTER_INT 0x28 + + +//------------------------------------------------------ +// I2C SLAVE REGISTERs 0X0050_0900 +//------------------------------------------------------ +#define JASPER_I2C_SLAVE_BASE 0x00500900 + +#define I2C_SLAVE_ADDR 0x00 +#define I2C_SLAVE_DATAOUT 0x04 +#define I2C_SLAVE_DATAIN 0x08 +#define I2C_SLAVE_STATUS 0x0C +#define I2C_SLAVE_INTEN 0x10 +#define I2C_SLAVE_INT 0x14 +#define I2C_SLAVE_BUS_HOLD 0x18 + +//------------------------------------------------------ +// IDE_REGISTERs 0x0050_0B00 +//------------------------------------------------------ +#define JASPER_IDE_BASE 0x00500B00 + +#define JASPER_IDE_DMA_BASE 0x00500E00 + + // **** DMA CHANNEL REG **** +#define IDE_BMIC 0x00 // ( 8 BIT) BMIC IDE COMMAND REG +#define IDE_BMIS 0x04 // ( 8 BIT) BMIC IDE STATUS REG +#define IDE_BMIDTP 0x08 // (32 BIT) BUSMASTER IDE DESCRIPTOR TABLE POINTER REG +#define IDE_TIM 0x40 // (16 BIT) IDE TIMING Reg +#define IDE_SIDETIM 0x48 // ( 8 BIT) SLAVE IDE TIMING Reg +#define IDE_SRC 0x4C // (16 BIT) SLEW RATE CTRL Reg (45h-46h) +#define IDE_STATUS 0x50 // ( 8 BIT) IDESTATUS +#define IDE_UDMACTL 0x54 // ( 8 BIT) ULTRA DMA CONTORL Reg +#define IDE_UDMATIM 0x58 // (16 BIT) ULTRA DMA TIMING Reg (4A - 4B) +#define IDE_PRI_DEVICE_CONTROL 0xE6 // (16 BIT) Device 0: +#define IDE_PRI_DATA 0xF0 // (16 BIT) Device 0: +#define IDE_PRI_SECTOR_COUNT 0xF2 // (16 BIT) Device 0: +#define IDE_PRI_DEVICE_HEAD 0xF6 // (16 BIT) Device 0: +#define IDE_PRI_CMD 0xF7 // (16 BIT) Device 0: + + +//------------------------------------------------------ +// DVD-LOADER_REGISTERs 0x0050_0C00 +//------------------------------------------------------ +#define JASPER_DVD_BASE 0x00500C00 +#define DVD_AV_CTRL 0x00 // (16 BIT) AUDIO/VIDEO PART FROM HOST +#define DVD_AV_SEC_CNT 0x04 // +#define DVD_AV_BYTE_CNT 0x08 // +#define DVD_AV_INTMSK 0x0C // +#define DVD_AV_INT 0x10 // +#define DVD_FIFO_LIM 0x14 // +#define DVD_TIMOUT_LIM 0x18 // +#define DVD_AV_X2C 0x1C // +#define DVD_HOST_CTRL 0x20 // +#define DVD_HOST_SCLK 0x24 // +#define DVD_HOST_TXREG 0x28 // +#define DVD_HOST_RXREG 0x2C // + +//------------------------------------------------------ +// FIP REGISTERs 0x0050_0D00 +//------------------------------------------------------ + +#define JASPER_FIP_BASE 0x00500D00 + +#define FIP_COMMAND 0x00 +#define FIP_DISPLAY_DATA 0x04 +#define FIP_LED_DATA 0x08 +#define FIP_KEY_DATA1 0x0C +#define FIP_KEY_DATA2 0x10 +#define FIP_SWITCH_DATA 0x14 +#define FIP_CLK_DIV 0x20 +#define FIP_TRISTATE_MODE 0x24 + +//------------------------------------------------------ +// DVD-DMA REGISTERs 0x0050_0F00 +//------------------------------------------------------ +#define JASPER_DVD_DMA_BASE 0x00500F00 +#define DVD_DMACTL 0x00 // +#define DVD_DMAMSK 0x04 // +#define DVD_DMAINT 0x08 // +#define DVD_DMARAW 0x0C // +#define DVD_RXADDR 0x10 // +#define DVD_RXBYTES 0x14 // + +//------------------------------------------------------ +// RTC REGISTER 0x0050_1400 +//------------------------------------------------------ +#define JASPER_RTC_BASE 0x00501400 +#define RTC_CTRL 0x00000000 +#define RTC_LOAD1 0x00000004 +#define RTC_LOAD2 0x00000008 +#define RTC_ALARM 0x0000000C +#define RTC_INTEN 0x00000010 +#define RTC_INT0 0x00000014 +#define RTC_COUNT1 0x00000018 +#define RTC_COUNT2 0x0000001C + +//------------------------------------------------------ +// HOST/QUASAR SLAVE REGISTERS 0x0050_1500 +//------------------------------------------------------ +#define JASPER_HOST_SLAVE_QUASAR_BASE 0x00501500 + +#define HOST_SLAVE_WR_QUASAR_BYTE 0x00000000 +#define HOST_SLAVE_WR_QUASAR_1BYTE 0x00000000 +#define HOST_SLAVE_WR_QUASAR_2BYTE 0x00000004 +#define HOST_SLAVE_WR_QUASAR_3BYTE 0x00000008 +#define HOST_SLAVE_WR_QUASAR_4BYTE 0x0000000C + +#define HOST_SLAVE_RD_QUASAR_BYTE 0x00000000 +#define HOST_SLAVE_RD_QUASAR_1BYTE 0x00000000 +#define HOST_SLAVE_RD_QUASAR_2BYTE 0x00000004 +#define HOST_SLAVE_RD_QUASAR_3BYTE 0x00000008 +#define HOST_SLAVE_RD_QUASAR_4BYTE 0x0000000C + +//------------------------------------------------------ +// I2S REGISTERs 0x0050_1600 +//------------------------------------------------------ +#define JASPER_I2S_BASE 0x00501600 + +#define I2S_CTRL 0x0 +#define I2S_PROG_LEN 0x4 +#define I2S_STATUS 0x8 +#define I2S_FRAME_CNTR 0xC + +#define I2S_RESET 0x2 +#define I2S_ENABLE 0x1 +#define I2S_MASTER_MODE 0x4 +#define I2S_SCIN_DIV00 0x00 +#define I2S_SCIN_DIV01 0x08 +#define I2S_SCIN_DIV10 0x10 +#define I2S_SCIN_DIV11 0x18 + +//------------------------------------------------------ +// SPI/I2S_DMA REGISTERs 0x0050_1700 +//------------------------------------------------------ +#define JASPER_SPI_I2S_DMA_BASE 0x00501700 + +#define SPI_I2S_DMA_CTRL_REG 0x0 +#define SPI_I2S_DMA_BASE_ADD_REG 0x4 +#define SPI_I2S_DMA_SIZE_REG 0x8 +#define SPI_I2S_DMA_WR_PTR_REG 0xC +#define SPI_I2S_DMA_RD_PTR_REG 0x10 +#define SPI_I2S_DMA_TRSH_REG 0x14 +#define SPI_I2S_DMA_INT_EN_REG 0x18 +#define SPI_I2S_DMA_INT_REG 0x1C +#define SPI_I2S_DMA_INT_POLL_REG 0x20 +#define SPI_I2S_DMA_INTER_FIFO_REG 0x24 +#define SPI_I2S_DMA_CNT_REG 0x28 + +//------------------------------------------------------ +// SPI REGISTERs 0x0050_1800 +//------------------------------------------------------ +#define JASPER_SPI_BASE 0x00501800 + +#define SPI_CNTR 0x0 +#define SPI_REC_COUNTER 0x4 +#define SPI_INT_EN 0x8 +#define SPI_INT_STATUS 0xC +#define SPI_ERROR_CNT 0x10 + + +//------------------------------------------------------ +// QUASAR PM/DM AREA +//------------------------------------------------------ +#define JASPER_QUASAR_BASE 0x00600000 + +#define QUASAR_MAP_AREA 0x00600000 +#define QUASAR_PM_START 0x00600000 +#define QUASAR_PM_SIZE 0x00002000 +#define QUASAR_DM_START 0x00604000 +#define QUASAR_DM_SIZE 0x00002000 + +//------------------------------------------------------ +// QUASAR DRAM CONTROLLER +//------------------------------------------------------ + +#define QUASAR_DRAM_CFG 0x00007000 +#define QUASAR_DRAM_FIFOSIZE0 0x00007004 +#define QUASAR_DRAM_FIFOSIZE1 0x00007008 +#define QUASAR_DRAM_CASDELAY 0x0000700C +#define QUASAR_DRAM_PLLCONTROL 0x00007010 +#define QUASAR_DRAM_TK0 0x00007014 +#define QUASAR_DRAM_TK1 0x00007018 +#define QUASAR_DRAM_TK2 0x0000701C +#define QUASAR_DRAM_STARTUP0 0x00007020 +#define QUASAR_DRAM_STARTUP1 0x00007024 +#define QUASAR_DRAM_AC3_BASE 0x00007028 +#define QUASAR_DRAM_FIFOSIZE2 0x0000702C +#define QUASAR_DRAM_PORTMUX 0x00007030 + +//------------------------------------------------------ +// QUASAR <-> HOST INTERFACE ( DMA ENGINES ) +//------------------------------------------------------ + +#define QUASAR_H2Q_READ_ADDRESS_LO 0x00007F80 //0xFE0 +#define QUASAR_H2Q_READ_ADDRESS_HI 0x00007F84 //0xFE1 +#define QUASAR_H2Q_READ_CONTER 0x00007F88 //0xFE2 +#define QUASAR_H2Q_READ_MASTER_ENABLE 0x00007F8C //0xFE3 +#define QUASAR_H2Q_INT_MASK 0x00007F90 //0xFE4 +#define QUASAR_H2Q_INT 0x00007F94 //0xFE5 +#define QUASAR_H2Q_INT_STATUS 0x00007F98 //0xFE6 + +#define QUASAR_Q2H_WRITE_ADDRESS_LO 0x00007FA0 //0xFE8 +#define QUASAR_Q2H_WRITE_ADDRESS_HI 0x00007FA4 //0xFE9 +#define QUASAR_Q2H_WRITE_CONTER 0x00007FA8 //0xFEA +#define QUASAR_Q2H_WRITE_MASTER_ENABLE 0x00007FAC //0xFEB +#define QUASAR_Q2H_INT_MASK 0x00007FB0 //0xFEC +#define QUASAR_Q2H_INT 0x00007FB4 //0xFED +#define QUASAR_Q2H_INT_STATUS 0x00007FB8 //0xFEE + +#define QUASAR_OSD_SOURCE_ADDRESS_LO 0x00007980 //0xE60 +#define QUASAR_OSD_SOURCE_ADDRESS_HI 0x00007984 //0xE61 +#define QUASAR_OSD_SOURCE_COUNTER 0x00007988 //0xE62 +#define QUASAR_OSD_SOURCE_MUX_ENABLE 0x0000798C //0xE63 +#define QUASAR_OSD_INT_MASK 0x00007990 //0xE64 +#define QUASAR_OSD_INT 0x00007994 //0xE65 +#define QUASAR_OSD_INT_STATUS 0x00007998 //0xE66 + +//------------------------------------------------------ +// QUASAR Local Bus Controller (LBC) +//------------------------------------------------------ + +#define QUASAR_LBC_CONFIG0 0x00007900 //0x1E40 +#define QUASAR_LBC_CONFIG1 0x00007904 //0x1E41 +#define QUASAR_LBC_WRITE_FIFO0_ACCESS 0x00007908 //0x1E42 +#define QUASAR_LBC_WRITE_FIFO0_CNT 0x0000790C //0x1E43 +#define QUASAR_LBC_READ_FIFO0_ACCESS 0x00007910 //0x1E44 +#define QUASAR_LBC_READ_FIFO0_CNT 0x00007914 //0x1E45 +#define QUASAR_LBC_READ_FIFO1_ACCESS 0x00007918 //0x1E46 +#define QUASAR_LBC_READ_FIFO1_CNT 0x0000791C //0x1E47 +#define QUASAR_LBC_WRITE_ADDR 0x00007920 //0x1E48 +#define QUASAR_LBC_WRITE_DATA 0x00007924 //0x1E49 +#define QUASAR_LBC_READ_ADDR 0x00007928 //0x1E4a +#define QUASAR_LBC_READ_DATA 0x0000792C //0x1E4b +#define QUASAR_LBC_BURST_XFER_CTRL 0x00007930 //0x1E4c +#define QUASAR_LBC_STATUS 0x00007934 //0x1E4d +#define QUASAR_LBC_INTERRUPT 0x00007938 //0x1E4e +#define QUASAR_LBC_PGIO 0x0000793C //0x1E4f + +//------------------------------------------------------ +// PIO COMMAND DEFINITIONS +//------------------------------------------------------ +// IN/OUTPUT PIO direction +#define PIO_OUTPUT_BIT0 0x00010001 +#define PIO_OUTPUT_BIT1 0x00020002 +#define PIO_OUTPUT_BIT2 0x00040004 +#define PIO_OUTPUT_BIT3 0x00080008 +#define PIO_OUTPUT_BIT4 0x00100010 +#define PIO_OUTPUT_BIT5 0x00200020 +#define PIO_OUTPUT_BIT6 0x00400040 +#define PIO_OUTPUT_BIT7 0x00800080 +#define PIO_OUTOUT_ALL16BITS 0xFFFFFFFF +// Set DATA to the PIO OUTPUT pin +#define PIO_DATA_BIT0 0x00010001 +#define PIO_DATA_BIT1 0x00020002 +#define PIO_DATA_BIT2 0x00040004 +#define PIO_DATA_BIT3 0x00080008 +#define PIO_DATA_BIT4 0x00100010 +#define PIO_DATA_BIT5 0x00200020 +#define PIO_DATA_BIT6 0x00400040 +#define PIO_DATA_BIT7 0x00800080 + +#define PIO_EN_SET_BIT7 0x00800000 +#define PIO_EN_SET_BIT6 0x00400000 +#define PIO_EN_SET_BIT5 0x00200000 +#define PIO_EN_SET_BIT4 0x00100000 +#define PIO_EN_SET_BIT3 0x00080000 +#define PIO_EN_SET_BIT2 0x00040000 +#define PIO_EN_SET_BIT1 0x00020000 +#define PIO_EN_SET_BIT0 0x00010000 +#define PIO_EN_ALL16_BITS 0xFFFF0000 +#define PIO_EN_BITS15to8 0xFF000000 +#define PIO_EN_BITS7to0 0x00FF0000 + +//------------------------------------------------------ +// INT CTRL COMMAND DEFINITIONS +//------------------------------------------------------ +#define ENABLE_TIMER0_INT 0x00000001 +#define ENABLE_TIMER1_INT 0x00000002 +#define ENABLE_PIO0_INT 0x00000020 +#define ENABLE_PIO1_INT 0x00000040 + +#define GLOBAL_INT_ENABLE 0x80000000 + +#define MAX_INT 0x00800000 +#define Q2H_LOC_INT 0x00400000 +#define Q2H_RISC_INT 0x00200000 +#define SPI_INT 0x00100000 +#define SPI_I2S_DMA_INT 0x00080000 +#define I2S_INT 0x00040000 +#define RTC_INT 0x00020000 +#define Q2P_INT 0x00010000 +#define P2Q_DMA_INT 0x00008000 +#define OSD_DMA_INT 0x00004000 +#define FIP_INT 0x00002000 +#define IDE_DMA_INT 0x00001000 +#define IDE_INT 0x00000800 +#define DVD_DMA_INT 0x00000400 +#define DVD_INT 0x00000200 +#define I2CS_INT 0x00000100 +#define I2CM_INT 0x00000080 +#define PIO1_INT 0x00000040 +#define PIO0_INT 0x00000020 +#define UART1_INT 0x00000008 +#define UART0_INT 0x00000004 +#define WDTIMER_INT 0x00000002 +#define TIMER1_INT 0x00000002 +#define TIMER0_INT 0x00000001 + +#define JASPER_SC_VALID_INT 0x007FFFEF + +//------------------------------------------------------ +// INT CTRL COMMAND DEFINITIONS II for J_IRQCTRL.c +//------------------------------------------------------ +#define ENABLE_INT_GLOBAL 0x80000000 //INT_ENABLE command + +#define ENABLE_INT_TIMER0 0x80000001 //INT_ENABLE command +#define ENABLE_INT_WDTIMER 0x80000002 //INT_ENABLE command +#define ENABLE_INT_UART_1 0x80000004 //INT_ENABLE command +#define ENABLE_INT_UART_2 0x80000008 //INT_ENABLE command + +#define ENABLE_INT_PIO 0x80000020 //INT_ENABLE command +#define ENABLE_INT_PIO1 0x80000040 //INT_ENABLE command +#define ENABLE_INT_I2C_MASTER 0x80000080 //INT_ENABLE command + +#define ENABLE_INT_I2C_SLAVE 0x80000100 //INT_ENABLE command +#define ENABLE_INT_DVD 0x80000200 //INT_ENABLE command +#define ENABLE_INT_DVD_DMA 0x80000400 //INT_ENABLE command +#define ENABLE_INT_IDE 0x80000800 //INT_ENABLE command + +#define ENABLE_INT_IDE_DMA 0x80001000 //INT_ENABLE command +#define ENABLE_INT_FIP 0x80002000 //INT_ENABLE command +#define ENABLE_INT_OSD_DMA 0x80004000 //INT_ENABLE command +#define ENABLE_INT_H2Q_DMA 0x80008000 //INT_ENABLE command + +#define ENABLE_INT_Q2H_DMA 0x80010000 //INT_ENABLE command +#define ENABLE_INT_RTC 0x80020000 //INT_ENABLE command +#define ENABLE_INT_I2S 0x80040000 //INT_ENABLE command +#define ENABLE_INT_I2S_DMA 0x80080000 //INT_ENABLE command + +#define ENABLE_INT_SPI 0x80100000 //INT_ENABLE command +#define ENABLE_INT_Q2H_RISC 0x80200000 //INT_ENABLE command +#define ENABLE_INT_Q2H_LOC 0x80400000 //INT_ENABLE command + +#define DISABLE_INT_GLOBAL 0x7FFFFFFF //INT_ENABLE command +#define DISABLE_INT_UART_1 0xFFFFFFFB //INT_ENABLE command +#define DISABLE_INT_UART_2 0xFFFFFFF7 //INT_ENABLE command +#define DISABLE_INT_ALL 0x10000000 //INT_ENABLE command + +//------------------------------------------------------ +//SYSTEM CTRL COMMAND DEFINITIONS +//------------------------------------------------------ +#define SYSCTRL_CMD_RESET_TIMER 0x00000001 // Bit 0 +#define SYSCTRL_CMD_RESET_INTCTRL 0x00000002 // Bit 1 +#define SYSCTRL_CMD_RESET_UART0 0x00000004 // Bit 2 +#define SYSCTRL_CMD_RESET_UART1 0x00000008 // Bit 3 +#define SYSCTRL_CMD_RESET_MAC 0x00000010 // Bit 4 +#define SYSCTRL_CMD_RESET_PIO0 0x00000020 // Bit 5 +#define SYSCTRL_CMD_RESET_PIO1 0x00000040 // Bit 6 +#define SYSCTRL_CMD_RESET_I2CM 0x00000080 // Bit 7 + +#define SYSCTRL_CMD_RESET_I2CS 0x00000100 // Bit 8 +#define SYSCTRL_CMD_RESET_DVD 0x00000200 // Bit 9 +#define SYSCTRL_CMD_RESET_DVD_DMA 0x00000400 // Bit10 +#define SYSCTRL_CMD_RESET_IDE 0x00000800 // Bit11 +#define SYSCTRL_CMD_RESET_IDE_DMA 0x00001000 // Bit12 +#define SYSCTRL_CMD_RESET_FIP 0x00002000 // Bit13 +#define SYSCTRL_CMD_RESET_OSD_DMA 0x00004000 // Bit14 +#define SYSCTRL_CMD_RESET_H2Q_DMA 0x00008000 // Bit15 + +#define SYSCTRL_CMD_RESET_Q2H_DMA 0x00010000 // Bit16 +#define SYSCTRL_CMD_RESET_RTC 0x00020000 // Bit17 +#define SYSCTRL_CMD_RESET_I2S 0x00040000 // Bit18 +#define SYSCTRL_CMD_RESET_I2S_DMA 0x00080000 // Bit19 +#define SYSCTRL_CMD_RESET_SPI 0x00100000 // Bit20 +#define SYSCTRL_CMD_RESET_SLAVE 0x00200000 // Bit21 +#define SYSCTRL_CMD_RESET_3 0x00400000 // Bit22 +#define SYSCTRL_CMD_RESET_4 0x00800000 // Bit23 + +#define SYSCTRL_CMD_RESET_5 0x01000000 // Bit24 +#define SYSCTRL_CMD_RESET_6 0x02000000 +#define SYSCTRL_CMD_RESET_7 0x03000000 +#define SYSCTRL_CMD_RESET_8 0x04000000 +#define SYSCTRL_CMD_RESET_10 0x10000000 +#define SYSCTRL_CMD_RESET_11 0x20000000 +#define SYSCTRL_CMD_RESET_Q4 0x40000000 // Bit30 +#define SYSCTRL_CMD_RESET_ALL 0x80000000 // Bit31 + +#define FORCE_REMAP 0x1 +#define CPU_ACCESS_240_CLOCKS 0xF // 240 closck +#define CPU_TIMEOUT_15_CLOCK 0xF // 15 clocks +#define CPU_TIMESLOT_ENABLE 0x100 // Enable TIMESLOT mechanism + + +//------------------------------------------------------ +// MAC COMMAND DEFINITIONS +//------------------------------------------------------ +#define SDRAMCLK_EN 0x1 +#define SDRAMINI_SET 0x2 +//#define TIME_SLOT_4DW 0x0 +//#define TIME_SLOT_8DW 0x1 +//#define TIME_SLOT_16DW 0x2 +//#define TIME_SLOT_32DW 0x3 +#define TIME_SLOT_HIGH_ARBITRATION 0x10000 +#define TIME_SLOT_SLOW_ARBITRATION 0x000 + + + +//------------------------------------------------------ +// TIMER COMMAND DEFINITIONS +//------------------------------------------------------ +#define TIMER02IRQ 0xFFFFFFFE +#define TIMER02FIQ 0x00000001 +#define TIMER12IRQ 0xFFFFFFFD +#define TIMER12FIQ 0x00000002 +#define TIMER0_START 0x00000010 +#define TIMER1_START 0x00000020 +#define TIMER0_INT_CLR 0x00000001 +#define TIMER1_INT_CLR 0x00000002 + +#define TIMER_FREE_RUN_MODE 0x00000000 +#define TIMER_PERIODIC_MODE 0x00000010 +#define TIMER_RUN_OUT_MODE 0x00000020 +#define TIMER_COUNT_ENABLE 0x00000080 + +#define TIMER_NO_PRESCALE 0x0 +#define TIMER_PRESCALE_4 0x1 +#define TIMER_PRESCALE_8 0x2 +#define TIMER_PRESCALE_16 0x3 +#define TIMER_PRESCALE_32 0x4 +#define TIMER_PRESCALE_64 0x5 +#define TIMER_PRESCALE_128 0x6 +#define TIMER_PRESCALE_256 0x7 +#define TIMER_PRESCALE_512 0x8 +#define TIMER_PRESCALE_1024 0x9 +#define TIMER_PRESCALE_2048 0xA +#define TIMER_PRESCALE_4096 0xB +#define TIMER_PRESCALE_8192 0xC +#define TIMER_PRESCALE_16384 0xD +#define TIMER_PRESCALE_32768 0xE +#define TIMER_PRESCALE_65536 0xF + + +//------------------------------------------------------ +// SPI COMMAND DEFINITIONS +//------------------------------------------------------ +#define SPI_ENABLE 0x1 +#define SPI_RESET 0x2 +#define SPI_RESET_ERR_CNTR 0x4 +#define SPI_DISABLE_ERR_CHECK 0x8 +#define SPI_STATUS_MASK 0x000070 + +#define SPI_PKT_DONE 0x1 +#define SPI_SIZE_ERR 0x2 +#define SPI_SYNC_ERR 0x4 +#define SPI_HW_ERR 0x8 + +//------------------------------------------------------ +// SPI/I2S-DMA COMMAND DEFINITIONS +//------------------------------------------------------ +#define SPI_I2S_DMA_RESET 0x2 +#define SPI_I2S_DMA_EN 0x1 +#define SPI_I2S_INT_BUF_FULL 0x1 +#define SPI_I2S_INT_BUF_14 0x2 +#define SPI_I2S_INT_BUF_12 0x4 +#define SPI_I2S_INT_BUF_34 0x8 +#define SPI_I2S_CIR_EN 0x8 /* Enable circular buffer */ +#define SPI_I2S_MUX_SPI_SELECT 0x10 +#define SPI_I2S_FLASH_INTER_FIFO 0x4 + +//------------------------------------------------------ +// FIP COMMAND DEFINITIONS +//------------------------------------------------------ +#define FIP_CMD_DISP_MODE_08DIGITS_20SEGMENTS 0x00 +#define FIP_CMD_DISP_MODE_09DIGITS_19SEGMENTS 0x08 +#define FIP_CMD_DISP_MODE_10DIGITS_18SEGMENTS 0x09 +#define FIP_CMD_DISP_MODE_11DIGITS_17SEGMENTS 0x0a +#define FIP_CMD_DISP_MODE_12DIGITS_16SEGMENTS 0x0b +#define FIP_CMD_DISP_MODE_13DIGITS_15SEGMENTS 0x0c +#define FIP_CMD_DISP_MODE_14DIGITS_14SEGMENTS 0x0d +#define FIP_CMD_DISP_MODE_15DIGITS_13SEGMENTS 0x0e +#define FIP_CMD_DISP_MODE_16DIGITS_12SEGMENTS 0x0f + + +#define FIP_CMD_DATA_SET_RW_MODE_WRITE_DISPLAY 0x40 +#define FIP_CMD_DATA_SET_RW_MODE_WRITE_LED_PORT 0x41 +#define FIP_CMD_DATA_SET_RW_MODE_READ_KEYS 0x42 +#define FIP_CMD_DATA_SET_RW_MODE_READ_SWITCHES 0x43 +#define FIP_CMD_DATA_SET_ADR_MODE_INCREMENT_ADR 0x40 +#define FIP_CMD_DATA_SET_ADR_MODE_FIXED_ADR 0x44 +#define FIP_CMD_DATA_SET_OP_MODE_NORMAL_OPERATION 0x40 +#define FIP_CMD_DATA_SET_OP_MODE_TEST_MODE 0x48 + +#define FIP_CMD_ADR_SETTING 0xC0 + +#define FIP_CMD_DISP_CTRL_PULSE_WIDTH_1_16 0x80 +#define FIP_CMD_DISP_CTRL_PULSE_WIDTH_2_16 0x81 +#define FIP_CMD_DISP_CTRL_PULSE_WIDTH_4_16 0x82 +#define FIP_CMD_DISP_CTRL_PULSE_WIDTH_10_16 0x83 +#define FIP_CMD_DISP_CTRL_PULSE_WIDTH_11_16 0x84 +#define FIP_CMD_DISP_CTRL_PULSE_WIDTH_12_16 0x85 +#define FIP_CMD_DISP_CTRL_PULSE_WIDTH_13_16 0x86 +#define FIP_CMD_DISP_CTRL_PULSE_WIDTH_14_16 0x87 +#define FIP_CMD_DISP_CTRL_TURN_DISPLAY_OFF_MASK 0x87 +#define FIP_CMD_DISP_CTRL_TURN_DISPLAY_ON 0x88 + +//--------------------------------------------------------------------------------------------- +// I/O Macro definitions +//--------------------------------------------------------------------------------------------- +#define PRINT_STATUS *( (volatile unsigned int * )SimStatusAddress) +#define VERILOG_STOP *( (volatile unsigned int * )SIMULATION_CONTROL) = SIMULATION_CMD_STOP_WITH_ERROR + +//--------------------------------------------------------------------------------------------- +// QuickTurn Macro definitions +//--------------------------------------------------------------------------------------------- +#define WRITE_LED_DISPLAY PIO_0_DATA_REG +#define WRITE_HEX_DISPLAY PIO_1_DATA_REG + +//--------------------------------------------------------------------------------------------- +// PIO Macro definitions +//--------------------------------------------------------------------------------------------- +#define PIO_0_INT_STATUS_REG ( (volatile unsigned int * ) (JASPER_PIO0_BASE + PIO_INT_STATUS) ) +#define PIO_0_DATA_REG ( (volatile unsigned int * ) (JASPER_PIO0_BASE + PIO_DATA) ) +#define PIO_0_DIR_REG ( (volatile unsigned int * ) (JASPER_PIO0_BASE + PIO_DIR) ) +#define PIO_0_POL_REG ( (volatile unsigned int * ) (JASPER_PIO0_BASE + PIO_POL) ) +#define PIO_0_INT_ENABLE_REG ( (volatile unsigned int * ) (JASPER_PIO0_BASE + PIO_INT_ENABLE) ) + +#define PIO_1_INT_STATUS_REG ( (volatile unsigned int * ) (JASPER_PIO1_BASE + PIO_INT_STATUS) ) +#define PIO_1_DATA_REG ( (volatile unsigned int * ) (JASPER_PIO1_BASE + PIO_DATA) ) +#define PIO_1_DIR_REG ( (volatile unsigned int * ) (JASPER_PIO1_BASE + PIO_DIR) ) +#define PIO_1_POL_REG ( (volatile unsigned int * ) (JASPER_PIO1_BASE + PIO_POL) ) +#define PIO_1_INT_ENABLE_REG ( (volatile unsigned int * ) (JASPER_PIO1_BASE + PIO_INT_ENABLE) ) + +//--------------------------------------------------------------------------------------------- +// SPI_I2S_DMA Macro definitions +//--------------------------------------------------------------------------------------------- +#define SPI_I2S_DMA_REG_CTRL ( (volatile unsigned int * ) (SPI_I2S_DMA_BASE + SPI_I2S_DMA_CTRL_REG) ) +#define SPI_I2S_DMA_REG_BASE_ADD ( (volatile unsigned int * ) (SPI_I2S_DMA_BASE + SPI_I2S_DMA_BASE_ADD_REG) ) +#define SPI_I2S_DMA_REG_SIZE ( (volatile unsigned int * ) (SPI_I2S_DMA_BASE + SPI_I2S_DMA_SIZE_REG) ) +#define SPI_I2S_DMA_REG_WR_PTR ( (volatile unsigned int * ) (SPI_I2S_DMA_BASE + SPI_I2S_DMA_WR_PTR_REG) ) +#define SPI_I2S_DMA_REG_RD_PTR ( (volatile unsigned int * ) (SPI_I2S_DMA_BASE + SPI_I2S_DMA_RD_PTR_REG) ) +#define SPI_I2S_DMA_REG_TRSH ( (volatile unsigned int * ) (SPI_I2S_DMA_BASE + SPI_I2S_DMA_TRSH_REG) ) +#define SPI_I2S_DMA_REG_INT_EN ( (volatile unsigned int * ) (SPI_I2S_DMA_BASE + SPI_I2S_DMA_INT_EN_REG) ) +#define SPI_I2S_DMA_REG_INT ( (volatile unsigned int * ) (SPI_I2S_DMA_BASE + SPI_I2S_DMA_INT_REG) ) +#define SPI_I2S_DMA_REG_INT_POLL ( (volatile unsigned int * ) (SPI_I2S_DMA_BASE + SPI_I2S_DMA_INT_POLL_REG) ) +#define SPI_I2S_DMA_REG_INTER_FIFO ( (volatile unsigned int * ) (SPI_I2S_DMA_BASE + SPI_I2S_DMA_INTER_FIFO_REG) ) +#define SPI_I2S_DMA_REG_CNT ( (volatile unsigned int * ) (SPI_I2S_DMA_BASE + SPI_I2S_DMA_CNT_REG) ) + +//--------------------------------------------------------------------------------------------- +// Memory Access Controller (MAC) Macro definitions +//--------------------------------------------------------------------------------------------- +#define MAC_REFTIMER_REG ( (volatile unsigned int * ) (MAC_base + MAC_REFTIMER) ) +#define MAC_FLASHCFG_REG ( (volatile unsigned int * ) (MAC_base + MAC_FLASH_CFG) ) +#define MAC_SDRAMCTL_REG ( (volatile unsigned int * ) (MAC_base + MAC_SDRAMCTL) ) +#define MAC_SDRAMCFG_REG ( (volatile unsigned int * ) (MAC_base + MAC_SDRAMCFG) ) +#define MAC_SDRAMDATA_REG ( (volatile unsigned int * ) (MAC_base + MAC_SDRAMDATA) ) + +//--------------------------------------------------------------------------------------------- +// Interrupt Controller Macro definitions +//--------------------------------------------------------------------------------------------- +#define INT_IRQSTAT_REG ( (volatile unsigned int * ) (JASPER_INT_CONTROLLER_BASE + INT_IRQSTAT) ) +#define INT_FIQSTAT_REG ( (volatile unsigned int * ) (JASPER_INT_CONTROLLER_BASE + INT_FIQSTAT) ) +#define INT_TYPE_REG ( (volatile unsigned int * ) (JASPER_INT_CONTROLLER_BASE + INT_INTTYPE) ) +#define INT_POLL_REG ( (volatile unsigned int * ) (JASPER_INT_CONTROLLER_BASE + INT_INTPOLL) ) +#define INT_ENABLE_REG ( (volatile unsigned int * ) (JASPER_INT_CONTROLLER_BASE + INT_INTEN) ) + +//--------------------------------------------------------------------------------------------- +// SPI Registers Macro definitions +//--------------------------------------------------------------------------------------------- +#define SPI_CNTR_REG ( (volatile unsigned int * ) (JASPER_SPI_BASE + SPI_CNTR) ) +#define SPI_REC_COUNTER_REG ( (volatile unsigned int * ) (JASPER_SPI_BASE + SPI_REC_COUNTER) ) +#define SPI_INT_EN_REG ( (volatile unsigned int * ) (JASPER_SPI_BASE + SPI_INT_EN) ) +#define SPI_INT_STATUS_REG ( (volatile unsigned int * ) (JASPER_SPI_BASE + SPI_INT_STATUS) ) +#define SPI_ERROR_CNT_REG ( (volatile unsigned int * ) (JASPER_SPI_BASE + SPI_ERROR_CNT) ) + +//--------------------------------------------------------------------------------------------- +// I2S Registers Macro definitions +//--------------------------------------------------------------------------------------------- +#define I2S_CTRL_REG ( (volatile unsigned int * ) (JASPER_I2S_BASE + I2S_CTRL) ) +#define I2S_PROG_LEN_REG ( (volatile unsigned int * ) (JASPER_I2S_BASE + I2S_PROG_LEN) ) +#define I2S_STATUS_REG ( (volatile unsigned int * ) (JASPER_I2S_BASE + I2S_STATUS) ) +#define I2S_FRAME_CNTR_REG ( (volatile unsigned int * ) (JASPER_I2S_BASE + I2S_FRAME_CNTR) ) + +//--------------------------------------------------------------------------------------------- +// RTC Registers Macro definitions +//--------------------------------------------------------------------------------------------- +#define RTC_CTRL_REG ( (volatile unsigned int * ) (JASPER_RTC_BASE + RTC_CTRL) ) +#define RTC_LOAD1_REG ( (volatile unsigned int * ) (JASPER_RTC_BASE + RTC_LOAD1) ) +#define RTC_LOAD2_REG ( (volatile unsigned int * ) (JASPER_RTC_BASE + RTC_LOAD2) ) +#define RTC_ALARM_REG ( (volatile unsigned int * ) (JASPER_RTC_BASE + RTC_ALARM) ) +#define RTC_INTEN_REG ( (volatile unsigned int * ) (JASPER_RTC_BASE + RTC_INTEN) ) +#define RTC_INT_REG ( (volatile unsigned int * ) (JASPER_RTC_BASE + RTC_INT0) ) +#define RTC_COUNT1_REG ( (volatile unsigned int * ) (JASPER_RTC_BASE + RTC_COUNT1) ) +#define RTC_COUNT2_REG ( (volatile unsigned int * ) (JASPER_RTC_BASE + RTC_COUNT2) ) + +//--------------------------------------------------------------------------------------------- +// Timer Registers Macro definitions +//--------------------------------------------------------------------------------------------- +#define TIMER_TMRSTAT_REG ( (volatile unsigned int * ) (JASPER_TIMER_BASE + TIMER_TMRSTAT) ) +#define TIMER0_LOAD_REG ( (volatile unsigned int * ) (JASPER_TIMER_BASE + TIMER_TMR0LOAD) ) +#define TIMER0_VAL_REG ( (volatile unsigned int * ) (JASPER_TIMER_BASE + TIMER_TMR0VAL) ) +#define TIMER0_CNTL_REG ( (volatile unsigned int * ) (JASPER_TIMER_BASE + TIMER_TMR0CTL) ) + +#define TIMER1_LOAD_REG ( (volatile unsigned int * ) (JASPER_TIMER_BASE + TIMER_TMR1LOAD) ) +#define TIMER1_VAL_REG ( (volatile unsigned int * ) (JASPER_TIMER_BASE + TIMER_TMR1VAL) ) +#define TIMER1_CNTL_REG ( (volatile unsigned int * ) (JASPER_TIMER_BASE + TIMER_TMR1CTL) ) + +//--------------------------------------------------------------------------------------------- +// Interrupt Registers Macro definitions +//--------------------------------------------------------------------------------------------- +/* XXX - defined twice - see above INT_xxx_REG +#define INTERRUPT_IRQSTAT_REG ( (volatile unsigned int * ) (INT_CONTROLLER_BASE + INT_IRQSTAT) ) +#define INTERRUPT_FIQSTAT_REG ( (volatile unsigned int * ) (INT_CONTROLLER_BASE + INT_FIQSTAT) ) +#define INTERRUPT_INTTYPE_REG ( (volatile unsigned int * ) (INT_CONTROLLER_BASE + INT_INTTYPE) ) +#define INTERRUPT_POLL_REG ( (volatile unsigned int * ) (INT_CONTROLLER_BASE + INT_INTPOLL) ) +#define INTERRUPT_ENABLE_REG ( (volatile unsigned int * ) (INT_CONTROLLER_BASE + INT_INTEN) ) +*/ + +//--------------------------------------------------------------------------------------------- +// System Registers Macro definitions +//--------------------------------------------------------------------------------------------- +#define SYS_CHIPID_REG ( (volatile unsigned int * ) (JASPER_SYSCTRL_BASE + SYSCTRL_CHIP_ID) ) +#define SYS_REVID_REG ( (volatile unsigned int * ) (JASPER_SYSCTRL_BASE + SYSCTRL_REVISION_ID) ) +#define SYS_CPU_CFG_REG ( (volatile unsigned int * ) (JASPER_SYSCTRL_BASE + SYSCTRL_CPUCFG) ) +#define SYS_TESTSTAT_REG ( (volatile unsigned int * ) (JASPER_SYSCTRL_BASE + SYSCTRL_TESTSTAT) ) +#define SYS_RESET_REG ( (volatile unsigned int * ) (JASPER_SYSCTRL_BASE + SYSCTRL_RSTCTL) ) +#define SYS_TIMESLOT_REG ( (volatile unsigned int * ) (JASPER_SYSCTRL_BASE + SYSCTRL_CPUTIMESLOT) ) + +//--------------------------------------------------------------------------------------------- +// FIP Registers Macro definitions +//--------------------------------------------------------------------------------------------- +#define FIP_COMMAND_REG ( (volatile unsigned int * ) (JASPER_FIP_BASE + FIP_COMMAND) ) +#define FIP_DISPLAY_DATA_REG ( (volatile unsigned int * ) (JASPER_FIP_BASE + FIP_DISPLAY_DATA) ) +#define FIP_LED_DATA_REG ( (volatile unsigned int * ) (JASPER_FIP_BASE + FIP_LED_DATA) ) +#define FIP_KEY_DATA1_REG ( (volatile unsigned int * ) (JASPER_FIP_BASE + FIP_KEY_DATA1) ) +#define FIP_KEY_DATA2_REG ( (volatile unsigned int * ) (JASPER_FIP_BASE + FIP_KEY_DATA2) ) +#define FIP_SWITCH_DATA_REG ( (volatile unsigned int * ) (JASPER_FIP_BASE + FIP_SWITCH_DATA) ) +#define FIP_CLK_DIV_REG ( (volatile unsigned int * ) (JASPER_FIP_BASE + FIP_CLK_DIV) ) +#define FIP_TRISTATE_MODE_REG ( (volatile unsigned int * ) (JASPER_FIP_BASE + FIP_TRISTATE_MODE) ) + +#ifndef __ASSEMBLY__ +#include + +#define HARD_RESET_NOW() outl(JASPER_SYSCTRL_BASE + SYSCTRL_RSTCTL,1) + +static inline unsigned long __get_clock(unsigned int unit) +{ + unsigned int clock; +#ifndef CONFIG_QUICKTURN_HACKS + unsigned int pll_reg; + unsigned int pll_mult, pll_div, pll_D; + + pll_reg = inl(JASPER_QUASAR_BASE + QUASAR_DRAM_PLLCONTROL); + pll_mult = (pll_reg & 0xFF) >> 2; + pll_div = (pll_reg >> 8 ) & 0x3; + pll_D = (pll_reg & 0x2); + + clock = ((JASPER_EXT_CLOCK / unit) * (pll_mult+2))/(pll_div + 2); + if(pll_D) + clock = clock / 2; +#else +#warning ***** QUICKTURN HACK ***** Kernel think CPU clock is 4 Mhz + clock = 4000000 / unit; +#endif + return clock; +} +#endif + +#endif /* _ASM_ARCH_HARDWARE_H */ + + diff --git a/src/libsp/MG35/sp_cdrom.cpp b/src/libsp/MG35/sp_cdrom.cpp new file mode 100644 index 0000000..7cbf44f --- /dev/null +++ b/src/libsp/MG35/sp_cdrom.cpp @@ -0,0 +1,651 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - CDROM driver's functions source file. + * For Technosonic-compatible players ('MP') + * \file sp_cdrom.cpp + * \author bombur + * \version 0.1 + * \date 4.05.2004 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef __USE_LARGEFILE64 +#define __USE_LARGEFILE64 +#endif + +#include + +#include +#include + +#include "sp_misc.h" +#include "sp_msg.h" +#include "sp_cdrom.h" + +/// CD-ROM fs mount flag +static BOOL cdrom_cd_mounted = FALSE, cdrom_hdd_mounted = FALSE, cdrom_cd_inserted = FALSE; +static BOOL cdrom_cd = FALSE, cdrom_hdd = FALSE, cdrom_hdd_root = FALSE; +/// CD-ROM fs mount language +static char *def_cdrom_language = "iso8859-1"; +static char *cdrom_language = NULL; +static char cdrom_last_letter = 'c'; +static CDROM_STATUS cdrom_hdd_status = CDROM_STATUS_HAS_ISO; + +/// Internal function used by cdrom_getstatus() +static int cdrom_getmediumtype(); + +#if 0 +static void cdrom_count_tracks(); +#endif + +/// OS-specific path to CD-ROM. Used by cdrom_getdevicepath() +static const char *curdvdpath = "/dev/cdroms/cdrom0"; + +/// CD-ROM device handle +int cdrom_handle = -1; +int cdrom_hdd_handle[2] = { -1, -1 }; + +/// Used by cdrom_getmediumtype(). +/// Taken from linux/cdrom.h (kernel mode) + +struct mode_page_header +{ + WORD mode_data_length; + BYTE medium_type; + BYTE reserved1; + BYTE reserved2; + BYTE reserved3; + WORD desc_length; +}; + + +/// Used by cdrom_getmediumtype(). +/// Taken from drivers/ide/ide-cd.h (uClinux source) +struct atapi_capabilities_page +{ + struct mode_page_header header; + BYTE caps[20]; +}; + + +BOOL cdrom_init() +{ + cdrom_deinit(); + + cdrom_language = SPstrdup(def_cdrom_language); + cdrom_hdd_mounted = FALSE; + cdrom_cd_mounted = FALSE; + cdrom_cd_inserted = FALSE; + + // try CD-ROM + cdrom_handle = open("/dev/cdroms/cdrom0", O_NONBLOCK); + if (cdrom_handle >= 0) + { + cdrom_cd = TRUE; + printf("cdrom: CD-ROM Detected!\n"); + } + // try HDD + for (int i = 0; i < 2; i++) + { + char hdd_name[40]; + sprintf(hdd_name, "/dev/discs/disc%d/disc", i); + cdrom_hdd_handle[i] = open(hdd_name, O_NONBLOCK); + if (cdrom_hdd_handle[i] >= 0) + { + cdrom_hdd = TRUE; + printf("cdrom: HDD-%d Detected!\n", i+1); + } + } + + return (cdrom_handle != -1 || cdrom_hdd_handle[0] != -1 || cdrom_hdd_handle[1] != -1); +} + +BOOL cdrom_switch(BOOL on) +{ + if (cdrom_hdd) + { + BYTE args1[4], args2[4]; + int r1, r2 = 0; + + for (int i = 0; i < 2; i++) + { + if (on) + { + static const BYTE on_args1[4] = { WIN_SETIDLE1, 0, 0, 0 }; + static const BYTE on_args2[4] = { WIN_SETIDLE2, 0, 0, 0 }; + + memcpy(args1, on_args1, 4); + memcpy(args2, on_args2, 4); + } else + { + static const BYTE off_args1[4] = { WIN_STANDBYNOW1, 0, 0, 0 }; + static const BYTE off_args2[4] = { WIN_STANDBYNOW2, 0, 0, 0 }; + + memcpy(args1, off_args1, 4); + memcpy(args2, off_args2, 4); + } + + if (cdrom_hdd_handle[i] == -1) + continue; + r1 = ioctl(cdrom_hdd_handle[i], HDIO_DRIVE_CMD, &args1); + if (r1 != 0) + r2 = ioctl(cdrom_hdd_handle[i], HDIO_DRIVE_CMD, &args2); + + printf("HDD-%d: Switch %s (result=%d,%d)\n", i+1, (on ? "On" : "Off"), r1, r2); + + // now check the results... + BYTE cp_args1[4] = { WIN_CHECKPOWERMODE1, 0, 0, 0 }; + BYTE cp_args2[4] = { WIN_CHECKPOWERMODE2, 0, 0, 0 }; + const char *state; + if (ioctl(cdrom_hdd_handle[i], HDIO_DRIVE_CMD, &cp_args1) && ioctl(cdrom_hdd_handle[i], HDIO_DRIVE_CMD, &cp_args2)) + { + if (errno != EIO || cp_args1[0] != 0 || cp_args1[1] != 0) + state = "Unknown"; + else + state = "Sleeping"; + } else + { + if (cp_args1[2] == 255 || cp_args2[2] == 255) + state = "Active/Idle"; + else + state = "Standby"; + } + msg("HDD-%d: Drive state is: %s\n", i+1, state); + } + } + if (cdrom_cd) + { + if (on) + ioctl(cdrom_handle, CDROMSTART); + else + ioctl(cdrom_handle, CDROMSTOP); + + printf("CD: Switch %s\n", (on ? "On" : "Off")); + } + return TRUE; +} + +BOOL cdrom_deinit() +{ + if (cdrom_handle != -1) + close (cdrom_handle); + if (cdrom_hdd_handle[0] != -1) + close (cdrom_hdd_handle[0]); + if (cdrom_hdd_handle[1] != -1) + close (cdrom_hdd_handle[1]); + cdrom_handle = -1; + cdrom_hdd_handle[0] = cdrom_hdd_handle[1] = -1; + SPSafeFree(cdrom_language); + return TRUE; +} + +int cdrom_eject(BOOL open) +{ + if (cdrom_cd) + { + struct timeval tv; + struct timezone tz; + int start_time, stop_time; + + cdrom_generic_command cmd; + struct request_sense sense; + + cdrom_umount(); + + // [bombur]: I wish it was true... :-( + //return ioctl(cdrom_handle, CDROMEJECT, 0); + + // [bombur]: this is the exact code used in original init! + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd[0] = GPCMD_PREVENT_ALLOW_MEDIUM_REMOVAL; + cmd.cmd[4] = 0; // unlock + cmd.buffer = NULL; + cmd.buflen = 0; + cmd.sense = &sense; + cmd.data_direction = CGC_DATA_READ; + ioctl(cdrom_handle, CDROM_SEND_PACKET, &cmd); + + gettimeofday(&tv, &tz); + start_time = tv.tv_sec * 1000 + (tv.tv_usec / 1000); + + cmd.cmd[0] = GPCMD_START_STOP_UNIT; + cmd.cmd[1] = 1; + cmd.cmd[4] = open ? 2 : 3; + if (ioctl(cdrom_handle, CDROM_SEND_PACKET, &cmd)) + return -1; + + do + { + gui_update(); + usleep(10000); + gettimeofday(&tv, &tz); + stop_time = tv.tv_sec * 1000 + (tv.tv_usec / 1000); + } while (stop_time - start_time < 2000); // less than 2 seconds passed - wait! + + return 0; + } + else if (cdrom_hdd) + { + // emulate 'ejecting' HDD - to enable 'Setup' mode possibility + cdrom_hdd_status = open ? CDROM_STATUS_TRAYOPEN : CDROM_STATUS_HAS_ISO; + return 0; + } + + return -1; +} + +int cdrom_mount(char *language, BOOL cd_only) +{ + int ret = 0; + char iocharset[1024]; + if (language == NULL) + language = cdrom_language; + + BOOL lang_changed = strcasecmp(cdrom_language, language) != 0; + if (cdrom_cd && cdrom_cd_inserted) + { + if (!cdrom_cd_mounted || lang_changed) + { + int rmnt = ((cdrom_cd_mounted && lang_changed) ? MS_REMOUNT : 0); + sprintf(iocharset, "iocharset=%s", language); + ret = mount("/dev/cdroms/cdrom0", "/cdrom", "iso9660", MS_RDONLY | MS_NOSUID | MS_NODEV | rmnt, iocharset); + msg("CD: mount with %s(%d) = %d\n", iocharset, rmnt, ret < 0 ? -errno : ret); + if (ret >= 0) + { + cdrom_cd_mounted = TRUE; + } + } +// cdrom_count_tracks(); + } + + if (cdrom_hdd && !cd_only) + { + if (!cdrom_hdd_mounted || lang_changed) + { + char tmpdir[256], tmpmnt[256]; + char dirname[40]; + char *mntname = "/hdd/"; + char mnt_char = 'c'; + struct dirent *entry; + //int rmnt = ((cdrom_hdd_mounted && lang_changed) ? MS_REMOUNT : 0); + int rmnt = 0; + for (int i = 0; i < 2; i++) + { + if (cdrom_hdd_handle[i] < 0) + continue; + sprintf(dirname, "/dev/discs/disc%d", i); + DIR *dir = opendir(dirname); + printf("Mounting HDD-%d...\n", i+1); + while ((entry = readdir(dir)) != NULL) + { + if (memcmp(entry->d_name, "part", 4) == 0) + { + sprintf(tmpdir, "%s/%s", dirname, entry->d_name); + sprintf(tmpmnt, "%s%c", mntname, mnt_char); + + if (lang_changed) + umount(tmpmnt); + + printf("* Mounting %s AS FAT: ", tmpdir);fflush(stdout); + sprintf(iocharset, "iocharset=%s", language); + ret = mount(tmpdir, tmpmnt, "vfat", MS_RDONLY | MS_NOSUID | MS_NODEV | rmnt, iocharset); + if (ret < 0) + { + printf("NO. Mounting AS NTFS: ");fflush(stdout); + sprintf(iocharset, "nls=%s", language); + ret = mount(tmpdir, tmpmnt, "ntfs", MS_RDONLY | MS_NOSUID | MS_NODEV | rmnt, iocharset); + } + printf(ret < 0 ? "FAILED\n" : "OK\n");fflush(stdout); + if (ret >= 0) + { + mnt_char++; + cdrom_hdd_mounted = TRUE; + } + } + } + cdrom_last_letter = mnt_char - 1; + closedir(dir); + + msg("HDD-%d: mount with %s(%d) = %d\n", i+1, iocharset, rmnt, ret < 0 ? -errno : ret); + } + } + } + + if (lang_changed) + { + SPSafeFree(cdrom_language); + cdrom_language = SPstrdup(language); + } + + return ret; +} + +int cdrom_umount() +{ + msg("CD-ROM: umount.\n"); + if (cdrom_cd) + { + cdrom_cd_mounted = FALSE; + return umount("/cdrom"); + } + return FALSE; +} + +BOOL cdrom_ismounted() +{ + return cdrom_cd ? cdrom_cd_mounted : cdrom_hdd_mounted; +} + +static CDROM_STATUS cdrom_detect_dvd() +{ + char dvdpath2[1024]; + + cdrom_cd_inserted = TRUE; + + cdrom_mount(NULL, TRUE); + + DIR *dir = opendir("/cdrom"); + struct dirent *entry; + struct stat statbuf; + while ((entry = readdir(dir)) != NULL) + { + if (strcasecmp(entry->d_name, "VIDEO_TS") == 0) + { + sprintf(dvdpath2, "/cdrom/%s", entry->d_name); + if (stat(dvdpath2, &statbuf) >= 0) + { + if (S_ISDIR(statbuf.st_mode)) + { + closedir(dir); + cdrom_umount(); + return CDROM_STATUS_HAS_DVD; + } + } + } + } + closedir(dir); + + cdrom_umount(); + return CDROM_STATUS_HAS_ISO; +} + +CDROM_STATUS cdrom_getstatus() +{ + static int old_medium = -1; + static CDROM_STATUS old_status = CDROM_STATUS_UNKNOWN; + + if (!cdrom_cd && cdrom_hdd) + return cdrom_hdd_status; + + if (cdrom_handle != -1) + { + int medium = cdrom_getmediumtype(), auxtype; + + if (medium != old_medium) + msg("Drive: medium type = %d.\n", medium); + + switch (medium) + { + case 112: + old_status = cdrom_hdd ? cdrom_hdd_status : CDROM_STATUS_NODISC; + cdrom_cd_inserted = FALSE; + old_medium = medium; + return old_status; + case 113: + old_status = CDROM_STATUS_TRAYOPEN; + cdrom_cd_inserted = FALSE; + old_medium = medium; + return old_status; + case 1: + case 4: + case 5: + case 8: + case 0x11: + case 0x14: + case 0x15: + case 0x18: + case 0x21: + case 0x24: + case 0x25: + case 0x28: + // [bombur]: mixed discs are not always recognized by mediumtype + auxtype = ioctl(cdrom_handle, CDROM_DISC_STATUS, CDSL_CURRENT); + old_status = (auxtype == CDS_MIXED) ? CDROM_STATUS_HAS_MIXED : CDROM_STATUS_HAS_ISO; + old_medium = medium; + cdrom_cd_inserted = TRUE; + return old_status; + case 2: + case 6: + case 0x12: + case 0x16: + case 0x22: + case 0x26: + old_medium = medium; + old_status = CDROM_STATUS_HAS_AUDIO; + cdrom_cd_inserted = TRUE; + return old_status; + case 3: + case 7: + case 0x13: + case 0x17: + case 0x23: + case 0x27: + old_medium = medium; + old_status = CDROM_STATUS_HAS_MIXED; + cdrom_cd_inserted = TRUE; + return old_status; + case 0x40: + case 0x41: + case 0x42: + case 0x43: + case 0x44: + case 0x45: + case 0x46: + case 0x47: + case 0x48: + if (old_medium < 0x40 || old_medium > 0x48 || old_status == CDROM_STATUS_UNKNOWN) + old_status = cdrom_detect_dvd(); + old_medium = medium; + return old_status; + case 0: + old_medium = medium; + break; + default: + if (medium != old_medium) + msg("Drive: Unknown medium type = %d.\n", medium); + old_medium = medium; + break; + } + } + return CDROM_STATUS_UNKNOWN; +} + +BOOL cdrom_isready() +{ + // ready or not, we failed + if (cdrom_handle == -1) + return TRUE; + if (cdrom_hdd && !cdrom_cd) + return TRUE; + + int drive = ioctl(cdrom_handle, CDROM_DRIVE_STATUS, CDSL_CURRENT); + int disc = ioctl(cdrom_handle, CDROM_DISC_STATUS, CDSL_CURRENT); + +// msg("CD-ROM: drive.status = %d disc.status = %d.\n", drive, disc); +// gui_update(); + + if (/*drive == CDS_TRAY_OPEN || */drive == CDS_DRIVE_NOT_READY || + /*disc == CDS_TRAY_OPEN || */disc == CDS_DRIVE_NOT_READY) + return FALSE; + return TRUE; +} + +/// Internal function used by cdrom_getstatus() +/// [bombur]: this was die hard! +int cdrom_getmediumtype() +{ + cdrom_generic_command cmd; + struct request_sense sense; + struct atapi_capabilities_page caps; + + if (cdrom_handle != -1) + { + memset(&cmd, 0, sizeof(cmd)); + cmd.buffer = (BYTE *)∩︀ + cmd.buflen = sizeof(caps); + cmd.cmd[0] = GPCMD_MODE_SENSE_10; + cmd.cmd[2] = GPMODE_CAPABILITIES_PAGE; + cmd.cmd[8] = cmd.buflen & 0xff; + cmd.sense = &sense; + cmd.data_direction = CGC_DATA_READ; + ioctl(cdrom_handle, CDROM_SEND_PACKET, &cmd); + + return caps.header.medium_type; + } + return 0; +} + +const char *cdrom_getdevicepath(const char *src_path) +{ + if (src_path != NULL) + return src_path; + return curdvdpath; +} + +int cdrom_stat(const char *path, struct stat64 *s) +{ + return stat64(path, s); +} + +DIR *cdrom_opendir(const char *path) +{ + cdrom_hdd_root = FALSE; + if (cdrom_hdd) + { + if (strcasecmp(path, "/hdd/") == 0) + cdrom_hdd_root = TRUE; + } + return opendir(path); +} + +struct dirent *cdrom_readdir(DIR *dir) +{ + struct dirent *d = readdir(dir); + // fast mount-check (exclude unmounted folders) + if (cdrom_hdd_root && d->d_name[0] > cdrom_last_letter && d->d_name[1] == '\0') + return NULL; + return d; +} + +int cdrom_closedir(DIR *dir) +{ + return closedir(dir); +} + +int cdrom_open(const char *fname, int flags) +{ + return open(fname, flags); +} + +char *cdrom_getrealpath(const char *path) +{ + static char tmpp[4096]; +msg("CD-ROM: GET_REAL_PATH (%s) = %d %d %d\n", path, cdrom_hdd, cdrom_cd, cdrom_cd_inserted); + if (memcmp(path, "/", 2) == 0) + { + if (cdrom_hdd && cdrom_cd && cdrom_cd_inserted) + return (char *)path; + else if (cdrom_hdd) + strcpy(tmpp, "/hdd"); + else + strcpy(tmpp, "/cdrom"); + strcat(tmpp, path); + return tmpp; + } + return (char *)path; +} + + +#if 0 +void cdrom_count_tracks() +{ + struct cdrom_tochdr header; + struct cdrom_tocentry entry; + int ret, i; +/* + tracks->data=0; + tracks->audio=0; + tracks->cdi=0; + tracks->xa=0; + tracks->error=0; +*/ + /* Grab the TOC header so we can see how many tracks there are */ + if ((ret = ioctl(cdrom_handle, CDROMREADTOCHDR, &header))) + { + if (ret == -ENOMEDIUM) + msg("cdrom_count_tracks: CDS_NO_DISC!"); + else + msg("cdrom_count_tracks: CDS_NO_INFO!"); + return; + } + /* check what type of tracks are on this disc */ + entry.cdte_format = CDROM_MSF; + msg("Starting %d-%d:\n", header.cdth_trk0, header.cdth_trk1); + for (i = header.cdth_trk0; i <= header.cdth_trk1; i++) + { + entry.cdte_track = i; + if (ioctl(cdrom_handle, CDROMREADTOCENTRY, &entry)) + { + msg("track %2d: CDS_NO_INFO\n"); + continue; + } + /* + if (entry.cdte_ctrl & CDROM_DATA_TRACK) + { + if (entry.cdte_format == 0x10) + tracks->cdi++; + else if (entry.cdte_format == 0x20) + tracks->xa++; + else + tracks->data++; + } else + tracks->audio++; + */ + msg("track %2d: fmt=0x%x, ctrl=0x%x\n", i, entry.cdte_format, entry.cdte_ctrl); + } + /* + cdinfo(CD_COUNT_TRACKS, "disc has %d tracks: %d=audio %d=data %d=Cd-I %d=XA\n", + header.cdth_trk1, tracks->audio, tracks->data, + tracks->cdi, tracks->xa); + */ + usleep(1000000); +} +#endif diff --git a/src/libsp/MG35/sp_eeprom.cpp b/src/libsp/MG35/sp_eeprom.cpp new file mode 100644 index 0000000..1502220 --- /dev/null +++ b/src/libsp/MG35/sp_eeprom.cpp @@ -0,0 +1,60 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - EEPROM interface functions source file + * For Technosonic-compatible players ('MP') + * \file sp_eeprom.cpp + * \author bombur + * \version 0.1 + * \date 4.05.2004 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#include +#include + +#include "sp_misc.h" +#include "sp_khwl.h" +#include "sp_eeprom.h" + +BOOL eeprom_set_value(DWORD addr, DWORD val, int size) +{ + KHWL_ADDR_DATA data; + /// \warning: not usual byte order used! + for (int i = 0; i < size; i++) + { + data.Addr = addr + i; + data.Data = (val >> (i * 8)) & 0xff; + khwl_setproperty(KHWL_EEPROM_SET, eEepromAccess, sizeof(data), &data); + } + return TRUE; +} + +DWORD eeprom_get_value(DWORD addr, int size) +{ + KHWL_ADDR_DATA data; + DWORD val = 0; + /// \warning: not usual byte order used! + for (int i = 0; i < size; i++) + { + data.Addr = addr + i; + data.Data = 0; + khwl_getproperty(KHWL_EEPROM_SET, eEepromAccess, sizeof(data), &data); + val |= (data.Data & 0xff) << (i * 8); + } + return val; +} + diff --git a/src/libsp/MG35/sp_fip.cpp b/src/libsp/MG35/sp_fip.cpp new file mode 100644 index 0000000..44f82f4 --- /dev/null +++ b/src/libsp/MG35/sp_fip.cpp @@ -0,0 +1,261 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - FIP interface functions source file. + * For Technosonic-compatible players ('MP') + * \file sp_fip.cpp + * \author bombur + * \version 0.1 + * \date 4.05.2004 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include +#include + +#include "sp_misc.h" +#include "sp_fip.h" +#include "sp_fip_ioctl.h" + +#if defined(SP_PLAYER_TECHNOSONIC) + #include "sp_fip_codes-technosonic.h" +#elif defined(SP_PLAYER_DREAMX108) + #include "sp_fip_codes-dreamx108.h" +#endif + +/// Characters supported by LED +static unsigned char char_table[] = " -0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz-\1"; +/// Current LEF data storage +static unsigned char fipram[12]; +/// Binary definitions for LED data +static unsigned char led_table[] = +{ + 0x00,0x10,0xEE,0x48,0xD6,0xDA,0x78,0xBA,0xBE,0xC8,0xFE,0xFA,0xFC,0xFE,0xA6,0xEE, + 0xB6,0xB4,0xBE,0x7C,0x48,0x4A,0x7C,0x26,0xEC,0xEC,0xEE,0xF4,0xEE,0xFC,0xBA,0xC8, + 0x6E,0x6E,0x6E,0x6C,0x78,0x82,0x02,0x1E,0x3E,0x16,0x5E,0xF6,0xB4,0xFA,0x3C,0x08, + 0x0A,0x2C,0x26,0x1C,0x1C,0x1E,0xF4,0xF8,0x14,0xBA,0x36,0x0E,0x0E,0x0E,0x0C,0x78, + 0x12,0x10,0x00 +}; + +static unsigned char led_special[] = +{ + 0x01, 0x07, 0x01, 0x06, 0x03, 0x07, 0x02, 0x07, 0x05, 0x07, 0x04, 0x07, 0x07, 0x07, 0x07, 0x06, + 0x07, 0x05, 0x07, 0x04, 0x07, 0x03, 0x07, 0x02, 0x07, 0x01, 0x07, 0x00, 0x09, 0x07, 0x09, 0x06, + 0x09, 0x05, 0x09, 0x04, 0x09, 0x03, 0x09, 0x02, 0x09, 0x01, 0x09, 0x00, 0x08, 0x07, 0x08, 0x06, + 0x08, 0x05, 0x08, 0x04, 0x08, 0x03, 0x08, 0x02 +}; + +/// Button codes translation look-up table +static BYTE *button_LUT = NULL; +static DWORD butcode = 0; +static int fip_panel_num_codes = 0; + +/// FIP device handle +int fip_handle = -1; + +void fip_create_table(BYTE *lut) +{ + memset(lut, 0xff, 65536); + + DWORD *codes[2]; + codes[0] = fip_panel_codes; + codes[1] = fip_remote_codes; + + fip_panel_num_codes = 0; + for (int i = 0; i < 2; i++) + { + int j; + for (j = 0; codes[i][j] != 0xffffffff; j++) + { + lut[codes[i][j] & 0xffff] = j; + } + if (i == 0) + fip_panel_num_codes = j; + } +} + +BOOL fip_init(BOOL applymodule) +{ + fip_deinit(); + + if (applymodule == FALSE || module_apply("/drivers/fipmodule.o")) // insert + { + fip_handle = open("/dev/fip", O_NONBLOCK, 0); + if (fip_handle != -1) + { + fip_clear(); + + button_LUT = (BYTE *)SPmalloc(65536); + if (button_LUT == NULL) + return FALSE; + + fip_create_table(button_LUT); + + return TRUE; + } + } + + return FALSE; +} + +BOOL fip_deinit() +{ + if (fip_handle == -1) + return FALSE; + close(fip_handle); + fip_handle = -1; + + if (button_LUT != NULL) + { + SPfree(button_LUT); + button_LUT = NULL; + } + + return TRUE; +} + +BOOL fip_clear() +{ + int i; + + if (fip_handle == -1) + return FALSE; + // clear FIP + for (i = 0; i < 10; i++) + { + fipram[i] = 0; +// ioctl(fip_handle, FIP_DISPLAY_SYMBOL, i << 16); + } + return TRUE; +} + +BOOL fip_write_char(int ch, int pos) +{ + int lr; + unsigned char old_fip1, old_fip2 = 0; + + if (pos < 1 || pos - 1 > 6 || fip_handle == -1) + return FALSE; + ch &= 255; + for (lr = 0; lr < 66; lr++) + { + if (char_table[lr] == (BYTE)ch) + break; + } + // saving + old_fip1 = fipram[pos-1]; + + // clear + if (pos == 2) + { + old_fip2 = fipram[pos-2]; + + fipram[pos-1] &= 192; + fipram[pos-2] &= 127; + } else + fipram[pos-1] &= 128; + // set + if (pos == 2) + { + fipram[pos-1] |= led_table[lr] >> 2; + fipram[pos-2] |= (led_table[lr] << 6) & ~127; + } else + fipram[pos-1] |= led_table[lr] >> 1; + // write to device +// if (old_fip1 != fipram[pos-1]) +// ioctl(fip_handle, FIP_DISPLAY_SYMBOL, fipram[pos-1] | ((pos-1) << 16)); +// if (pos == 2 && old_fip2 != fipram[pos-2]) +// ioctl(fip_handle, FIP_DISPLAY_SYMBOL, fipram[pos-2] | ((pos-2) << 16)); + return TRUE; +} + +BOOL fip_write_string(const char *str) +{ + int i, len; + if (str == NULL || fip_handle == -1) + return FALSE; + len = strlen(str); + if (len > 7) + len = 7; + for (i = 0; i < 7; i++) + fip_write_char(i < len ? str[len - i - 1] : ' ', i + 1); + return TRUE; +} + +BOOL fip_write_special_char(int pos, int shift, BOOL onoff) +{ + BYTE ch = fipram[pos]; + if (onoff) + fipram[pos] = ch | (1 << shift); + else + fipram[pos] = ch & ~(1 << shift); + if (fipram[pos] == ch) + return TRUE; +// ioctl(fip_handle, FIP_DISPLAY_SYMBOL, fipram[pos] | (pos << 16)); + return TRUE; +} + +BOOL fip_write_special(int id, BOOL onoff) +{ + if (id < 0 || id > 27) + return FALSE; + return fip_write_special_char(led_special[id*2], led_special[id*2+1], onoff); +} + +BOOL fip_get_special(int id) +{ + if (id < 0 || id > 27) + return FALSE; + int pos = led_special[id*2]; + int shift = led_special[id*2+1]; + return (fipram[pos] >> shift) & 1; +} + +int fip_read_button(BOOL blocked) +{ + if (fip_handle == -1 || button_LUT == NULL) + return 0; + ioctl(fip_handle, FIP_BUTTON_READ, &butcode); + if (butcode == 0) + return FIP_KEY_NONE; + +#if 0 + BYTE bc = button_LUT[butcode & 0xffff]; +#ifdef SP_PLAYER_DREAMX108 + // special case - front panel STOP button + if (butcode == 0x00080000) + return FIP_KEY_FRONT_STOP; + // special case - front panel PREV button + if (butcode == 0x00010000) + return FIP_KEY_FRONT_SKIP_PREV; +#endif + + if (bc == 0xff) + return FIP_KEY_NONE; + if (bc < fip_panel_num_codes) + { + if (butcode == fip_panel_codes[bc]) + return (unsigned int)bc + fip_code_offset[0]; + } + if (butcode == fip_remote_codes[bc]) + return (unsigned int)bc + fip_code_offset[1]; +#endif + return FIP_KEY_NONE; +} diff --git a/src/libsp/MG35/sp_fip_codes-dreamx108.h b/src/libsp/MG35/sp_fip_codes-dreamx108.h new file mode 100644 index 0000000..9f7ab06 --- /dev/null +++ b/src/libsp/MG35/sp_fip_codes-dreamx108.h @@ -0,0 +1,119 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - FIP button internal codes header. + * For DreamX-108 player + * \file sp_fip_codes-dreamx108.h + * \author bombur + * \version 0.2 + * \date 21.01.2009 4.05.2007 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#ifndef SP_FIP_CODES_H +#define SP_FIP_CODES_H + + +#ifdef __cplusplus +extern "C" { +#endif + +//////////////////////////////////// +/// Button codes definitions: + +static DWORD fip_code_offset[2] = { FIP_KEY_FRONT_EJECT, FIP_KEY_POWER }; + +/// 1) definitions for the buttons on the front panel: +static DWORD fip_panel_codes[] = +{ + 0x00001000, // FIP_KEY_FRONT_EJECT, + 0x80000080, // FIP_KEY_FRONT_PLAY, + 0x00080000, // FIP_KEY_FRONT_STOP, + 0x08000008, // FIP_KEY_FRONT_PAUSE, + 0x00010000, // FIP_KEY_FRONT_SKIP_PREV, + 0x00004000, // FIP_KEY_FRONT_SKIP_NEXT, + 0x10000010, // FIP_KEY_FRONT_REWIND, + 0x01000001, // FIP_KEY_FRONT_FORWARD, + + 0xffffffff +}; + +/// 2) definitions for the keys on the remote control: +static DWORD fip_remote_codes[] = +{ + 0x00FBF00F, // FIP_KEY_POWER, + 0x00FBB847, // FIP_KEY_EJECT, + + 0x00FBCA35, // FIP_KEY_ONE, + 0x00FBD02F, // FIP_KEY_TWO, + 0x00FB7887, // FIP_KEY_THREE, + 0x00FBC03F, // FIP_KEY_FOUR, + 0x00FB8A75, // FIP_KEY_FIVE, + 0x00FB20DF, // FIP_KEY_SIX, + 0x00FB2AD5, // FIP_KEY_SEVEN, + 0x00FB807F, // FIP_KEY_EIGHT, + 0x00FBD827, // FIP_KEY_NINE, + 0x00FB708F, // FIP_KEY_ZERO, + + 0x00FB6897, // FIP_KEY_CANCEL, + 0x00FBE21D, // FIP_KEY_SEARCH, + 0x00FBA05F, // FIP_KEY_ENTER, + + 0x00FB827D, // FIP_KEY_OSD, + 0x00FBA25D, // FIP_KEY_SUBTITLE, + 0x00FBB24D, // FIP_KEY_SETUP, + 0x00FBD22D, // FIP_KEY_RETURN, + 0x00FBF807, // FIP_KEY_TITLE, + 0x00000800, // FIP_KEY_PN, + 0x00FB42BD, // FIP_KEY_MENU, + 0x00FB28D7, // FIP_KEY_AB, + 0x00FB8877, // FIP_KEY_REPEAT, + + 0x00FB30CF, // FIP_KEY_UP, + 0x00FBF20D, // FIP_KEY_DOWN, + 0x00FB9867, // FIP_KEY_LEFT, + 0x00FB32CD, // FIP_KEY_RIGHT, + + 0x00FBC23D, // FIP_KEY_VOLUME_DOWN, + 0x00FB22DD, // FIP_KEY_VOLUME_UP, + 0x00FB629D, // FIP_KEY_PAUSE, + + 0x00FB926D, // FIP_KEY_REWIND, + 0x00FB6A95, // FIP_KEY_FORWARD, + 0x00FB38C7, // FIP_KEY_SKIP_PREV, + 0x00FB609F, // FIP_KEY_SKIP_NEXT, + + 0x00FBA857, // FIP_KEY_PLAY, + 0x00FBB04F, // FIP_KEY_STOP, + + 0x00FBE817, // FIP_KEY_SLOW, + 0x00FB728D, // FIP_KEY_AUDIO, + 0x00FBAA55, // FIP_KEY_VMODE, + 0x00FB906F, // FIP_KEY_MUTE, + 0x00FBC837, // FIP_KEY_ZOOM, + 0x00FFBABA, // FIP_KEY_PROGRAM, + 0x00FF728D, // FIP_KEY_PBC, + 0x00FB4AB5, // FIP_KEY_ANGLE, + + 0xffffffff +}; + +#ifdef __cplusplus +} +#endif + + +#endif // of SP_FIP_CODES_H diff --git a/src/libsp/MG35/sp_fip_codes-technosonic.h b/src/libsp/MG35/sp_fip_codes-technosonic.h new file mode 100644 index 0000000..85a49b2 --- /dev/null +++ b/src/libsp/MG35/sp_fip_codes-technosonic.h @@ -0,0 +1,119 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - FIP button internal codes header. + * For Technosonic-compatible players ('MP') + * \file sp_fip_codes-technosonic.h + * \author bombur + * \version 0.1 + * \date 4.05.2004 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#ifndef SP_FIP_CODES_H +#define SP_FIP_CODES_H + + +#ifdef __cplusplus +extern "C" { +#endif + +//////////////////////////////////// +/// Button codes definitions: + +static DWORD fip_code_offset[2] = { FIP_KEY_FRONT_EJECT, FIP_KEY_POWER }; + +/// 1) definitions for the buttons on the front panel: +static DWORD fip_panel_codes[] = +{ + 0x08000008, // FIP_KEY_FRONT_EJECT, + 0x00000080, // FIP_KEY_FRONT_PLAY, + 0x40000040, // FIP_KEY_FRONT_STOP, + 0x04000004, // FIP_KEY_FRONT_PAUSE, + 0x02000002, // FIP_KEY_FRONT_SKIP_PREV, + 0x20000020, // FIP_KEY_FRONT_SKIP_NEXT, + 0x10000010, // FIP_KEY_FRONT_REWIND, + 0x01000001, // FIP_KEY_FRONT_FORWARD, + + 0xffffffff +}; + +/// 2) definitions for the keys on the remote control: +static DWORD fip_remote_codes[] = +{ + 0x00FF30CF, // FIP_KEY_POWER, + 0x00FFB04F, // FIP_KEY_EJECT, + + 0x00FF00FF, // FIP_KEY_ONE, + 0x00FF807F, // FIP_KEY_TWO, + 0x00FF40BF, // FIP_KEY_THREE, + 0x00FFC03F, // FIP_KEY_FOUR, + 0x00FF20DF, // FIP_KEY_FIVE, + 0x00FFA05F, // FIP_KEY_SIX, + 0x00FF609F, // FIP_KEY_SEVEN, + 0x00FFE01F, // FIP_KEY_EIGHT, + 0x00FF10EF, // FIP_KEY_NINE, + 0x00FF906F, // FIP_KEY_ZERO, + + 0x00FF50AF, // FIP_KEY_CANCEL, + 0x00FFD02F, // FIP_KEY_SEARCH, + 0x00FF708F, // FIP_KEY_ENTER, + + 0x00FF7887, // FIP_KEY_OSD, + 0x00FFF807, // FIP_KEY_SUBTITLE, + 0x00FF38C7, // FIP_KEY_SETUP, + 0x00FFB847, // FIP_KEY_RETURN, + 0x00FF28D7, // FIP_KEY_TITLE, + 0x00FFA857, // FIP_KEY_PN, + 0x00FF6897, // FIP_KEY_MENU, + 0x00FFE817, // FIP_KEY_AB, + 0x00FF18E7, // FIP_KEY_REPEAT, + + 0x00FF08F7, // FIP_KEY_UP, + 0x00FF8877, // FIP_KEY_DOWN, + 0x00FF48B7, // FIP_KEY_LEFT, + 0x00FFC837, // FIP_KEY_RIGHT, + + 0x00FFD827, // FIP_KEY_VOLUME_DOWN, + 0x00FF58A7, // FIP_KEY_VOLUME_UP, + 0x00FF9867, // FIP_KEY_PAUSE, + + 0x00FF02FD, // FIP_KEY_REWIND, + 0x00FF827D, // FIP_KEY_FORWARD, + 0x00FF42BD, // FIP_KEY_SKIP_PREV, + 0x00FFC23D, // FIP_KEY_SKIP_NEXT, + + 0x00FF22DD, // FIP_KEY_PLAY, + 0x00FFA25D, // FIP_KEY_STOP, + + 0x00FF12ED, // FIP_KEY_SLOW, + 0x00FF926D, // FIP_KEY_AUDIO, + 0x00FF52AD, // FIP_KEY_VMODE, + 0x00FFD22D, // FIP_KEY_MUTE, + 0x00FF32CD, // FIP_KEY_ZOOM, + 0x00FFB24D, // FIP_KEY_PROGRAM, + 0x00FF728D, // FIP_KEY_PBC, + 0x00FFF20D, // FIP_KEY_ANGLE, + + 0xffffffff +}; + +#ifdef __cplusplus +} +#endif + + +#endif // of SP_FIP_CODES_H diff --git a/src/libsp/MG35/sp_fip_ioctl.h b/src/libsp/MG35/sp_fip_ioctl.h new file mode 100644 index 0000000..8e460e2 --- /dev/null +++ b/src/libsp/MG35/sp_fip_ioctl.h @@ -0,0 +1,58 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - Internal FIP IOCTL codes. + * For Technosonic-compatible players ('MP') + * \file sp_fip_ioctl.h + * \author bombur + * \version 0.1 + * \date 4.05.2004 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#ifndef SP_FIP_IOCTL_H +#define SP_FIP_IOCTL_H + +#ifdef __cplusplus +extern "C" { +#endif + +//////////////////////////////////// +/// Driver I/O defines: + + +/// Controls the display. +/// Used for setting the pulse width and turning the display on and off. +/// The flags you wish to set on the display where flags is one of the +/// FIP_PULSE_ defines. +#define FIP_DISPLAY_CONTROL 0x45000f + +/// Description: Displays the symbol on the display. +/// Sets given symbol data at given position: (data | (pos << 16)). +#define FIP_DISPLAY_SYMBOL 0x45000f + +/// Reads a button code (like getchar()). Can be blocked or not. +/// The button code is returned. +#define FIP_BUTTON_READ 0x450001 + +// #define FIP_??? 0x450004 + +#ifdef __cplusplus +} +#endif + + +#endif // of SP_FIP_IOCTL_H diff --git a/src/libsp/MG35/sp_flash.cpp b/src/libsp/MG35/sp_flash.cpp new file mode 100644 index 0000000..c6569eb --- /dev/null +++ b/src/libsp/MG35/sp_flash.cpp @@ -0,0 +1,174 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - FLASH-ROM interface functions source file. + * For Technosonic-compatible players ('MP') + * \file sp_flash.cpp + * \author bombur + * \version 0.1 + * \date 4.05.2004 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include + +#include +#include + +#include "sp_misc.h" +#include "sp_fip.h" +#include "sp_flash.h" + + +static int file_handle, mtd_handle; +static DWORD file_read_cnt, num_read, num_written; +static int flen; +static void *buf = NULL; + +/* +void flash_output_progress(int cnt, int total) +{ + char buf[10]; + cnt = cnt * 100 / total; + sprintf(buf, "%u", cnt); + fip_write_string(buf); +} +*/ + +int flash_end() +{ + if (buf != NULL) + SPSafeFree(buf); + close(file_handle); + close(mtd_handle); + return 0; +} + + +int flash_file(char *fname, DWORD address) +{ + if (buf != NULL) + { + flash_end(); + } + int addr = address; + DWORD length = 0x400000 - addr; + struct erase_info_user eiu; + int mtdpos; + + file_handle = open(fname, O_RDONLY); + if (file_handle == -1) + return -1; + flen = lseek(file_handle, 0, SEEK_END); + if (flen < (int)length) + length = flen; + lseek(file_handle, 0, SEEK_SET); + + mtd_handle = open("/dev/mtd/0", O_RDWR); + if (mtd_handle == -1) + { + mtd_handle = file_handle; + goto err0; + } + mtdpos = lseek(mtd_handle, addr, SEEK_SET); + if (mtdpos == -1) + goto err; + + buf = SPmalloc(0x1000); + if (buf == NULL) + goto err; + + if (addr < 0x4000) + { + length += addr; + addr = 0; + } else if (addr < 0x8000) + { + length += (DWORD)addr % 0x2000; + addr &= ~0x1fff; + } else if (addr < 0x10000) + { + length += (DWORD)addr - 0x8000; + addr = 0x8000; + } else + { + length += (DWORD)addr & 0xffff; + addr = (DWORD)addr / 0x10000 * 0x10000; + } + + if (addr + (int)length <= 0x4000) + { + length = 0x4000; + } else if (addr + (int)length <= 0x8000) + { + length = ((length - 1) / 0x2000) * 0x2000 + 0x1fff; + length = length - 1; + } else if (addr + (int)length <= 0x10000) + { + length = 0x10000 - addr; + } else + { + length = ((((DWORD)(addr+length)-1) / 0x10000+1 ) * 0x10000) -1; + length = length + 1 - (DWORD)addr; + } + + // erase FLASH memory + eiu.start = addr; + eiu.length = length; + if (ioctl(mtd_handle, MEMERASE, &eiu) != 0) + goto err; + + file_read_cnt = 0; + return 0; + +err: + if (buf != NULL) + SPfree(buf); + close(file_handle); +err0: + close(mtd_handle); + return -1; +} + +int flash_cycle() +{ + if (buf == NULL) + { + return -1; + } + num_read = read(file_handle, buf, 0x1000); + if (num_read <= 0) + { + flash_end(); + return 100; + } + num_written = write(mtd_handle, buf, num_read); + if (num_read != num_written) + goto err; + file_read_cnt += num_read; + + if ((int)file_read_cnt != flen) + goto err; + + return file_read_cnt * 100 / flen; +err: + flash_end(); + return -1; +} + diff --git a/src/libsp/MG35/sp_i2c.cpp b/src/libsp/MG35/sp_i2c.cpp new file mode 100644 index 0000000..30bad99 --- /dev/null +++ b/src/libsp/MG35/sp_i2c.cpp @@ -0,0 +1,93 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - I2C data transfer functions source file. + * For Technosonic-compatible players ('MP') + * \file sp_i2c.cpp + * \author bombur + * \version 0.1 + * \date 1.02.2005 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include + +#include "sp_misc.h" +#include "sp_io.h" +#include "sp_i2c.h" + +#include "arch-jasper/hardware.h" + + +BOOL i2c_data_in(BYTE addr, BYTE idx, BYTE *data, int num) +{ + outl(375, JASPER_I2C_MASTER_BASE+I2C_MASTER_CLK_DIV); + while ((inl(JASPER_I2C_MASTER_BASE+I2C_MASTER_STATUS) & 1) == 0) + ; + + for (int i = 0; i < num; i++) + { + outl(250, JASPER_I2C_MASTER_BASE+I2C_MASTER_CONFIG); + outl(0, JASPER_I2C_MASTER_BASE+I2C_MASTER_BYTE_COUNT); + outl(addr/2, JASPER_I2C_MASTER_BASE+I2C_MASTER_DEV_ADDR); + outl(idx++, JASPER_I2C_MASTER_BASE+I2C_MASTER_DATAOUT); + outl(4, JASPER_I2C_MASTER_BASE+I2C_MASTER_STARTXFER); + + while ((inl(JASPER_I2C_MASTER_BASE+I2C_MASTER_STATUS) & 2) == 0) + ; + while ((inl(JASPER_I2C_MASTER_BASE+I2C_MASTER_STATUS) & 1) == 0) + ; + + outl(250, JASPER_I2C_MASTER_BASE+I2C_MASTER_CONFIG); + outl(0, JASPER_I2C_MASTER_BASE+I2C_MASTER_BYTE_COUNT); + outl(addr/2, JASPER_I2C_MASTER_BASE+I2C_MASTER_DEV_ADDR); + outl(1, JASPER_I2C_MASTER_BASE+I2C_MASTER_STARTXFER); + + while ((inl(JASPER_I2C_MASTER_BASE+I2C_MASTER_STATUS) & 4) == 0) + ; + while ((inl(JASPER_I2C_MASTER_BASE+I2C_MASTER_STATUS) & 1) == 0) + ; + + *data++ = inl(JASPER_I2C_MASTER_BASE+I2C_MASTER_DATAIN); + } + return TRUE; +} + +BOOL i2c_data_out(BYTE addr, BYTE idx, BYTE *data, int num) +{ + outl(248, JASPER_I2C_MASTER_BASE+I2C_MASTER_CONFIG); + outl(375, JASPER_I2C_MASTER_BASE+I2C_MASTER_CLK_DIV); + outl(addr/2, JASPER_I2C_MASTER_BASE+I2C_MASTER_DEV_ADDR); + + while ((inl(JASPER_I2C_MASTER_BASE+I2C_MASTER_STATUS) & 1) == 0) + ; + + for (int i = 0; i < num; i++) + { + outl(idx++, JASPER_I2C_MASTER_BASE+I2C_MASTER_ADR); + outl(0, JASPER_I2C_MASTER_BASE+I2C_MASTER_BYTE_COUNT); + outl(*data++, JASPER_I2C_MASTER_BASE+I2C_MASTER_DATAOUT); + outl(0, JASPER_I2C_MASTER_BASE+I2C_MASTER_STARTXFER); + + while ((inl(JASPER_I2C_MASTER_BASE+I2C_MASTER_STATUS) & 2) == 0) + ; + } + return TRUE; +} + diff --git a/src/libsp/MG35/sp_khwl.cpp b/src/libsp/MG35/sp_khwl.cpp new file mode 100644 index 0000000..458dcf8 --- /dev/null +++ b/src/libsp/MG35/sp_khwl.cpp @@ -0,0 +1,351 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - KHWL interface functions source file. + * For Technosonic-compatible players ('MP') + * \file sp_khwl.cpp + * \author bombur + * \version 0.1 + * \date 4.07.2004 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include + +#include "sp_misc.h" +#include "sp_khwl.h" +#include "sp_khwl_ioctl.h" +#include "sp_mpeg.h" +#include "sp_fip.h" +#include "sp_io.h" +#include "sp_i2c.h" + +#include "arch-jasper/hardware.h" + + +/// Muxed media packets +MpegPlayStruct *MPEG_PLAY_STRUCT = (MpegPlayStruct *)(0x17c0000 + 0x3fc00 + 768); +/// Video packets +MpegPlayStruct *MPEG_VIDEO_STRUCT = (MpegPlayStruct *)(0x2000000 - 0x800000 - 232); +/// Audio packets +MpegPlayStruct *MPEG_AUDIO_STRUCT = (MpegPlayStruct *)(0x2000000 - 0x800000 - 208); +/// Subpicture packets +MpegPlayStruct *MPEG_SPU_STRUCT = (MpegPlayStruct *)(0x2000000 - 0x800000 - 184); + +/// Buffers storage +BYTE *BUF_BASE = (BYTE *)0x01680000; +//BYTE *BUF_BASE = (BYTE *)0x016c0000; + + +/// KHWL module handle +int khwl_handle = -1; + +/// OSD properties used +KHWL_OSDSTRUCT osd = { NULL }; + +/// insmod flag +static BOOL module_applied = FALSE; + +/// If digital TV (HDTV) is installed. +static BOOL digital_tv = FALSE; + +int FRAME_WIDTH = 720; +int FRAME_HEIGHT = 480; + + +BOOL khwl_init(BOOL applymodule) +{ + khwl_inityuv(); + khwl_deinit(); + + if (module_applied == TRUE || applymodule == FALSE || module_apply("/drivers/khwl.o")) // insert + { + module_applied = TRUE; + + khwl_handle = open("/dev/realmagichwl0", 0, 0); + if (khwl_handle != -1) + { + //khwl_reset(); + + if (!khwl_restoreparams()) + return FALSE; + + return TRUE; + } + } + return FALSE; +} + +BOOL khwl_restoreparams() +{ + int val = 0; + khwl_setproperty(KHWL_VIDEO_SET, evMacrovisionFlags, sizeof(int), &val); + + int flickerval = 15; // flicker = max + khwl_setproperty(KHWL_DECODER_SET, edecOsdFlicker, sizeof(int), &flickerval); + + KHWL_WINDOW wnd, osdwnd; + khwl_getproperty(KHWL_VIDEO_SET, evMaxDisplayWindow, sizeof(wnd), &wnd); + khwl_setproperty(KHWL_VIDEO_SET, evDestinationWindow, sizeof(wnd), &wnd); + + osdwnd = wnd; + // fix rather strange OSD scaling bug... + osdwnd.h = 30 * wnd.h / 31; + khwl_setproperty(KHWL_OSD_SET, eOsdDestinationWindow, sizeof(osdwnd), &osdwnd); + + val = 0; + khwl_setproperty(KHWL_VIDEO_SET, evForcedProgressiveAlways, sizeof(int), &val); + + KHWL_YUV_WRITE_PARAMS_TYPE yuvparams; + yuvparams.wWidth = wnd.w; + yuvparams.wHeight = wnd.h; + yuvparams.YUVFormat = KHWL_YUV_420_UNPACKED; + + khwl_setproperty(KHWL_VIDEO_SET, evYUVWriteParams, sizeof(yuvparams), &yuvparams); + + // do some HDTV-specific checks... + digital_tv = FALSE; + if ((inl(JASPER_QUASAR_BASE + QUASAR_DRAM_STARTUP1) & 0x2000) != 0) + { + BYTE data[4]; + // test for HDTV 1080I + i2c_data_in(112, 0, data, 4); + if (data[0] == 1 && data[1] == 0 && data[2] == 8 && data[3] == 0) + digital_tv = TRUE; + } + + return TRUE; +} + +BOOL khwl_deinit() +{ + if (khwl_handle == -1) + return FALSE; + close(khwl_handle); + khwl_handle = -1; + return TRUE; +} + +BOOL khwl_reset() +{ + if (khwl_handle == -1) + return FALSE; + return ioctl(khwl_handle, KHWL_HARDRESET, 0); +} + +int khwl_osd_switch(KHWL_OSDSTRUCT *_osd, BOOL autoupd) +{ + KHWL_WINDOW wnd; + int ret; + if (khwl_handle == -1) + return FALSE; + osd.flags = autoupd ? 1 : 0; + ret = ioctl(khwl_handle, KHWL_OSDFB_SWITCH, &osd); + + if (_osd != NULL) + { + khwl_getproperty(KHWL_VIDEO_SET, evMaxDisplayWindow, sizeof(wnd), &wnd); + khwl_setproperty(KHWL_OSD_SET, eOsdDestinationWindow, sizeof(wnd), &wnd); + *_osd = osd; + } + return ret; +} + +void khwl_get_osd_size(int *width, int *height) +{ + *width = 640;//osd.width; + *height = 480;//osd.height; +} + + +BOOL khwl_osd_update() +{ + ioctl(khwl_handle, KHWL_OSDFB_UPDATE, 0); + return TRUE; +} + +int khwl_osd_setalpha(int alpha) +{ + return ioctl(khwl_handle, KHWL_OSDFB_ALPHA, &alpha); +} + +void khwl_osd_setpalette(BYTE *pal, int entry, BYTE r, BYTE g, BYTE b, BYTE a) +{ + // assert(entry >= 0 && entry < 256); + BYTE y, u, v; + entry <<= 2; + khwl_vgargbtotvyuv(r, g, b, &y, &u, &v); + pal[entry++] = a; // alpha + pal[entry++] = y; + pal[entry++] = u; + pal[entry] = v; +} + +int khwl_displayYUV(KHWL_YUV_FRAME *f) +{ + int ret = ioctl(khwl_handle, KHWL_DISPLAY_YUV, f); + return ret; +} + +int khwl_setproperty(KHWL_PROPERTY_SET pset, int id, int size, void *value) +{ + KHWL_PROPERTY p; + p.pset = pset; + p.id = id; + p.size = size; + p.v = value; + return ioctl(khwl_handle, KHWL_SETPROP, &p); +} + +int khwl_getproperty(KHWL_PROPERTY_SET pset, int id, int size, void *value) +{ + KHWL_PROPERTY p; + p.pset = pset; + p.id = id; + p.size = size; + p.v = value; + return ioctl(khwl_handle, KHWL_GETPROP, &p); +} + +int khwl_audioswitch(BOOL ison) +{ + int val = ison ? 1 : 0; + return ioctl(khwl_handle, KHWL_AUDIOSWITCH, &val); +} + +int *khwl_get_samplerates() +{ + static int rates[2][12] = + { + // chip rev.A + { 16000, 22050, 24000, 32000, 44100, 48000, -1 }, + // chip rev.B supports more samplerates + { 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000, 64000, 88200, 96000, -1 }, + }; + return rates[inl(SYS_REVID_REG) & 1]; +} + +int khwl_play(int mode) +{ + return ioctl(khwl_handle, KHWL_PLAY, &mode); +} + +int khwl_stop() +{ + return ioctl(khwl_handle, KHWL_STOP, 0); +} + +int khwl_pause() +{ + return ioctl(khwl_handle, KHWL_PAUSE, 0); +} + +BOOL khwl_blockirq(BOOL block) +{ + unsigned irqstat; + irqstat = inl(JASPER_INT_CONTROLLER_BASE + INT_INTEN); + if (block) + irqstat &= ~Q2H_RISC_INT; + else + irqstat |= Q2H_RISC_INT; + outl(irqstat, JASPER_INT_CONTROLLER_BASE + INT_INTEN); + return TRUE; +} + +BOOL khwl_happeningwait(DWORD *mask) +{ + KHWL_WAITABLE w; + w.mask = *mask; + w.timeout_microsecond = 400000; + ioctl(khwl_handle, KHWL_WAIT, &w); + *mask = w.mask; + return TRUE; +} + +BOOL khwl_poll(WORD mask, DWORD timeout) +{ + pollfd pfd; + pfd.events = mask; + pfd.fd = khwl_handle; + return poll(&pfd, 1, timeout) >= 0; +} + +BOOL khwl_ideswitch(BOOL ison) +{ + KHWL_ADDR_DATA data; + // switch IDE? + data.Addr = 4; + data.Data = ison ? 1 : 0; + khwl_setproperty(KHWL_BOARDINFO_SET, ebiPIOAccess, sizeof(data), &data); + return TRUE; +} + +int khwl_getfrequency() +{ + int temp = inl(JASPER_QUASAR_BASE + QUASAR_DRAM_PLLCONTROL); + + int div = (temp >> 8) & 0xff; + int mul = (temp >> 2) & 63; + + int freq = (27 * (mul + 2)) / ((div + 2) * 2); + return freq; +} + +BOOL khwl_setfrequency(int freq) +{ + if (freq < 100 || freq > 202) + return FALSE; + + int temp = inl(JASPER_QUASAR_BASE + QUASAR_DRAM_PLLCONTROL); + temp = temp & 0xF000; + int div = (temp >> 8) & 0xff; + //int mul = (temp >> 2) & 63; + + int mul = (freq * ((10 * div+20)*20) / 270 - 20 /* + 5 */) / 10; + if (mul < 0 || mul > 63) + return FALSE; + + temp = temp | 0x02; + temp = temp | (div << 8); + temp = temp | (mul << 2); + + outl(temp & 0x7FFF, JASPER_QUASAR_BASE + QUASAR_DRAM_PLLCONTROL); + return TRUE; +} + +char *khwl_gethw() +{ + static char hw[256]; +/* + char b[128]; + b[0] = '\0'; + khwl_getproperty(KHWL_BOARDINFO_SET, ebiBoardNameString, 256, b); + + DWORD v_ebiDeviceId, v_ebiSubId, v_ebiBoardVersion, v_ebiHwLibVersion, v_ebiUcodeVersion; + khwl_getproperty(KHWL_BOARDINFO_SET, ebiDeviceId, sizeof(DWORD), &v_ebiDeviceId); + khwl_getproperty(KHWL_BOARDINFO_SET, ebiSubId, sizeof(DWORD), &v_ebiSubId); + khwl_getproperty(KHWL_BOARDINFO_SET, ebiBoardVersion, sizeof(DWORD), &v_ebiBoardVersion); + khwl_getproperty(KHWL_BOARDINFO_SET, ebiHwLibVersion, sizeof(DWORD), &v_ebiHwLibVersion); + khwl_getproperty(KHWL_BOARDINFO_SET, ebiUcodeVersion , sizeof(DWORD), &v_ebiUcodeVersion); +*/ + sprintf(hw, "%X.%c", inl(SYS_CHIPID_REG), inl(SYS_REVID_REG) + 'A'); + + return hw; +} diff --git a/src/libsp/MG35/sp_khwl_ioctl.h b/src/libsp/MG35/sp_khwl_ioctl.h new file mode 100644 index 0000000..b5b44e1 --- /dev/null +++ b/src/libsp/MG35/sp_khwl_ioctl.h @@ -0,0 +1,65 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - Internal KHWL driver's IOCTL codes. + * For Technosonic-compatible players ('MP') + * \file sp_khwl_ioctl.h + * \author bombur + * \version 0.1 + * \date 4.07.2004 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#ifndef SP_KHWL_IOCTL_H +#define SP_KHWL_IOCTL_H + +#ifdef __cplusplus +extern "C" { +#endif + +//////////////////////////////////// +/// Driver I/O defines: + +#define KHWL_IO_BASE 0x3cc +#define KHWL_IO_PLAYER 0x3d0 +#define KHWL_IO_PROP 0x3d4 +#define KHWL_IO_OSD 0x3e1 +#define KHWL_IO_DISPLAY 0x3e6 + + +#define KHWL_HARDRESET KHWL_IO_BASE+3 + +#define KHWL_PLAY KHWL_IO_PLAYER +#define KHWL_STOP KHWL_IO_PLAYER+1 +#define KHWL_PAUSE KHWL_IO_PLAYER+2 +#define KHWL_AUDIOSWITCH KHWL_IO_PLAYER+3 + +#define KHWL_WAIT KHWL_IO_PROP+1 +#define KHWL_SETPROP KHWL_IO_PROP+2 +#define KHWL_GETPROP KHWL_IO_PROP+3 + +#define KHWL_OSDFB_SWITCH KHWL_IO_OSD +#define KHWL_OSDFB_UPDATE KHWL_IO_OSD+1 +#define KHWL_OSDFB_ALPHA KHWL_IO_OSD+2 // general alpha apply + +#define KHWL_DISPLAY_CLEAR KHWL_IO_DISPLAY +#define KHWL_DISPLAY_YUV KHWL_IO_DISPLAY+1 + +#ifdef __cplusplus +} +#endif + +#endif // of SP_HWL_IOCTL_H diff --git a/src/libsp/MG35/sp_module.cpp b/src/libsp/MG35/sp_module.cpp new file mode 100644 index 0000000..c18db37 --- /dev/null +++ b/src/libsp/MG35/sp_module.cpp @@ -0,0 +1,198 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - Module system support source file. + * For Technosonic-compatible players ('MP') + * \file sp_module.cpp + * \author bombur + * \version 0.1 + * \date 10.12.2008 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +extern "C" +{ + +// portions of code from uClibc... +#ifdef COMPILE_MODULE + +void (*__app_fini)(void) = NULL; + +extern int main(int argc, char **argv, char **envp); + + +void __attribute__ ((__noreturn__)) +__uClibc_start_main(int argc, char **argv, char **envp, + void (*app_init)(void), void (*app_fini)(void)) +{ + /* Arrange for the application's dtors to run before we exit. */ + __app_fini = app_fini; + + /* Run all the application's ctors now. */ + if (app_init!=NULL) { + app_init(); + } + + exit(main(argc, argv, envp)); +} + +void exit(int rv) +{ + if (__app_fini != NULL) + (__app_fini)(); + + _exit(rv); +} + +void module_wait() +{ + kill(getpid(), SIGSTOP); +} + +/* Copy SRC to DEST. */ +char *strcpy (char *dest, const char *src) +{ + char c; + char *s = (char *) (src); + const int off = (dest) - s - 1; + + do + { + c = *s++; + s[off] = c; + } + while (c != '\0'); + + return dest; +} + +void abort(void) +{ + struct sigaction act; + sigset_t sset; + + (void) fflush (NULL); + + if ((sigemptyset (&sset) == 0) && (sigaddset (&sset, SIGABRT) == 0)) { + (void) sigprocmask (SIG_UNBLOCK, &sset, (sigset_t *) NULL); + } + + raise(SIGABRT); + + memset (&act, '\0', sizeof (struct sigaction)); + act.sa_handler = SIG_DFL; + sigfillset (&act.sa_mask); + act.sa_flags = 0; + sigaction (SIGABRT, &act, NULL); + + raise (SIGABRT); + + _exit (127); +} + +int atexit(void (*) (void)) +{ + // empty + return 0; +} + + +#undef LOAD_ARGS_1 +#define LOAD_ARGS_1(a1) \ + _a1 = (int) (a1); +#undef ASM_ARGS_1 +#define ASM_ARGS_1 , "r" (_a1) +#ifndef SYS_ify +# define SYS_ify(syscall_name) (__NR_##syscall_name) +#endif + +#undef INLINE_SYSCALL +#define INLINE_SYSCALL(name, nr, args...) \ + ({ unsigned int _sys_result; \ + { \ + register int _a1 asm ("a1"); \ + LOAD_ARGS_##nr (args) \ + asm volatile ("swi %1 @ syscall " #name \ + : "=r" (_a1) \ + : "i" (SYS_ify(name)) ASM_ARGS_##nr \ + : "memory"); \ + _sys_result = _a1; \ + } \ + (int) _sys_result; }) + + +void _exit(int status) +{ + INLINE_SYSCALL(exit, 1, status); +} + + +#else ///////////////////// server side + +int module_binary_load(char *fname, char *arg) +{ + char *args[3]; + args[0] = fname; + args[1] = arg; + args[2] = NULL; + + return exec_file(args[0], (const char **)args); +} + +int module_binary_unload(int pid) +{ + // kill it by force + kill(pid, SIGKILL); + int pstat; + waitpid(pid, &pstat, 0); + return 0; +} + +void module_copy_func(void *from, void *to) +{ + #define JMP_OPCODE_SIZE 2 + DWORD diff = (((DWORD)from)/4 - (((DWORD)to)/4 + JMP_OPCODE_SIZE)); + *((DWORD *)to) = 0xEA000000 | (diff & 0xFFFFFF); +} + +void module_clear_func(void *func) +{ + *((DWORD *)func) = 0; +} + + +#endif + +} diff --git a/src/libsp/MG35/sp_module_crt0.S b/src/libsp/MG35/sp_module_crt0.S new file mode 100644 index 0000000..7478630 --- /dev/null +++ b/src/libsp/MG35/sp_module_crt0.S @@ -0,0 +1,125 @@ +/* When we enter this piece of code, the program stack looks like this: + argc argument counter (integer) + argv[0] program name (pointer) + argv[1...N] program args (pointers) + argv[argc-1] end of args (integer) + NULL + env[0...N] environment variables (pointers) + NULL + + For uClinux it looks like this: + + argc argument counter (integer) + argv char *argv[] + envp char *envp[] + argv[0] program name (pointer) + argv[1...N] program args (pointers) + argv[argc-1] end of args (integer) + NULL + env[0...N] environment variables (pointers) + NULL + + When we are done here, we want + a1=argc + a2=argv[0] + a3=argv[argc+1] + +ARM register quick reference: + + Name Number ARM Procedure Calling Standard Role + + a1 r0 argument 1 / integer result / scratch register / argc + a2 r1 argument 2 / scratch register / argv + a3 r2 argument 3 / scratch register / envp + a4 r3 argument 4 / scratch register + v1 r4 register variable + v2 r5 register variable + v3 r6 register variable + v4 r7 register variable + v5 r8 register variable + sb/v6 r9 static base / register variable + sl/v7 r10 stack limit / stack chunk handle / reg. variable + fp r11 frame pointer + ip r12 scratch register / new-sb in inter-link-unit calls + sp r13 lower end of current stack frame + lr r14 link address / scratch register + pc r15 program counter +*/ + +#include + +.text + .global _start + .type _start,%function +#if ! defined __UCLIBC_CTOR_DTOR__ + .type __uClibc_main,%function +#else + .weak _init + .weak _fini + .type __uClibc_start_main,%function +#endif +/* Stick in a dummy reference to main(), so that if an application + * is linking when the main() function is in a static library (.a) + * we can be sure that main() actually gets linked in */ + .type main,%function + + +.text +_start: + /* clear the frame pointer */ + mov fp, #0 + +#ifdef __UCLIBC_HAS_MMU__ + /* Load register r0 (argc) from the stack to its final resting place */ + ldr r0, [sp], #4 + + /* Copy argv pointer into r1 -- which its final resting place */ + mov r1, sp + + /* Skip to the end of argv and put a pointer to whatever + we find there (hopefully the environment) in r2 */ + add r2, r1, r0, lsl #2 + add r2, r2, #4 + +#else + /* + * uClinux stacks look a little different from normal + * MMU-full Linux stacks (for no good reason) + */ + /* pull argc, argv and envp off the stack */ + ldr r0,[sp, #0] + ldr r1,[sp, #4] + ldr r2,[sp, #8] +#endif + +#if defined __UCLIBC_CTOR_DTOR__ + /* Store the address of _init in r3 as an argument to main() */ + ldr r3, =_init + + /* Push _fini onto the stack as the final argument to main() */ + ldr r4, =_fini + stmfd sp!, {r4} + + /* Ok, now run uClibc's main() -- shouldn't return */ + bl __uClibc_start_main +#else + bl __uClibc_main +#endif + + /* Crash if somehow `exit' returns anyways. */ + bl abort + +/* We need this stuff to make gdb behave itself, otherwise + gdb will chokes with SIGILL when trying to debug apps. +*/ + .section ".note.ABI-tag", "a" + .align 4 + .long 1f - 0f + .long 3f - 2f + .long 1 +0: .asciz "GNU" +1: .align 4 +2: .long 0 + .long 2,0,0 +3: .align 4 + diff --git a/src/libsp/MP/arch-jasper/hardware.h b/src/libsp/MP/arch-jasper/hardware.h new file mode 100644 index 0000000..c8cdec0 --- /dev/null +++ b/src/libsp/MP/arch-jasper/hardware.h @@ -0,0 +1,798 @@ +/* + * linux/include/asm-arm/arch-jasper/hardware.h + * [bombur]: this is a part of uClinux (GPL) headers. + * for JASPER + * Created 12/12/2001 Fabrice Gautier + * Copyright 2001, Sigma Desings, Inc + */ + +#ifndef __ASM_ARCH_HARDWARE_H +#define __ASM_ARCH_HARDWARE_H + +#define IO_ADDRESS(x) (x) + +// PLL input clock, typically 27 Mhz +#define JASPER_EXT_CLOCK 27000000 + +/* 0=TC0, 1=TC1, 2=TC2 */ + +//------------------------------------------------------ +// SYSTEM CONTROLLER 0x0050_0000 +//------------------------------------------------------ +#define JASPER_SYSCTRL_BASE 0x00500000 + +#define SYSCTRL_CHIP_ID 0x00000000 +#define SYSCTRL_REVISION_ID 0x00000008 +#define SYSCTRL_CPUCFG 0x0000000C +#define SYSCTRL_TESTSTAT 0x00000010 +#define SYSCTRL_ERRSTAT 0x00000014 +#define SYSCTRL_BADADDR 0x00000018 +#define SYSCTRL_RSTCTL 0x00000020 +#define SYSCTRL_CPUTIMESLOT 0x00000024 + +//------------------------------------------------------ +// TIMER 0 AND 1 0x0050_0100 +//-------------------------TIMER_----------------------- +#define JASPER_TIMER_BASE 0x00500100 + +#define TIMER_TMRSTAT 0x00000000 +#define TIMER_TMR0LOAD 0x00000010 +#define TIMER_TMR0VAL 0x00000014 +#define TIMER_TMR0CTL 0x00000018 +#define TIMER_TMR1LOAD 0x00000020 +#define TIMER_TMR1VAL 0x00000024 +#define TIMER_TMR1CTL 0x00000028 + +//------------------------------------------------------ +// INT CONTROLLER 0x0050_0200 +//------------------------------------------------------ +#define JASPER_INT_CONTROLLER_BASE 0x00500200 + +#define INT_IRQSTAT 0x00000000 +#define INT_FIQSTAT 0x00000004 +#define INT_INTTYPE 0x00000010 +#define INT_INTPOLL 0x00000020 +#define INT_INTEN 0x00000024 + +//------------------------------------------------------ +// MAC Registers 0x00500300 +//------------------------------------------------------ +#define JASPER_MAC_BASE 0x00500300 + +#define MAC_REFTIMER 0x00000000 +#define MAC_FLASH_CFG 0x00000014 +#define MAC_FLASH_ST 0x0000001c +#define MAC_SDRAMCTL 0x00000020 +#define MAC_SDRAMCFG 0x00000024 +#define MAC_SDRAMDATA 0x00000028 +#define MAC_SDRAMST 0x0000002c +#define MAC_ARBITERCTRL 0x00000090 +#define MAC_ARBITERSTATE 0x00000094 +#define MAC_WATCHDOG_CTRL 0x000000A0 +#define MAC_WATCHDOG_TMO0 0x000000A4 +#define MAC_WATCHDOG_TMO1 0x000000A8 +#define MAC_WATCHDOG_INT 0x000000Ac +#define MAC_TIMESLOTCNT 0x000000B0 + + +//------------------------------------------------------ +// UART REGISTERs +// UART0 0050_0500 +// UART1 0050_1300 +//------------------------------------------------------ +#define JASPER_UART0_BASE 0x00500500 +#define JASPER_UART1_BASE 0x00501300 +#define UART_NR 2 + +#define UART_RBR 0x00 +#define UART_TBR 0x04 +#define UART_IER 0x08 +#define UART_IIR 0x0C +#define UART_FCR 0x10 +#define UART_LCR 0x14 +#define UART_MCR 0x18 +#define UART_LSR 0x1C +#define UART_MSR 0x20 +#define UART_SCRATCH 0x24 +#define UART_CLKDIV 0x28 +#define UART_CLKSEL 0x2C + +//------------------------------------------------------ +// PIO0 block 0x0050_0600 +// PIO1 block 0x0050_0A00 +//------------------------------------------------------ +#define JASPER_PIO0_BASE 0x00500600 +#define JASPER_PIO1_BASE 0x00500A00 + +#define PIO_INT_STATUS 0x00000000 +#define PIO_DATA 0x00000004 +#define PIO_DIR 0x00000008 +#define PIO_POL 0x0000000C +#define PIO_INT_ENABLE 0x00000010 + + +//------------------------------------------------------ +// I2C MASTER REGISTERs 0X0050_0800 +//------------------------------------------------------ +#define JASPER_I2C_MASTER_BASE 0x00500800 + +#define I2C_MASTER_CONFIG 0x00 +#define I2C_MASTER_CLK_DIV 0x04 +#define I2C_MASTER_DEV_ADDR 0x08 +#define I2C_MASTER_ADR 0x0C +#define I2C_MASTER_DATAOUT 0x10 +#define I2C_MASTER_DATAIN 0x14 +#define I2C_MASTER_STATUS 0x18 +#define I2C_MASTER_STARTXFER 0x1C +#define I2C_MASTER_BYTE_COUNT 0x20 +#define I2C_MASTER_INTEN 0x24 +#define I2C_MASTER_INT 0x28 + + +//------------------------------------------------------ +// I2C SLAVE REGISTERs 0X0050_0900 +//------------------------------------------------------ +#define JASPER_I2C_SLAVE_BASE 0x00500900 + +#define I2C_SLAVE_ADDR 0x00 +#define I2C_SLAVE_DATAOUT 0x04 +#define I2C_SLAVE_DATAIN 0x08 +#define I2C_SLAVE_STATUS 0x0C +#define I2C_SLAVE_INTEN 0x10 +#define I2C_SLAVE_INT 0x14 +#define I2C_SLAVE_BUS_HOLD 0x18 + +//------------------------------------------------------ +// IDE_REGISTERs 0x0050_0B00 +//------------------------------------------------------ +#define JASPER_IDE_BASE 0x00500B00 + +#define JASPER_IDE_DMA_BASE 0x00500E00 + + // **** DMA CHANNEL REG **** +#define IDE_BMIC 0x00 // ( 8 BIT) BMIC IDE COMMAND REG +#define IDE_BMIS 0x04 // ( 8 BIT) BMIC IDE STATUS REG +#define IDE_BMIDTP 0x08 // (32 BIT) BUSMASTER IDE DESCRIPTOR TABLE POINTER REG +#define IDE_TIM 0x40 // (16 BIT) IDE TIMING Reg +#define IDE_SIDETIM 0x48 // ( 8 BIT) SLAVE IDE TIMING Reg +#define IDE_SRC 0x4C // (16 BIT) SLEW RATE CTRL Reg (45h-46h) +#define IDE_STATUS 0x50 // ( 8 BIT) IDESTATUS +#define IDE_UDMACTL 0x54 // ( 8 BIT) ULTRA DMA CONTORL Reg +#define IDE_UDMATIM 0x58 // (16 BIT) ULTRA DMA TIMING Reg (4A - 4B) +#define IDE_PRI_DEVICE_CONTROL 0xE6 // (16 BIT) Device 0: +#define IDE_PRI_DATA 0xF0 // (16 BIT) Device 0: +#define IDE_PRI_SECTOR_COUNT 0xF2 // (16 BIT) Device 0: +#define IDE_PRI_DEVICE_HEAD 0xF6 // (16 BIT) Device 0: +#define IDE_PRI_CMD 0xF7 // (16 BIT) Device 0: + + +//------------------------------------------------------ +// DVD-LOADER_REGISTERs 0x0050_0C00 +//------------------------------------------------------ +#define JASPER_DVD_BASE 0x00500C00 +#define DVD_AV_CTRL 0x00 // (16 BIT) AUDIO/VIDEO PART FROM HOST +#define DVD_AV_SEC_CNT 0x04 // +#define DVD_AV_BYTE_CNT 0x08 // +#define DVD_AV_INTMSK 0x0C // +#define DVD_AV_INT 0x10 // +#define DVD_FIFO_LIM 0x14 // +#define DVD_TIMOUT_LIM 0x18 // +#define DVD_AV_X2C 0x1C // +#define DVD_HOST_CTRL 0x20 // +#define DVD_HOST_SCLK 0x24 // +#define DVD_HOST_TXREG 0x28 // +#define DVD_HOST_RXREG 0x2C // + +//------------------------------------------------------ +// FIP REGISTERs 0x0050_0D00 +//------------------------------------------------------ + +#define JASPER_FIP_BASE 0x00500D00 + +#define FIP_COMMAND 0x00 +#define FIP_DISPLAY_DATA 0x04 +#define FIP_LED_DATA 0x08 +#define FIP_KEY_DATA1 0x0C +#define FIP_KEY_DATA2 0x10 +#define FIP_SWITCH_DATA 0x14 +#define FIP_CLK_DIV 0x20 +#define FIP_TRISTATE_MODE 0x24 + +//------------------------------------------------------ +// DVD-DMA REGISTERs 0x0050_0F00 +//------------------------------------------------------ +#define JASPER_DVD_DMA_BASE 0x00500F00 +#define DVD_DMACTL 0x00 // +#define DVD_DMAMSK 0x04 // +#define DVD_DMAINT 0x08 // +#define DVD_DMARAW 0x0C // +#define DVD_RXADDR 0x10 // +#define DVD_RXBYTES 0x14 // + +//------------------------------------------------------ +// RTC REGISTER 0x0050_1400 +//------------------------------------------------------ +#define JASPER_RTC_BASE 0x00501400 +#define RTC_CTRL 0x00000000 +#define RTC_LOAD1 0x00000004 +#define RTC_LOAD2 0x00000008 +#define RTC_ALARM 0x0000000C +#define RTC_INTEN 0x00000010 +#define RTC_INT0 0x00000014 +#define RTC_COUNT1 0x00000018 +#define RTC_COUNT2 0x0000001C + +//------------------------------------------------------ +// HOST/QUASAR SLAVE REGISTERS 0x0050_1500 +//------------------------------------------------------ +#define JASPER_HOST_SLAVE_QUASAR_BASE 0x00501500 + +#define HOST_SLAVE_WR_QUASAR_BYTE 0x00000000 +#define HOST_SLAVE_WR_QUASAR_1BYTE 0x00000000 +#define HOST_SLAVE_WR_QUASAR_2BYTE 0x00000004 +#define HOST_SLAVE_WR_QUASAR_3BYTE 0x00000008 +#define HOST_SLAVE_WR_QUASAR_4BYTE 0x0000000C + +#define HOST_SLAVE_RD_QUASAR_BYTE 0x00000000 +#define HOST_SLAVE_RD_QUASAR_1BYTE 0x00000000 +#define HOST_SLAVE_RD_QUASAR_2BYTE 0x00000004 +#define HOST_SLAVE_RD_QUASAR_3BYTE 0x00000008 +#define HOST_SLAVE_RD_QUASAR_4BYTE 0x0000000C + +//------------------------------------------------------ +// I2S REGISTERs 0x0050_1600 +//------------------------------------------------------ +#define JASPER_I2S_BASE 0x00501600 + +#define I2S_CTRL 0x0 +#define I2S_PROG_LEN 0x4 +#define I2S_STATUS 0x8 +#define I2S_FRAME_CNTR 0xC + +#define I2S_RESET 0x2 +#define I2S_ENABLE 0x1 +#define I2S_MASTER_MODE 0x4 +#define I2S_SCIN_DIV00 0x00 +#define I2S_SCIN_DIV01 0x08 +#define I2S_SCIN_DIV10 0x10 +#define I2S_SCIN_DIV11 0x18 + +//------------------------------------------------------ +// SPI/I2S_DMA REGISTERs 0x0050_1700 +//------------------------------------------------------ +#define JASPER_SPI_I2S_DMA_BASE 0x00501700 + +#define SPI_I2S_DMA_CTRL_REG 0x0 +#define SPI_I2S_DMA_BASE_ADD_REG 0x4 +#define SPI_I2S_DMA_SIZE_REG 0x8 +#define SPI_I2S_DMA_WR_PTR_REG 0xC +#define SPI_I2S_DMA_RD_PTR_REG 0x10 +#define SPI_I2S_DMA_TRSH_REG 0x14 +#define SPI_I2S_DMA_INT_EN_REG 0x18 +#define SPI_I2S_DMA_INT_REG 0x1C +#define SPI_I2S_DMA_INT_POLL_REG 0x20 +#define SPI_I2S_DMA_INTER_FIFO_REG 0x24 +#define SPI_I2S_DMA_CNT_REG 0x28 + +//------------------------------------------------------ +// SPI REGISTERs 0x0050_1800 +//------------------------------------------------------ +#define JASPER_SPI_BASE 0x00501800 + +#define SPI_CNTR 0x0 +#define SPI_REC_COUNTER 0x4 +#define SPI_INT_EN 0x8 +#define SPI_INT_STATUS 0xC +#define SPI_ERROR_CNT 0x10 + + +//------------------------------------------------------ +// QUASAR PM/DM AREA +//------------------------------------------------------ +#define JASPER_QUASAR_BASE 0x00600000 + +#define QUASAR_MAP_AREA 0x00600000 +#define QUASAR_PM_START 0x00600000 +#define QUASAR_PM_SIZE 0x00002000 +#define QUASAR_DM_START 0x00604000 +#define QUASAR_DM_SIZE 0x00002000 + +//------------------------------------------------------ +// QUASAR DRAM CONTROLLER +//------------------------------------------------------ + +#define QUASAR_DRAM_CFG 0x00007000 +#define QUASAR_DRAM_FIFOSIZE0 0x00007004 +#define QUASAR_DRAM_FIFOSIZE1 0x00007008 +#define QUASAR_DRAM_CASDELAY 0x0000700C +#define QUASAR_DRAM_PLLCONTROL 0x00007010 +#define QUASAR_DRAM_TK0 0x00007014 +#define QUASAR_DRAM_TK1 0x00007018 +#define QUASAR_DRAM_TK2 0x0000701C +#define QUASAR_DRAM_STARTUP0 0x00007020 +#define QUASAR_DRAM_STARTUP1 0x00007024 +#define QUASAR_DRAM_AC3_BASE 0x00007028 +#define QUASAR_DRAM_FIFOSIZE2 0x0000702C +#define QUASAR_DRAM_PORTMUX 0x00007030 + +//------------------------------------------------------ +// QUASAR <-> HOST INTERFACE ( DMA ENGINES ) +//------------------------------------------------------ + +#define QUASAR_H2Q_READ_ADDRESS_LO 0x00007F80 //0xFE0 +#define QUASAR_H2Q_READ_ADDRESS_HI 0x00007F84 //0xFE1 +#define QUASAR_H2Q_READ_CONTER 0x00007F88 //0xFE2 +#define QUASAR_H2Q_READ_MASTER_ENABLE 0x00007F8C //0xFE3 +#define QUASAR_H2Q_INT_MASK 0x00007F90 //0xFE4 +#define QUASAR_H2Q_INT 0x00007F94 //0xFE5 +#define QUASAR_H2Q_INT_STATUS 0x00007F98 //0xFE6 + +#define QUASAR_Q2H_WRITE_ADDRESS_LO 0x00007FA0 //0xFE8 +#define QUASAR_Q2H_WRITE_ADDRESS_HI 0x00007FA4 //0xFE9 +#define QUASAR_Q2H_WRITE_CONTER 0x00007FA8 //0xFEA +#define QUASAR_Q2H_WRITE_MASTER_ENABLE 0x00007FAC //0xFEB +#define QUASAR_Q2H_INT_MASK 0x00007FB0 //0xFEC +#define QUASAR_Q2H_INT 0x00007FB4 //0xFED +#define QUASAR_Q2H_INT_STATUS 0x00007FB8 //0xFEE + +#define QUASAR_OSD_SOURCE_ADDRESS_LO 0x00007980 //0xE60 +#define QUASAR_OSD_SOURCE_ADDRESS_HI 0x00007984 //0xE61 +#define QUASAR_OSD_SOURCE_COUNTER 0x00007988 //0xE62 +#define QUASAR_OSD_SOURCE_MUX_ENABLE 0x0000798C //0xE63 +#define QUASAR_OSD_INT_MASK 0x00007990 //0xE64 +#define QUASAR_OSD_INT 0x00007994 //0xE65 +#define QUASAR_OSD_INT_STATUS 0x00007998 //0xE66 + +//------------------------------------------------------ +// QUASAR Local Bus Controller (LBC) +//------------------------------------------------------ + +#define QUASAR_LBC_CONFIG0 0x00007900 //0x1E40 +#define QUASAR_LBC_CONFIG1 0x00007904 //0x1E41 +#define QUASAR_LBC_WRITE_FIFO0_ACCESS 0x00007908 //0x1E42 +#define QUASAR_LBC_WRITE_FIFO0_CNT 0x0000790C //0x1E43 +#define QUASAR_LBC_READ_FIFO0_ACCESS 0x00007910 //0x1E44 +#define QUASAR_LBC_READ_FIFO0_CNT 0x00007914 //0x1E45 +#define QUASAR_LBC_READ_FIFO1_ACCESS 0x00007918 //0x1E46 +#define QUASAR_LBC_READ_FIFO1_CNT 0x0000791C //0x1E47 +#define QUASAR_LBC_WRITE_ADDR 0x00007920 //0x1E48 +#define QUASAR_LBC_WRITE_DATA 0x00007924 //0x1E49 +#define QUASAR_LBC_READ_ADDR 0x00007928 //0x1E4a +#define QUASAR_LBC_READ_DATA 0x0000792C //0x1E4b +#define QUASAR_LBC_BURST_XFER_CTRL 0x00007930 //0x1E4c +#define QUASAR_LBC_STATUS 0x00007934 //0x1E4d +#define QUASAR_LBC_INTERRUPT 0x00007938 //0x1E4e +#define QUASAR_LBC_PGIO 0x0000793C //0x1E4f + +//------------------------------------------------------ +// PIO COMMAND DEFINITIONS +//------------------------------------------------------ +// IN/OUTPUT PIO direction +#define PIO_OUTPUT_BIT0 0x00010001 +#define PIO_OUTPUT_BIT1 0x00020002 +#define PIO_OUTPUT_BIT2 0x00040004 +#define PIO_OUTPUT_BIT3 0x00080008 +#define PIO_OUTPUT_BIT4 0x00100010 +#define PIO_OUTPUT_BIT5 0x00200020 +#define PIO_OUTPUT_BIT6 0x00400040 +#define PIO_OUTPUT_BIT7 0x00800080 +#define PIO_OUTOUT_ALL16BITS 0xFFFFFFFF +// Set DATA to the PIO OUTPUT pin +#define PIO_DATA_BIT0 0x00010001 +#define PIO_DATA_BIT1 0x00020002 +#define PIO_DATA_BIT2 0x00040004 +#define PIO_DATA_BIT3 0x00080008 +#define PIO_DATA_BIT4 0x00100010 +#define PIO_DATA_BIT5 0x00200020 +#define PIO_DATA_BIT6 0x00400040 +#define PIO_DATA_BIT7 0x00800080 + +#define PIO_EN_SET_BIT7 0x00800000 +#define PIO_EN_SET_BIT6 0x00400000 +#define PIO_EN_SET_BIT5 0x00200000 +#define PIO_EN_SET_BIT4 0x00100000 +#define PIO_EN_SET_BIT3 0x00080000 +#define PIO_EN_SET_BIT2 0x00040000 +#define PIO_EN_SET_BIT1 0x00020000 +#define PIO_EN_SET_BIT0 0x00010000 +#define PIO_EN_ALL16_BITS 0xFFFF0000 +#define PIO_EN_BITS15to8 0xFF000000 +#define PIO_EN_BITS7to0 0x00FF0000 + +//------------------------------------------------------ +// INT CTRL COMMAND DEFINITIONS +//------------------------------------------------------ +#define ENABLE_TIMER0_INT 0x00000001 +#define ENABLE_TIMER1_INT 0x00000002 +#define ENABLE_PIO0_INT 0x00000020 +#define ENABLE_PIO1_INT 0x00000040 + +#define GLOBAL_INT_ENABLE 0x80000000 + +#define MAX_INT 0x00800000 +#define Q2H_LOC_INT 0x00400000 +#define Q2H_RISC_INT 0x00200000 +#define SPI_INT 0x00100000 +#define SPI_I2S_DMA_INT 0x00080000 +#define I2S_INT 0x00040000 +#define RTC_INT 0x00020000 +#define Q2P_INT 0x00010000 +#define P2Q_DMA_INT 0x00008000 +#define OSD_DMA_INT 0x00004000 +#define FIP_INT 0x00002000 +#define IDE_DMA_INT 0x00001000 +#define IDE_INT 0x00000800 +#define DVD_DMA_INT 0x00000400 +#define DVD_INT 0x00000200 +#define I2CS_INT 0x00000100 +#define I2CM_INT 0x00000080 +#define PIO1_INT 0x00000040 +#define PIO0_INT 0x00000020 +#define UART1_INT 0x00000008 +#define UART0_INT 0x00000004 +#define WDTIMER_INT 0x00000002 +#define TIMER1_INT 0x00000002 +#define TIMER0_INT 0x00000001 + +#define JASPER_SC_VALID_INT 0x007FFFEF + +//------------------------------------------------------ +// INT CTRL COMMAND DEFINITIONS II for J_IRQCTRL.c +//------------------------------------------------------ +#define ENABLE_INT_GLOBAL 0x80000000 //INT_ENABLE command + +#define ENABLE_INT_TIMER0 0x80000001 //INT_ENABLE command +#define ENABLE_INT_WDTIMER 0x80000002 //INT_ENABLE command +#define ENABLE_INT_UART_1 0x80000004 //INT_ENABLE command +#define ENABLE_INT_UART_2 0x80000008 //INT_ENABLE command + +#define ENABLE_INT_PIO 0x80000020 //INT_ENABLE command +#define ENABLE_INT_PIO1 0x80000040 //INT_ENABLE command +#define ENABLE_INT_I2C_MASTER 0x80000080 //INT_ENABLE command + +#define ENABLE_INT_I2C_SLAVE 0x80000100 //INT_ENABLE command +#define ENABLE_INT_DVD 0x80000200 //INT_ENABLE command +#define ENABLE_INT_DVD_DMA 0x80000400 //INT_ENABLE command +#define ENABLE_INT_IDE 0x80000800 //INT_ENABLE command + +#define ENABLE_INT_IDE_DMA 0x80001000 //INT_ENABLE command +#define ENABLE_INT_FIP 0x80002000 //INT_ENABLE command +#define ENABLE_INT_OSD_DMA 0x80004000 //INT_ENABLE command +#define ENABLE_INT_H2Q_DMA 0x80008000 //INT_ENABLE command + +#define ENABLE_INT_Q2H_DMA 0x80010000 //INT_ENABLE command +#define ENABLE_INT_RTC 0x80020000 //INT_ENABLE command +#define ENABLE_INT_I2S 0x80040000 //INT_ENABLE command +#define ENABLE_INT_I2S_DMA 0x80080000 //INT_ENABLE command + +#define ENABLE_INT_SPI 0x80100000 //INT_ENABLE command +#define ENABLE_INT_Q2H_RISC 0x80200000 //INT_ENABLE command +#define ENABLE_INT_Q2H_LOC 0x80400000 //INT_ENABLE command + +#define DISABLE_INT_GLOBAL 0x7FFFFFFF //INT_ENABLE command +#define DISABLE_INT_UART_1 0xFFFFFFFB //INT_ENABLE command +#define DISABLE_INT_UART_2 0xFFFFFFF7 //INT_ENABLE command +#define DISABLE_INT_ALL 0x10000000 //INT_ENABLE command + +//------------------------------------------------------ +//SYSTEM CTRL COMMAND DEFINITIONS +//------------------------------------------------------ +#define SYSCTRL_CMD_RESET_TIMER 0x00000001 // Bit 0 +#define SYSCTRL_CMD_RESET_INTCTRL 0x00000002 // Bit 1 +#define SYSCTRL_CMD_RESET_UART0 0x00000004 // Bit 2 +#define SYSCTRL_CMD_RESET_UART1 0x00000008 // Bit 3 +#define SYSCTRL_CMD_RESET_MAC 0x00000010 // Bit 4 +#define SYSCTRL_CMD_RESET_PIO0 0x00000020 // Bit 5 +#define SYSCTRL_CMD_RESET_PIO1 0x00000040 // Bit 6 +#define SYSCTRL_CMD_RESET_I2CM 0x00000080 // Bit 7 + +#define SYSCTRL_CMD_RESET_I2CS 0x00000100 // Bit 8 +#define SYSCTRL_CMD_RESET_DVD 0x00000200 // Bit 9 +#define SYSCTRL_CMD_RESET_DVD_DMA 0x00000400 // Bit10 +#define SYSCTRL_CMD_RESET_IDE 0x00000800 // Bit11 +#define SYSCTRL_CMD_RESET_IDE_DMA 0x00001000 // Bit12 +#define SYSCTRL_CMD_RESET_FIP 0x00002000 // Bit13 +#define SYSCTRL_CMD_RESET_OSD_DMA 0x00004000 // Bit14 +#define SYSCTRL_CMD_RESET_H2Q_DMA 0x00008000 // Bit15 + +#define SYSCTRL_CMD_RESET_Q2H_DMA 0x00010000 // Bit16 +#define SYSCTRL_CMD_RESET_RTC 0x00020000 // Bit17 +#define SYSCTRL_CMD_RESET_I2S 0x00040000 // Bit18 +#define SYSCTRL_CMD_RESET_I2S_DMA 0x00080000 // Bit19 +#define SYSCTRL_CMD_RESET_SPI 0x00100000 // Bit20 +#define SYSCTRL_CMD_RESET_SLAVE 0x00200000 // Bit21 +#define SYSCTRL_CMD_RESET_3 0x00400000 // Bit22 +#define SYSCTRL_CMD_RESET_4 0x00800000 // Bit23 + +#define SYSCTRL_CMD_RESET_5 0x01000000 // Bit24 +#define SYSCTRL_CMD_RESET_6 0x02000000 +#define SYSCTRL_CMD_RESET_7 0x03000000 +#define SYSCTRL_CMD_RESET_8 0x04000000 +#define SYSCTRL_CMD_RESET_10 0x10000000 +#define SYSCTRL_CMD_RESET_11 0x20000000 +#define SYSCTRL_CMD_RESET_Q4 0x40000000 // Bit30 +#define SYSCTRL_CMD_RESET_ALL 0x80000000 // Bit31 + +#define FORCE_REMAP 0x1 +#define CPU_ACCESS_240_CLOCKS 0xF // 240 closck +#define CPU_TIMEOUT_15_CLOCK 0xF // 15 clocks +#define CPU_TIMESLOT_ENABLE 0x100 // Enable TIMESLOT mechanism + + +//------------------------------------------------------ +// MAC COMMAND DEFINITIONS +//------------------------------------------------------ +#define SDRAMCLK_EN 0x1 +#define SDRAMINI_SET 0x2 +//#define TIME_SLOT_4DW 0x0 +//#define TIME_SLOT_8DW 0x1 +//#define TIME_SLOT_16DW 0x2 +//#define TIME_SLOT_32DW 0x3 +#define TIME_SLOT_HIGH_ARBITRATION 0x10000 +#define TIME_SLOT_SLOW_ARBITRATION 0x000 + + + +//------------------------------------------------------ +// TIMER COMMAND DEFINITIONS +//------------------------------------------------------ +#define TIMER02IRQ 0xFFFFFFFE +#define TIMER02FIQ 0x00000001 +#define TIMER12IRQ 0xFFFFFFFD +#define TIMER12FIQ 0x00000002 +#define TIMER0_START 0x00000010 +#define TIMER1_START 0x00000020 +#define TIMER0_INT_CLR 0x00000001 +#define TIMER1_INT_CLR 0x00000002 + +#define TIMER_FREE_RUN_MODE 0x00000000 +#define TIMER_PERIODIC_MODE 0x00000010 +#define TIMER_RUN_OUT_MODE 0x00000020 +#define TIMER_COUNT_ENABLE 0x00000080 + +#define TIMER_NO_PRESCALE 0x0 +#define TIMER_PRESCALE_4 0x1 +#define TIMER_PRESCALE_8 0x2 +#define TIMER_PRESCALE_16 0x3 +#define TIMER_PRESCALE_32 0x4 +#define TIMER_PRESCALE_64 0x5 +#define TIMER_PRESCALE_128 0x6 +#define TIMER_PRESCALE_256 0x7 +#define TIMER_PRESCALE_512 0x8 +#define TIMER_PRESCALE_1024 0x9 +#define TIMER_PRESCALE_2048 0xA +#define TIMER_PRESCALE_4096 0xB +#define TIMER_PRESCALE_8192 0xC +#define TIMER_PRESCALE_16384 0xD +#define TIMER_PRESCALE_32768 0xE +#define TIMER_PRESCALE_65536 0xF + + +//------------------------------------------------------ +// SPI COMMAND DEFINITIONS +//------------------------------------------------------ +#define SPI_ENABLE 0x1 +#define SPI_RESET 0x2 +#define SPI_RESET_ERR_CNTR 0x4 +#define SPI_DISABLE_ERR_CHECK 0x8 +#define SPI_STATUS_MASK 0x000070 + +#define SPI_PKT_DONE 0x1 +#define SPI_SIZE_ERR 0x2 +#define SPI_SYNC_ERR 0x4 +#define SPI_HW_ERR 0x8 + +//------------------------------------------------------ +// SPI/I2S-DMA COMMAND DEFINITIONS +//------------------------------------------------------ +#define SPI_I2S_DMA_RESET 0x2 +#define SPI_I2S_DMA_EN 0x1 +#define SPI_I2S_INT_BUF_FULL 0x1 +#define SPI_I2S_INT_BUF_14 0x2 +#define SPI_I2S_INT_BUF_12 0x4 +#define SPI_I2S_INT_BUF_34 0x8 +#define SPI_I2S_CIR_EN 0x8 /* Enable circular buffer */ +#define SPI_I2S_MUX_SPI_SELECT 0x10 +#define SPI_I2S_FLASH_INTER_FIFO 0x4 + +//------------------------------------------------------ +// FIP COMMAND DEFINITIONS +//------------------------------------------------------ +#define FIP_CMD_DISP_MODE_08DIGITS_20SEGMENTS 0x00 +#define FIP_CMD_DISP_MODE_09DIGITS_19SEGMENTS 0x08 +#define FIP_CMD_DISP_MODE_10DIGITS_18SEGMENTS 0x09 +#define FIP_CMD_DISP_MODE_11DIGITS_17SEGMENTS 0x0a +#define FIP_CMD_DISP_MODE_12DIGITS_16SEGMENTS 0x0b +#define FIP_CMD_DISP_MODE_13DIGITS_15SEGMENTS 0x0c +#define FIP_CMD_DISP_MODE_14DIGITS_14SEGMENTS 0x0d +#define FIP_CMD_DISP_MODE_15DIGITS_13SEGMENTS 0x0e +#define FIP_CMD_DISP_MODE_16DIGITS_12SEGMENTS 0x0f + + +#define FIP_CMD_DATA_SET_RW_MODE_WRITE_DISPLAY 0x40 +#define FIP_CMD_DATA_SET_RW_MODE_WRITE_LED_PORT 0x41 +#define FIP_CMD_DATA_SET_RW_MODE_READ_KEYS 0x42 +#define FIP_CMD_DATA_SET_RW_MODE_READ_SWITCHES 0x43 +#define FIP_CMD_DATA_SET_ADR_MODE_INCREMENT_ADR 0x40 +#define FIP_CMD_DATA_SET_ADR_MODE_FIXED_ADR 0x44 +#define FIP_CMD_DATA_SET_OP_MODE_NORMAL_OPERATION 0x40 +#define FIP_CMD_DATA_SET_OP_MODE_TEST_MODE 0x48 + +#define FIP_CMD_ADR_SETTING 0xC0 + +#define FIP_CMD_DISP_CTRL_PULSE_WIDTH_1_16 0x80 +#define FIP_CMD_DISP_CTRL_PULSE_WIDTH_2_16 0x81 +#define FIP_CMD_DISP_CTRL_PULSE_WIDTH_4_16 0x82 +#define FIP_CMD_DISP_CTRL_PULSE_WIDTH_10_16 0x83 +#define FIP_CMD_DISP_CTRL_PULSE_WIDTH_11_16 0x84 +#define FIP_CMD_DISP_CTRL_PULSE_WIDTH_12_16 0x85 +#define FIP_CMD_DISP_CTRL_PULSE_WIDTH_13_16 0x86 +#define FIP_CMD_DISP_CTRL_PULSE_WIDTH_14_16 0x87 +#define FIP_CMD_DISP_CTRL_TURN_DISPLAY_OFF_MASK 0x87 +#define FIP_CMD_DISP_CTRL_TURN_DISPLAY_ON 0x88 + +//--------------------------------------------------------------------------------------------- +// I/O Macro definitions +//--------------------------------------------------------------------------------------------- +#define PRINT_STATUS *( (volatile unsigned int * )SimStatusAddress) +#define VERILOG_STOP *( (volatile unsigned int * )SIMULATION_CONTROL) = SIMULATION_CMD_STOP_WITH_ERROR + +//--------------------------------------------------------------------------------------------- +// QuickTurn Macro definitions +//--------------------------------------------------------------------------------------------- +#define WRITE_LED_DISPLAY PIO_0_DATA_REG +#define WRITE_HEX_DISPLAY PIO_1_DATA_REG + +//--------------------------------------------------------------------------------------------- +// PIO Macro definitions +//--------------------------------------------------------------------------------------------- +#define PIO_0_INT_STATUS_REG ( (volatile unsigned int * ) (JASPER_PIO0_BASE + PIO_INT_STATUS) ) +#define PIO_0_DATA_REG ( (volatile unsigned int * ) (JASPER_PIO0_BASE + PIO_DATA) ) +#define PIO_0_DIR_REG ( (volatile unsigned int * ) (JASPER_PIO0_BASE + PIO_DIR) ) +#define PIO_0_POL_REG ( (volatile unsigned int * ) (JASPER_PIO0_BASE + PIO_POL) ) +#define PIO_0_INT_ENABLE_REG ( (volatile unsigned int * ) (JASPER_PIO0_BASE + PIO_INT_ENABLE) ) + +#define PIO_1_INT_STATUS_REG ( (volatile unsigned int * ) (JASPER_PIO1_BASE + PIO_INT_STATUS) ) +#define PIO_1_DATA_REG ( (volatile unsigned int * ) (JASPER_PIO1_BASE + PIO_DATA) ) +#define PIO_1_DIR_REG ( (volatile unsigned int * ) (JASPER_PIO1_BASE + PIO_DIR) ) +#define PIO_1_POL_REG ( (volatile unsigned int * ) (JASPER_PIO1_BASE + PIO_POL) ) +#define PIO_1_INT_ENABLE_REG ( (volatile unsigned int * ) (JASPER_PIO1_BASE + PIO_INT_ENABLE) ) + +//--------------------------------------------------------------------------------------------- +// SPI_I2S_DMA Macro definitions +//--------------------------------------------------------------------------------------------- +#define SPI_I2S_DMA_REG_CTRL ( (volatile unsigned int * ) (SPI_I2S_DMA_BASE + SPI_I2S_DMA_CTRL_REG) ) +#define SPI_I2S_DMA_REG_BASE_ADD ( (volatile unsigned int * ) (SPI_I2S_DMA_BASE + SPI_I2S_DMA_BASE_ADD_REG) ) +#define SPI_I2S_DMA_REG_SIZE ( (volatile unsigned int * ) (SPI_I2S_DMA_BASE + SPI_I2S_DMA_SIZE_REG) ) +#define SPI_I2S_DMA_REG_WR_PTR ( (volatile unsigned int * ) (SPI_I2S_DMA_BASE + SPI_I2S_DMA_WR_PTR_REG) ) +#define SPI_I2S_DMA_REG_RD_PTR ( (volatile unsigned int * ) (SPI_I2S_DMA_BASE + SPI_I2S_DMA_RD_PTR_REG) ) +#define SPI_I2S_DMA_REG_TRSH ( (volatile unsigned int * ) (SPI_I2S_DMA_BASE + SPI_I2S_DMA_TRSH_REG) ) +#define SPI_I2S_DMA_REG_INT_EN ( (volatile unsigned int * ) (SPI_I2S_DMA_BASE + SPI_I2S_DMA_INT_EN_REG) ) +#define SPI_I2S_DMA_REG_INT ( (volatile unsigned int * ) (SPI_I2S_DMA_BASE + SPI_I2S_DMA_INT_REG) ) +#define SPI_I2S_DMA_REG_INT_POLL ( (volatile unsigned int * ) (SPI_I2S_DMA_BASE + SPI_I2S_DMA_INT_POLL_REG) ) +#define SPI_I2S_DMA_REG_INTER_FIFO ( (volatile unsigned int * ) (SPI_I2S_DMA_BASE + SPI_I2S_DMA_INTER_FIFO_REG) ) +#define SPI_I2S_DMA_REG_CNT ( (volatile unsigned int * ) (SPI_I2S_DMA_BASE + SPI_I2S_DMA_CNT_REG) ) + +//--------------------------------------------------------------------------------------------- +// Memory Access Controller (MAC) Macro definitions +//--------------------------------------------------------------------------------------------- +#define MAC_REFTIMER_REG ( (volatile unsigned int * ) (MAC_base + MAC_REFTIMER) ) +#define MAC_FLASHCFG_REG ( (volatile unsigned int * ) (MAC_base + MAC_FLASH_CFG) ) +#define MAC_SDRAMCTL_REG ( (volatile unsigned int * ) (MAC_base + MAC_SDRAMCTL) ) +#define MAC_SDRAMCFG_REG ( (volatile unsigned int * ) (MAC_base + MAC_SDRAMCFG) ) +#define MAC_SDRAMDATA_REG ( (volatile unsigned int * ) (MAC_base + MAC_SDRAMDATA) ) + +//--------------------------------------------------------------------------------------------- +// Interrupt Controller Macro definitions +//--------------------------------------------------------------------------------------------- +#define INT_IRQSTAT_REG ( (volatile unsigned int * ) (JASPER_INT_CONTROLLER_BASE + INT_IRQSTAT) ) +#define INT_FIQSTAT_REG ( (volatile unsigned int * ) (JASPER_INT_CONTROLLER_BASE + INT_FIQSTAT) ) +#define INT_TYPE_REG ( (volatile unsigned int * ) (JASPER_INT_CONTROLLER_BASE + INT_INTTYPE) ) +#define INT_POLL_REG ( (volatile unsigned int * ) (JASPER_INT_CONTROLLER_BASE + INT_INTPOLL) ) +#define INT_ENABLE_REG ( (volatile unsigned int * ) (JASPER_INT_CONTROLLER_BASE + INT_INTEN) ) + +//--------------------------------------------------------------------------------------------- +// SPI Registers Macro definitions +//--------------------------------------------------------------------------------------------- +#define SPI_CNTR_REG ( (volatile unsigned int * ) (JASPER_SPI_BASE + SPI_CNTR) ) +#define SPI_REC_COUNTER_REG ( (volatile unsigned int * ) (JASPER_SPI_BASE + SPI_REC_COUNTER) ) +#define SPI_INT_EN_REG ( (volatile unsigned int * ) (JASPER_SPI_BASE + SPI_INT_EN) ) +#define SPI_INT_STATUS_REG ( (volatile unsigned int * ) (JASPER_SPI_BASE + SPI_INT_STATUS) ) +#define SPI_ERROR_CNT_REG ( (volatile unsigned int * ) (JASPER_SPI_BASE + SPI_ERROR_CNT) ) + +//--------------------------------------------------------------------------------------------- +// I2S Registers Macro definitions +//--------------------------------------------------------------------------------------------- +#define I2S_CTRL_REG ( (volatile unsigned int * ) (JASPER_I2S_BASE + I2S_CTRL) ) +#define I2S_PROG_LEN_REG ( (volatile unsigned int * ) (JASPER_I2S_BASE + I2S_PROG_LEN) ) +#define I2S_STATUS_REG ( (volatile unsigned int * ) (JASPER_I2S_BASE + I2S_STATUS) ) +#define I2S_FRAME_CNTR_REG ( (volatile unsigned int * ) (JASPER_I2S_BASE + I2S_FRAME_CNTR) ) + +//--------------------------------------------------------------------------------------------- +// RTC Registers Macro definitions +//--------------------------------------------------------------------------------------------- +#define RTC_CTRL_REG ( (volatile unsigned int * ) (JASPER_RTC_BASE + RTC_CTRL) ) +#define RTC_LOAD1_REG ( (volatile unsigned int * ) (JASPER_RTC_BASE + RTC_LOAD1) ) +#define RTC_LOAD2_REG ( (volatile unsigned int * ) (JASPER_RTC_BASE + RTC_LOAD2) ) +#define RTC_ALARM_REG ( (volatile unsigned int * ) (JASPER_RTC_BASE + RTC_ALARM) ) +#define RTC_INTEN_REG ( (volatile unsigned int * ) (JASPER_RTC_BASE + RTC_INTEN) ) +#define RTC_INT_REG ( (volatile unsigned int * ) (JASPER_RTC_BASE + RTC_INT0) ) +#define RTC_COUNT1_REG ( (volatile unsigned int * ) (JASPER_RTC_BASE + RTC_COUNT1) ) +#define RTC_COUNT2_REG ( (volatile unsigned int * ) (JASPER_RTC_BASE + RTC_COUNT2) ) + +//--------------------------------------------------------------------------------------------- +// Timer Registers Macro definitions +//--------------------------------------------------------------------------------------------- +#define TIMER_TMRSTAT_REG ( (volatile unsigned int * ) (JASPER_TIMER_BASE + TIMER_TMRSTAT) ) +#define TIMER0_LOAD_REG ( (volatile unsigned int * ) (JASPER_TIMER_BASE + TIMER_TMR0LOAD) ) +#define TIMER0_VAL_REG ( (volatile unsigned int * ) (JASPER_TIMER_BASE + TIMER_TMR0VAL) ) +#define TIMER0_CNTL_REG ( (volatile unsigned int * ) (JASPER_TIMER_BASE + TIMER_TMR0CTL) ) + +#define TIMER1_LOAD_REG ( (volatile unsigned int * ) (JASPER_TIMER_BASE + TIMER_TMR1LOAD) ) +#define TIMER1_VAL_REG ( (volatile unsigned int * ) (JASPER_TIMER_BASE + TIMER_TMR1VAL) ) +#define TIMER1_CNTL_REG ( (volatile unsigned int * ) (JASPER_TIMER_BASE + TIMER_TMR1CTL) ) + +//--------------------------------------------------------------------------------------------- +// Interrupt Registers Macro definitions +//--------------------------------------------------------------------------------------------- +/* XXX - defined twice - see above INT_xxx_REG +#define INTERRUPT_IRQSTAT_REG ( (volatile unsigned int * ) (INT_CONTROLLER_BASE + INT_IRQSTAT) ) +#define INTERRUPT_FIQSTAT_REG ( (volatile unsigned int * ) (INT_CONTROLLER_BASE + INT_FIQSTAT) ) +#define INTERRUPT_INTTYPE_REG ( (volatile unsigned int * ) (INT_CONTROLLER_BASE + INT_INTTYPE) ) +#define INTERRUPT_POLL_REG ( (volatile unsigned int * ) (INT_CONTROLLER_BASE + INT_INTPOLL) ) +#define INTERRUPT_ENABLE_REG ( (volatile unsigned int * ) (INT_CONTROLLER_BASE + INT_INTEN) ) +*/ + +//--------------------------------------------------------------------------------------------- +// System Registers Macro definitions +//--------------------------------------------------------------------------------------------- +#define SYS_CHIPID_REG ( (volatile unsigned int * ) (JASPER_SYSCTRL_BASE + SYSCTRL_CHIP_ID) ) +#define SYS_REVID_REG ( (volatile unsigned int * ) (JASPER_SYSCTRL_BASE + SYSCTRL_REVISION_ID) ) +#define SYS_CPU_CFG_REG ( (volatile unsigned int * ) (JASPER_SYSCTRL_BASE + SYSCTRL_CPUCFG) ) +#define SYS_TESTSTAT_REG ( (volatile unsigned int * ) (JASPER_SYSCTRL_BASE + SYSCTRL_TESTSTAT) ) +#define SYS_RESET_REG ( (volatile unsigned int * ) (JASPER_SYSCTRL_BASE + SYSCTRL_RSTCTL) ) +#define SYS_TIMESLOT_REG ( (volatile unsigned int * ) (JASPER_SYSCTRL_BASE + SYSCTRL_CPUTIMESLOT) ) + +//--------------------------------------------------------------------------------------------- +// FIP Registers Macro definitions +//--------------------------------------------------------------------------------------------- +#define FIP_COMMAND_REG ( (volatile unsigned int * ) (JASPER_FIP_BASE + FIP_COMMAND) ) +#define FIP_DISPLAY_DATA_REG ( (volatile unsigned int * ) (JASPER_FIP_BASE + FIP_DISPLAY_DATA) ) +#define FIP_LED_DATA_REG ( (volatile unsigned int * ) (JASPER_FIP_BASE + FIP_LED_DATA) ) +#define FIP_KEY_DATA1_REG ( (volatile unsigned int * ) (JASPER_FIP_BASE + FIP_KEY_DATA1) ) +#define FIP_KEY_DATA2_REG ( (volatile unsigned int * ) (JASPER_FIP_BASE + FIP_KEY_DATA2) ) +#define FIP_SWITCH_DATA_REG ( (volatile unsigned int * ) (JASPER_FIP_BASE + FIP_SWITCH_DATA) ) +#define FIP_CLK_DIV_REG ( (volatile unsigned int * ) (JASPER_FIP_BASE + FIP_CLK_DIV) ) +#define FIP_TRISTATE_MODE_REG ( (volatile unsigned int * ) (JASPER_FIP_BASE + FIP_TRISTATE_MODE) ) + +#ifndef __ASSEMBLY__ +#include + +#define HARD_RESET_NOW() outl(JASPER_SYSCTRL_BASE + SYSCTRL_RSTCTL,1) + +static inline unsigned long __get_clock(unsigned int unit) +{ + unsigned int clock; +#ifndef CONFIG_QUICKTURN_HACKS + unsigned int pll_reg; + unsigned int pll_mult, pll_div, pll_D; + + pll_reg = inl(JASPER_QUASAR_BASE + QUASAR_DRAM_PLLCONTROL); + pll_mult = (pll_reg & 0xFF) >> 2; + pll_div = (pll_reg >> 8 ) & 0x3; + pll_D = (pll_reg & 0x2); + + clock = ((JASPER_EXT_CLOCK / unit) * (pll_mult+2))/(pll_div + 2); + if(pll_D) + clock = clock / 2; +#else +#warning ***** QUICKTURN HACK ***** Kernel think CPU clock is 4 Mhz + clock = 4000000 / unit; +#endif + return clock; +} +#endif + +#endif /* _ASM_ARCH_HARDWARE_H */ + + diff --git a/src/libsp/MP/sp_cdrom.cpp b/src/libsp/MP/sp_cdrom.cpp new file mode 100644 index 0000000..a02ced6 --- /dev/null +++ b/src/libsp/MP/sp_cdrom.cpp @@ -0,0 +1,670 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - CDROM driver's functions source file. + * For Technosonic-compatible players ('MP') + * \file sp_cdrom.cpp + * \author bombur + * \version 0.1 + * \date 4.05.2004 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + + + +//#define USE_UCLINUX_26 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef __USE_LARGEFILE64 +#define __USE_LARGEFILE64 +#endif + +#include + +#include +#include + +#include "sp_misc.h" +#include "sp_msg.h" +#include "sp_cdrom.h" + +/// CD-ROM fs mount flag +static BOOL cdrom_cd_mounted = FALSE, cdrom_hdd_mounted = FALSE, cdrom_cd_inserted = FALSE; +static BOOL cdrom_cd = FALSE, cdrom_hdd = FALSE, cdrom_hdd_root = FALSE; +static int cdrom_mount_tries = 0; +/// CD-ROM fs mount language +static const char *def_cdrom_language = "iso8859-1"; +static char *cdrom_language = NULL; +static char cdrom_last_letter = 'c'; +static CDROM_STATUS cdrom_hdd_status = CDROM_STATUS_HAS_ISO; + +/// Internal function used by cdrom_getstatus() +static int cdrom_getmediumtype(); + +#if 0 +static void cdrom_count_tracks(); +#endif + +/// OS-specific path to CD-ROM. Used by cdrom_getdevicepath() +static const char *curdvdpath = "/dev/cdroms/cdrom0"; + +/// CD-ROM device handle +int cdrom_handle = -1; +int cdrom_hdd_handle[2] = { -1, -1 }; + +/// Used by cdrom_getmediumtype(). +/// Taken from linux/cdrom.h (kernel mode) + +#ifndef USE_UCLINUX_26 +struct mode_page_header +{ + WORD mode_data_length; + BYTE medium_type; + BYTE reserved1; + BYTE reserved2; + BYTE reserved3; + WORD desc_length; +}; +#endif + + +/// Used by cdrom_getmediumtype(). +/// Taken from drivers/ide/ide-cd.h (uClinux source) +struct atapi_capabilities_page +{ + struct mode_page_header header; + BYTE caps[20]; +}; + + +BOOL cdrom_init() +{ + cdrom_deinit(); + + cdrom_language = SPstrdup(def_cdrom_language); + cdrom_hdd_mounted = FALSE; + cdrom_cd_mounted = FALSE; + cdrom_mount_tries = 0; + cdrom_cd_inserted = FALSE; + + // try CD-ROM + cdrom_handle = open("/dev/cdroms/cdrom0", O_NONBLOCK); + if (cdrom_handle >= 0) + { + cdrom_cd = TRUE; + printf("cdrom: CD-ROM Detected!\n"); + } + // try HDD + for (int i = 0; i < 2; i++) + { + char hdd_name[40]; + sprintf(hdd_name, "/dev/discs/disc%d/disc", i); + cdrom_hdd_handle[i] = open(hdd_name, O_NONBLOCK); + if (cdrom_hdd_handle[i] >= 0) + { + cdrom_hdd = TRUE; + printf("cdrom: HDD-%d Detected!\n", i+1); + } + } + + return (cdrom_handle != -1 || cdrom_hdd_handle[0] != -1 || cdrom_hdd_handle[1] != -1); +} + +BOOL cdrom_switch(BOOL on) +{ + if (cdrom_hdd) + { + BYTE args1[4], args2[4]; + int r1, r2 = 0; + + for (int i = 0; i < 2; i++) + { + if (on) + { + static const BYTE on_args1[4] = { WIN_SETIDLE1, 0, 0, 0 }; + static const BYTE on_args2[4] = { WIN_SETIDLE2, 0, 0, 0 }; + + memcpy(args1, on_args1, 4); + memcpy(args2, on_args2, 4); + } else + { + static const BYTE off_args1[4] = { WIN_STANDBYNOW1, 0, 0, 0 }; + static const BYTE off_args2[4] = { WIN_STANDBYNOW2, 0, 0, 0 }; + + memcpy(args1, off_args1, 4); + memcpy(args2, off_args2, 4); + } + + if (cdrom_hdd_handle[i] == -1) + continue; + r1 = ioctl(cdrom_hdd_handle[i], HDIO_DRIVE_CMD, &args1); + if (r1 != 0) + r2 = ioctl(cdrom_hdd_handle[i], HDIO_DRIVE_CMD, &args2); + + printf("HDD-%d: Switch %s (result=%d,%d)\n", i+1, (on ? "On" : "Off"), r1, r2); + + // now check the results... + BYTE cp_args1[4] = { WIN_CHECKPOWERMODE1, 0, 0, 0 }; + BYTE cp_args2[4] = { WIN_CHECKPOWERMODE2, 0, 0, 0 }; + const char *state; + if (ioctl(cdrom_hdd_handle[i], HDIO_DRIVE_CMD, &cp_args1) && ioctl(cdrom_hdd_handle[i], HDIO_DRIVE_CMD, &cp_args2)) + { + if (errno != EIO || cp_args1[0] != 0 || cp_args1[1] != 0) + state = "Unknown"; + else + state = "Sleeping"; + } else + { + if (cp_args1[2] == 255 || cp_args2[2] == 255) + state = "Active/Idle"; + else + state = "Standby"; + } + msg("HDD-%d: Drive state is: %s\n", i+1, state); + } + } + if (cdrom_cd) + { + if (on) + ioctl(cdrom_handle, CDROMSTART); + else + ioctl(cdrom_handle, CDROMSTOP); + + printf("CD: Switch %s\n", (on ? "On" : "Off")); + } + return TRUE; +} + +BOOL cdrom_deinit() +{ + if (cdrom_handle != -1) + close (cdrom_handle); + if (cdrom_hdd_handle[0] != -1) + close (cdrom_hdd_handle[0]); + if (cdrom_hdd_handle[1] != -1) + close (cdrom_hdd_handle[1]); + cdrom_handle = -1; + cdrom_hdd_handle[0] = cdrom_hdd_handle[1] = -1; + SPSafeFree(cdrom_language); + return TRUE; +} + +int cdrom_eject(BOOL open) +{ + if (cdrom_cd) + { + struct timeval tv; + struct timezone tz; + int start_time, stop_time; + + cdrom_generic_command cmd; + struct request_sense sense; + + cdrom_umount(); + + // [bombur]: I wish it was true... :-( + //return ioctl(cdrom_handle, CDROMEJECT, 0); + + // [bombur]: this is the exact code used in original init! + memset(&cmd, 0, sizeof(cmd)); + cmd.cmd[0] = GPCMD_PREVENT_ALLOW_MEDIUM_REMOVAL; + cmd.cmd[4] = 0; // unlock + cmd.buffer = NULL; + cmd.buflen = 0; + cmd.sense = &sense; + cmd.data_direction = CGC_DATA_READ; + ioctl(cdrom_handle, CDROM_SEND_PACKET, &cmd); + + gettimeofday(&tv, &tz); + start_time = tv.tv_sec * 1000 + (tv.tv_usec / 1000); + + cmd.cmd[0] = GPCMD_START_STOP_UNIT; + cmd.cmd[1] = 1; + cmd.cmd[4] = open ? 2 : 3; + if (ioctl(cdrom_handle, CDROM_SEND_PACKET, &cmd)) + return -1; + + do + { + gui_update(); + usleep(10000); + gettimeofday(&tv, &tz); + stop_time = tv.tv_sec * 1000 + (tv.tv_usec / 1000); + } while (stop_time - start_time < 2000); // less than 2 seconds passed - wait! + + return 0; + } + else if (cdrom_hdd) + { + // emulate 'ejecting' HDD - to enable 'Setup' mode possibility + cdrom_hdd_status = open ? CDROM_STATUS_TRAYOPEN : CDROM_STATUS_HAS_ISO; + return 0; + } + + return -1; +} + +int cdrom_mount(char *language, BOOL cd_only) +{ + int ret = 0; + char iocharset[1024]; + if (language == NULL) + language = cdrom_language; + + BOOL lang_changed = strcasecmp(cdrom_language, language) != 0; + if (cdrom_cd && cdrom_cd_inserted) + { + if ((!cdrom_cd_mounted && cdrom_mount_tries < 1) || lang_changed) + { + int rmnt = ((cdrom_cd_mounted && lang_changed) ? MS_REMOUNT : 0); + sprintf(iocharset, "iocharset=%s", language); + ret = mount("/dev/cdroms/cdrom0", "/cdrom", "iso9660", MS_RDONLY | MS_NOSUID | MS_NODEV | rmnt, iocharset); + msg("CD: mount ISO with %s(%d) = %d\n", iocharset, rmnt, ret < 0 ? -errno : ret); + if (ret < 0) + { + ret = mount("/dev/cdroms/cdrom0", "/cdrom", "udf", MS_RDONLY | MS_NOSUID | MS_NODEV | rmnt, iocharset); + msg("CD: mount UDF with %s(%d) = %d\n", iocharset, rmnt, ret < 0 ? -errno : ret); + cdrom_mount_tries++; + } + if (ret >= 0) + { + cdrom_cd_mounted = TRUE; + cdrom_mount_tries = 0; + } + } +// cdrom_count_tracks(); + } + + if (cdrom_hdd && !cd_only) + { + if (!cdrom_hdd_mounted || lang_changed) + { + char tmpdir[256], tmpmnt[256]; + char dirname[40]; + const char *mntname = "/hdd/"; + char mnt_char = 'c'; + struct dirent *entry; + //int rmnt = ((cdrom_hdd_mounted && lang_changed) ? MS_REMOUNT : 0); + int rmnt = 0; + for (int i = 0; i < 2; i++) + { + if (cdrom_hdd_handle[i] < 0) + continue; + sprintf(dirname, "/dev/discs/disc%d", i); + DIR *dir = opendir(dirname); + printf("Mounting HDD-%d...\n", i+1); + while ((entry = readdir(dir)) != NULL) + { + if (memcmp(entry->d_name, "part", 4) == 0) + { + sprintf(tmpdir, "%s/%s", dirname, entry->d_name); + sprintf(tmpmnt, "%s%c", mntname, mnt_char); + + if (lang_changed) + umount(tmpmnt); + + printf("* Mounting %s AS FAT: ", tmpdir);fflush(stdout); + sprintf(iocharset, "iocharset=%s", language); + ret = mount(tmpdir, tmpmnt, "vfat", MS_RDONLY | MS_NOSUID | MS_NODEV | rmnt, iocharset); + if (ret < 0) + { + printf("NO. Mounting AS NTFS: ");fflush(stdout); + sprintf(iocharset, "nls=%s", language); + ret = mount(tmpdir, tmpmnt, "ntfs", MS_RDONLY | MS_NOSUID | MS_NODEV | rmnt, iocharset); + } + printf(ret < 0 ? "FAILED\n" : "OK\n");fflush(stdout); + if (ret >= 0) + { + mnt_char++; + cdrom_hdd_mounted = TRUE; + } + } + } + cdrom_last_letter = mnt_char - 1; + closedir(dir); + + msg("HDD-%d: mount with %s(%d) = %d\n", i+1, iocharset, rmnt, ret < 0 ? -errno : ret); + } + } + } + + if (lang_changed) + { + SPSafeFree(cdrom_language); + cdrom_language = SPstrdup(language); + } + + return ret; +} + +int cdrom_umount() +{ + msg("CD-ROM: umount.\n"); + if (cdrom_cd) + { + cdrom_cd_mounted = FALSE; + cdrom_mount_tries = 0; + return umount("/cdrom"); + } + return FALSE; +} + +BOOL cdrom_ismounted() +{ + return cdrom_cd ? cdrom_cd_mounted : cdrom_hdd_mounted; +} + +static CDROM_STATUS cdrom_detect_dvd() +{ + char dvdpath2[1024]; + + cdrom_cd_inserted = TRUE; + + cdrom_mount(NULL, TRUE); + + DIR *dir = opendir("/cdrom"); + struct dirent *entry; + struct stat statbuf; + while ((entry = readdir(dir)) != NULL) + { + if (strcasecmp(entry->d_name, "VIDEO_TS") == 0) + { + sprintf(dvdpath2, "/cdrom/%s", entry->d_name); + if (stat(dvdpath2, &statbuf) >= 0) + { + if (S_ISDIR(statbuf.st_mode)) + { + closedir(dir); + cdrom_umount(); + return CDROM_STATUS_HAS_DVD; + } + } + } + } + closedir(dir); + + cdrom_umount(); + return CDROM_STATUS_HAS_ISO; +} + +CDROM_STATUS cdrom_getstatus(BOOL *force_update) +{ + static int old_medium = -1; + static CDROM_STATUS old_status = CDROM_STATUS_UNKNOWN; + + if (!cdrom_cd && cdrom_hdd) + return cdrom_hdd_status; + + if (cdrom_handle != -1) + { + int medium = cdrom_getmediumtype(), auxtype; + + if (medium != old_medium) + { + *force_update = TRUE; + msg("Drive: medium type = %d.\n", medium); + } + + switch (medium) + { + case 112: + old_status = cdrom_hdd ? cdrom_hdd_status : CDROM_STATUS_NODISC; + cdrom_cd_inserted = FALSE; + cdrom_mount_tries = 0; + old_medium = medium; + return old_status; + case 113: + old_status = CDROM_STATUS_TRAYOPEN; + cdrom_cd_inserted = FALSE; + cdrom_mount_tries = 0; + old_medium = medium; + return old_status; + case 1: + case 4: + case 5: + case 8: + case 0x11: + case 0x14: + case 0x15: + case 0x18: + case 0x21: + case 0x24: + case 0x25: + case 0x28: + // [bombur]: mixed discs are not always recognized by mediumtype + auxtype = ioctl(cdrom_handle, CDROM_DISC_STATUS, CDSL_CURRENT); + old_status = (auxtype == CDS_MIXED) ? CDROM_STATUS_HAS_MIXED : CDROM_STATUS_HAS_ISO; + old_medium = medium; + cdrom_cd_inserted = TRUE; + return old_status; + case 2: + case 6: + case 0x12: + case 0x16: + case 0x22: + case 0x26: + old_medium = medium; + old_status = cdrom_hdd ? CDROM_STATUS_HAS_MIXED : CDROM_STATUS_HAS_AUDIO; + cdrom_cd_inserted = TRUE; + return old_status; + case 3: + case 7: + case 0x13: + case 0x17: + case 0x23: + case 0x27: + old_medium = medium; + old_status = CDROM_STATUS_HAS_MIXED; + cdrom_cd_inserted = TRUE; + return old_status; + case 0x40: + case 0x41: + case 0x42: + case 0x43: + case 0x44: + case 0x45: + case 0x46: + case 0x47: + case 0x48: + if (old_medium < 0x40 || old_medium > 0x48 || old_status == CDROM_STATUS_UNKNOWN) + old_status = cdrom_detect_dvd(); + old_medium = medium; + return old_status; + case 0: + old_medium = medium; + break; + default: + if (medium != old_medium) + msg("Drive: Unknown medium type = %d.\n", medium); + old_medium = medium; + break; + } + } + return CDROM_STATUS_UNKNOWN; +} + +BOOL cdrom_isready() +{ + // ready or not, we failed + if (cdrom_handle == -1) + return TRUE; + if (cdrom_hdd && !cdrom_cd) + return TRUE; + + int drive = ioctl(cdrom_handle, CDROM_DRIVE_STATUS, CDSL_CURRENT); + int disc = ioctl(cdrom_handle, CDROM_DISC_STATUS, CDSL_CURRENT); + +// msg("CD-ROM: drive.status = %d disc.status = %d.\n", drive, disc); +// gui_update(); + + if (/*drive == CDS_TRAY_OPEN || */drive == CDS_DRIVE_NOT_READY || + /*disc == CDS_TRAY_OPEN || */disc == CDS_DRIVE_NOT_READY) + return FALSE; + return TRUE; +} + +/// Internal function used by cdrom_getstatus() +/// [bombur]: this was die hard! +int cdrom_getmediumtype() +{ + cdrom_generic_command cmd; + struct request_sense sense; + struct atapi_capabilities_page caps; + + if (cdrom_handle != -1) + { + memset(&cmd, 0, sizeof(cmd)); + cmd.buffer = (BYTE *)∩︀ + cmd.buflen = sizeof(caps); + cmd.cmd[0] = GPCMD_MODE_SENSE_10; + cmd.cmd[2] = GPMODE_CAPABILITIES_PAGE; + cmd.cmd[8] = cmd.buflen & 0xff; + cmd.sense = &sense; + cmd.data_direction = CGC_DATA_READ; + ioctl(cdrom_handle, CDROM_SEND_PACKET, &cmd); + + return caps.header.medium_type; + } + return 0; +} + +const char *cdrom_getdevicepath(const char *src_path) +{ + if (src_path != NULL) + return src_path; + return curdvdpath; +} + +int cdrom_stat(const char *path, struct stat64 *s) +{ + return stat64(path, s); +} + +DIR *cdrom_opendir(const char *path) +{ + cdrom_hdd_root = FALSE; + if (cdrom_hdd) + { + if (strcasecmp(path, "/hdd/") == 0) + cdrom_hdd_root = TRUE; + } + return opendir(path); +} + +struct dirent *cdrom_readdir(DIR *dir) +{ + struct dirent *d = readdir(dir); + // fast mount-check (exclude unmounted folders) + if (cdrom_hdd_root && d->d_name[0] > cdrom_last_letter && d->d_name[1] == '\0') + return NULL; + return d; +} + +int cdrom_closedir(DIR *dir) +{ + return closedir(dir); +} + +int cdrom_open(const char *fname, int flags) +{ + return open(fname, flags | O_LARGEFILE); +} + +char *cdrom_getrealpath(const char *path) +{ + static char tmpp[4096]; +msg("CD-ROM: GET_REAL_PATH (%s) = %d %d %d\n", path, cdrom_hdd, cdrom_cd, cdrom_cd_inserted); + if (memcmp(path, "/", 2) == 0) + { + if (cdrom_hdd && cdrom_cd && cdrom_cd_inserted) + return (char *)path; + else if (cdrom_hdd) + strcpy(tmpp, "/hdd"); + else + strcpy(tmpp, "/cdrom"); + strcat(tmpp, path); + return tmpp; + } + return (char *)path; +} + + +#if 0 +void cdrom_count_tracks() +{ + struct cdrom_tochdr header; + struct cdrom_tocentry entry; + int ret, i; +/* + tracks->data=0; + tracks->audio=0; + tracks->cdi=0; + tracks->xa=0; + tracks->error=0; +*/ + /* Grab the TOC header so we can see how many tracks there are */ + if ((ret = ioctl(cdrom_handle, CDROMREADTOCHDR, &header))) + { + if (ret == -ENOMEDIUM) + msg("cdrom_count_tracks: CDS_NO_DISC!"); + else + msg("cdrom_count_tracks: CDS_NO_INFO!"); + return; + } + /* check what type of tracks are on this disc */ + entry.cdte_format = CDROM_MSF; + msg("Starting %d-%d:\n", header.cdth_trk0, header.cdth_trk1); + for (i = header.cdth_trk0; i <= header.cdth_trk1; i++) + { + entry.cdte_track = i; + if (ioctl(cdrom_handle, CDROMREADTOCENTRY, &entry)) + { + msg("track %2d: CDS_NO_INFO\n"); + continue; + } + /* + if (entry.cdte_ctrl & CDROM_DATA_TRACK) + { + if (entry.cdte_format == 0x10) + tracks->cdi++; + else if (entry.cdte_format == 0x20) + tracks->xa++; + else + tracks->data++; + } else + tracks->audio++; + */ + msg("track %2d: fmt=0x%x, ctrl=0x%x\n", i, entry.cdte_format, entry.cdte_ctrl); + } + /* + cdinfo(CD_COUNT_TRACKS, "disc has %d tracks: %d=audio %d=data %d=Cd-I %d=XA\n", + header.cdth_trk1, tracks->audio, tracks->data, + tracks->cdi, tracks->xa); + */ + usleep(1000000); +} +#endif diff --git a/src/libsp/MP/sp_eeprom.cpp b/src/libsp/MP/sp_eeprom.cpp new file mode 100644 index 0000000..1502220 --- /dev/null +++ b/src/libsp/MP/sp_eeprom.cpp @@ -0,0 +1,60 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - EEPROM interface functions source file + * For Technosonic-compatible players ('MP') + * \file sp_eeprom.cpp + * \author bombur + * \version 0.1 + * \date 4.05.2004 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#include +#include + +#include "sp_misc.h" +#include "sp_khwl.h" +#include "sp_eeprom.h" + +BOOL eeprom_set_value(DWORD addr, DWORD val, int size) +{ + KHWL_ADDR_DATA data; + /// \warning: not usual byte order used! + for (int i = 0; i < size; i++) + { + data.Addr = addr + i; + data.Data = (val >> (i * 8)) & 0xff; + khwl_setproperty(KHWL_EEPROM_SET, eEepromAccess, sizeof(data), &data); + } + return TRUE; +} + +DWORD eeprom_get_value(DWORD addr, int size) +{ + KHWL_ADDR_DATA data; + DWORD val = 0; + /// \warning: not usual byte order used! + for (int i = 0; i < size; i++) + { + data.Addr = addr + i; + data.Data = 0; + khwl_getproperty(KHWL_EEPROM_SET, eEepromAccess, sizeof(data), &data); + val |= (data.Data & 0xff) << (i * 8); + } + return val; +} + diff --git a/src/libsp/MP/sp_fip.cpp b/src/libsp/MP/sp_fip.cpp new file mode 100644 index 0000000..0e1503b --- /dev/null +++ b/src/libsp/MP/sp_fip.cpp @@ -0,0 +1,261 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - FIP interface functions source file. + * For Technosonic-compatible players ('MP') + * \file sp_fip.cpp + * \author bombur + * \version 0.1 + * \date 4.05.2004 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include +#include + +#include "sp_misc.h" +#include "sp_fip.h" +#include "sp_fip_ioctl.h" + +#if defined(SP_PLAYER_TECHNOSONIC) + #include "sp_fip_codes-technosonic.h" +#elif defined(SP_PLAYER_DREAMX108) + #include "sp_fip_codes-dreamx108.h" +#elif defined(SP_PLAYER_MECOTEK) + #include "sp_fip_codes-mecotek.h" +#endif + +/// Characters supported by LED +static unsigned char char_table[] = " -0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz-\1"; +/// Current LEF data storage +static unsigned char fipram[12]; +/// Binary definitions for LED data +static unsigned char led_table[] = +{ + 0x00,0x10,0xEE,0x48,0xD6,0xDA,0x78,0xBA,0xBE,0xC8,0xFE,0xFA,0xFC,0xFE,0xA6,0xEE, + 0xB6,0xB4,0xBE,0x7C,0x48,0x4A,0x7C,0x26,0xEC,0xEC,0xEE,0xF4,0xEE,0xFC,0xBA,0xC8, + 0x6E,0x6E,0x6E,0x6C,0x78,0x82,0x02,0x1E,0x3E,0x16,0x5E,0xF6,0xB4,0xFA,0x3C,0x08, + 0x0A,0x2C,0x26,0x1C,0x1C,0x1E,0xF4,0xF8,0x14,0xBA,0x36,0x0E,0x0E,0x0E,0x0C,0x78, + 0x12,0x10,0x00 +}; + +static unsigned char led_special[] = +{ + 0x01, 0x07, 0x01, 0x06, 0x03, 0x07, 0x02, 0x07, 0x05, 0x07, 0x04, 0x07, 0x07, 0x07, 0x07, 0x06, + 0x07, 0x05, 0x07, 0x04, 0x07, 0x03, 0x07, 0x02, 0x07, 0x01, 0x07, 0x00, 0x09, 0x07, 0x09, 0x06, + 0x09, 0x05, 0x09, 0x04, 0x09, 0x03, 0x09, 0x02, 0x09, 0x01, 0x09, 0x00, 0x08, 0x07, 0x08, 0x06, + 0x08, 0x05, 0x08, 0x04, 0x08, 0x03, 0x08, 0x02 +}; + +/// Button codes translation look-up table +static BYTE *button_LUT = NULL; +static DWORD butcode = 0; +static int fip_panel_num_codes = 0; + +/// FIP device handle +int fip_handle = -1; + +void fip_create_table(BYTE *lut) +{ + memset(lut, 0xff, 65536); + + DWORD *codes[2]; + codes[0] = fip_panel_codes; + codes[1] = fip_remote_codes; + + fip_panel_num_codes = 0; + for (int i = 0; i < 2; i++) + { + int j; + for (j = 0; codes[i][j] != 0xffffffff; j++) + { + lut[codes[i][j] & 0xffff] = j; + } + if (i == 0) + fip_panel_num_codes = j; + } +} + +BOOL fip_init(BOOL applymodule) +{ + fip_deinit(); + + if (applymodule == FALSE || module_apply("/drivers/fipmodule.o")) // insert + { + fip_handle = open("/dev/fip", O_NONBLOCK, 0); + if (fip_handle != -1) + { + fip_clear(); + + button_LUT = (BYTE *)SPmalloc(65536); + if (button_LUT == NULL) + return FALSE; + + fip_create_table(button_LUT); + + return TRUE; + } + } + + return FALSE; +} + +BOOL fip_deinit() +{ + if (fip_handle == -1) + return FALSE; + close(fip_handle); + fip_handle = -1; + + if (button_LUT != NULL) + { + SPfree(button_LUT); + button_LUT = NULL; + } + + return TRUE; +} + +BOOL fip_clear() +{ + int i; + + if (fip_handle == -1) + return FALSE; + // clear FIP + for (i = 0; i < 10; i++) + { + fipram[i] = 0; + ioctl(fip_handle, FIP_DISPLAY_SYMBOL, i << 16); + } + return TRUE; +} + +BOOL fip_write_char(int ch, int pos) +{ + int lr; + unsigned char old_fip1, old_fip2 = 0; + + if (pos < 1 || pos - 1 > 6 || fip_handle == -1) + return FALSE; + ch &= 255; + for (lr = 0; lr < 66; lr++) + { + if (char_table[lr] == (BYTE)ch) + break; + } + // saving + old_fip1 = fipram[pos-1]; + + // clear + if (pos == 2) + { + old_fip2 = fipram[pos-2]; + + fipram[pos-1] &= 192; + fipram[pos-2] &= 127; + } else + fipram[pos-1] &= 128; + // set + if (pos == 2) + { + fipram[pos-1] |= led_table[lr] >> 2; + fipram[pos-2] |= (led_table[lr] << 6) & ~127; + } else + fipram[pos-1] |= led_table[lr] >> 1; + // write to device + if (old_fip1 != fipram[pos-1]) + ioctl(fip_handle, FIP_DISPLAY_SYMBOL, fipram[pos-1] | ((pos-1) << 16)); + if (pos == 2 && old_fip2 != fipram[pos-2]) + ioctl(fip_handle, FIP_DISPLAY_SYMBOL, fipram[pos-2] | ((pos-2) << 16)); + return TRUE; +} + +BOOL fip_write_string(const char *str) +{ + int i, len; + if (str == NULL || fip_handle == -1) + return FALSE; + len = strlen(str); + if (len > 7) + len = 7; + for (i = 0; i < 7; i++) + fip_write_char(i < len ? str[len - i - 1] : ' ', i + 1); + return TRUE; +} + +BOOL fip_write_special_char(int pos, int shift, BOOL onoff) +{ + BYTE ch = fipram[pos]; + if (onoff) + fipram[pos] = ch | (1 << shift); + else + fipram[pos] = ch & ~(1 << shift); + if (fipram[pos] == ch) + return TRUE; + ioctl(fip_handle, FIP_DISPLAY_SYMBOL, fipram[pos] | (pos << 16)); + return TRUE; +} + +BOOL fip_write_special(int id, BOOL onoff) +{ + if (id < 0 || id > 27) + return FALSE; + return fip_write_special_char(led_special[id*2], led_special[id*2+1], onoff); +} + +BOOL fip_get_special(int id) +{ + if (id < 0 || id > 27) + return FALSE; + int pos = led_special[id*2]; + int shift = led_special[id*2+1]; + return (fipram[pos] >> shift) & 1; +} + +int fip_read_button(BOOL blocked) +{ + if (fip_handle == -1 || button_LUT == NULL) + return 0; + butcode = ioctl(fip_handle, FIP_BUTTON_READ, blocked); + if (butcode == 0) + return FIP_KEY_NONE; + + BYTE bc = button_LUT[butcode & 0xffff]; +#ifdef SP_PLAYER_DREAMX108 + // special case - front panel STOP button + if (butcode == 0x00080000) + return FIP_KEY_FRONT_STOP; + // special case - front panel PREV button + if (butcode == 0x00010000) + return FIP_KEY_FRONT_SKIP_PREV; +#endif + + if (bc == 0xff) + return FIP_KEY_NONE; + if (bc < fip_panel_num_codes) + { + if (butcode == fip_panel_codes[bc]) + return (unsigned int)bc + fip_code_offset[0]; + } + if (butcode == fip_remote_codes[bc]) + return (unsigned int)bc + fip_code_offset[1]; + return FIP_KEY_NONE; +} diff --git a/src/libsp/MP/sp_fip_codes-dreamx108.h b/src/libsp/MP/sp_fip_codes-dreamx108.h new file mode 100644 index 0000000..9f7ab06 --- /dev/null +++ b/src/libsp/MP/sp_fip_codes-dreamx108.h @@ -0,0 +1,119 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - FIP button internal codes header. + * For DreamX-108 player + * \file sp_fip_codes-dreamx108.h + * \author bombur + * \version 0.2 + * \date 21.01.2009 4.05.2007 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#ifndef SP_FIP_CODES_H +#define SP_FIP_CODES_H + + +#ifdef __cplusplus +extern "C" { +#endif + +//////////////////////////////////// +/// Button codes definitions: + +static DWORD fip_code_offset[2] = { FIP_KEY_FRONT_EJECT, FIP_KEY_POWER }; + +/// 1) definitions for the buttons on the front panel: +static DWORD fip_panel_codes[] = +{ + 0x00001000, // FIP_KEY_FRONT_EJECT, + 0x80000080, // FIP_KEY_FRONT_PLAY, + 0x00080000, // FIP_KEY_FRONT_STOP, + 0x08000008, // FIP_KEY_FRONT_PAUSE, + 0x00010000, // FIP_KEY_FRONT_SKIP_PREV, + 0x00004000, // FIP_KEY_FRONT_SKIP_NEXT, + 0x10000010, // FIP_KEY_FRONT_REWIND, + 0x01000001, // FIP_KEY_FRONT_FORWARD, + + 0xffffffff +}; + +/// 2) definitions for the keys on the remote control: +static DWORD fip_remote_codes[] = +{ + 0x00FBF00F, // FIP_KEY_POWER, + 0x00FBB847, // FIP_KEY_EJECT, + + 0x00FBCA35, // FIP_KEY_ONE, + 0x00FBD02F, // FIP_KEY_TWO, + 0x00FB7887, // FIP_KEY_THREE, + 0x00FBC03F, // FIP_KEY_FOUR, + 0x00FB8A75, // FIP_KEY_FIVE, + 0x00FB20DF, // FIP_KEY_SIX, + 0x00FB2AD5, // FIP_KEY_SEVEN, + 0x00FB807F, // FIP_KEY_EIGHT, + 0x00FBD827, // FIP_KEY_NINE, + 0x00FB708F, // FIP_KEY_ZERO, + + 0x00FB6897, // FIP_KEY_CANCEL, + 0x00FBE21D, // FIP_KEY_SEARCH, + 0x00FBA05F, // FIP_KEY_ENTER, + + 0x00FB827D, // FIP_KEY_OSD, + 0x00FBA25D, // FIP_KEY_SUBTITLE, + 0x00FBB24D, // FIP_KEY_SETUP, + 0x00FBD22D, // FIP_KEY_RETURN, + 0x00FBF807, // FIP_KEY_TITLE, + 0x00000800, // FIP_KEY_PN, + 0x00FB42BD, // FIP_KEY_MENU, + 0x00FB28D7, // FIP_KEY_AB, + 0x00FB8877, // FIP_KEY_REPEAT, + + 0x00FB30CF, // FIP_KEY_UP, + 0x00FBF20D, // FIP_KEY_DOWN, + 0x00FB9867, // FIP_KEY_LEFT, + 0x00FB32CD, // FIP_KEY_RIGHT, + + 0x00FBC23D, // FIP_KEY_VOLUME_DOWN, + 0x00FB22DD, // FIP_KEY_VOLUME_UP, + 0x00FB629D, // FIP_KEY_PAUSE, + + 0x00FB926D, // FIP_KEY_REWIND, + 0x00FB6A95, // FIP_KEY_FORWARD, + 0x00FB38C7, // FIP_KEY_SKIP_PREV, + 0x00FB609F, // FIP_KEY_SKIP_NEXT, + + 0x00FBA857, // FIP_KEY_PLAY, + 0x00FBB04F, // FIP_KEY_STOP, + + 0x00FBE817, // FIP_KEY_SLOW, + 0x00FB728D, // FIP_KEY_AUDIO, + 0x00FBAA55, // FIP_KEY_VMODE, + 0x00FB906F, // FIP_KEY_MUTE, + 0x00FBC837, // FIP_KEY_ZOOM, + 0x00FFBABA, // FIP_KEY_PROGRAM, + 0x00FF728D, // FIP_KEY_PBC, + 0x00FB4AB5, // FIP_KEY_ANGLE, + + 0xffffffff +}; + +#ifdef __cplusplus +} +#endif + + +#endif // of SP_FIP_CODES_H diff --git a/src/libsp/MP/sp_fip_codes-mecotek.h b/src/libsp/MP/sp_fip_codes-mecotek.h new file mode 100644 index 0000000..108ff2e --- /dev/null +++ b/src/libsp/MP/sp_fip_codes-mecotek.h @@ -0,0 +1,119 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - FIP button internal codes header. + * For Mecotek MK-X4000 player + * \file sp_fip_codes-mecotek.h + * \author bombur + * \version 0.1 + * \date 18.02.2010 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#ifndef SP_FIP_CODES_H +#define SP_FIP_CODES_H + + +#ifdef __cplusplus +extern "C" { +#endif + +//////////////////////////////////// +/// Button codes definitions: + +static DWORD fip_code_offset[2] = { FIP_KEY_FRONT_EJECT, FIP_KEY_POWER }; + +/// 1) definitions for the buttons on the front panel: +static DWORD fip_panel_codes[] = +{ + 0x08000008, // FIP_KEY_FRONT_EJECT, + 0x80000080, // FIP_KEY_FRONT_PLAY, + 0x04000004, // FIP_KEY_FRONT_STOP, + 0x40000040, // FIP_KEY_FRONT_PAUSE, + 0x10000010, // FIP_KEY_FRONT_SKIP_PREV, + 0x20000020, // FIP_KEY_FRONT_SKIP_NEXT, + 0x02003333, // FIP_KEY_FRONT_REWIND, // --------------- + 0x02005555, // FIP_KEY_FRONT_FORWARD, // --------------- + + 0xffffffff +}; + +/// 2) definitions for the keys on the remote control: +static DWORD fip_remote_codes[] = +{ + 0x00FBF00F, // FIP_KEY_POWER, + 0x00FBB847, // FIP_KEY_EJECT, + + 0x00FBCA35, // FIP_KEY_ONE, + 0x00FBD02F, // FIP_KEY_TWO, + 0x00FB7887, // FIP_KEY_THREE, + 0x00FBC03F, // FIP_KEY_FOUR, + 0x00FB8A75, // FIP_KEY_FIVE, + 0x00FB20DF, // FIP_KEY_SIX, + 0x00FB2AD5, // FIP_KEY_SEVEN, + 0x00FB807F, // FIP_KEY_EIGHT, + 0x00FBD827, // FIP_KEY_NINE, + 0x00FB708F, // FIP_KEY_ZERO, + + 0x00FB6897, // FIP_KEY_CANCEL, + 0x00FBE21D, // FIP_KEY_SEARCH, + 0x00FBA05F, // FIP_KEY_ENTER, + + 0x00FB827D, // FIP_KEY_OSD, + 0x00FBA25D, // FIP_KEY_SUBTITLE, + 0x00FBB24D, // FIP_KEY_SETUP, + 0x00FBD22D, // FIP_KEY_RETURN, + 0x00FBF807, // FIP_KEY_TITLE, + 0x02000002, // FIP_KEY_PN, + 0x00FB42BD, // FIP_KEY_MENU, + 0x00FB28D7, // FIP_KEY_AB, + 0x00FB8877, // FIP_KEY_REPEAT, + + 0x00FB30CF, // FIP_KEY_UP, + 0x00FBF20D, // FIP_KEY_DOWN, + 0x00FB9867, // FIP_KEY_LEFT, + 0x00FB32CD, // FIP_KEY_RIGHT, + + 0x00FBC23D, // FIP_KEY_VOLUME_DOWN, + 0x00FB22DD, // FIP_KEY_VOLUME_UP, + 0x00FB629D, // FIP_KEY_PAUSE, + + 0x00FB926D, // FIP_KEY_REWIND, + 0x00FB6A95, // FIP_KEY_FORWARD, + 0x00FB38C7, // FIP_KEY_SKIP_PREV, + 0x00FB609F, // FIP_KEY_SKIP_NEXT, + + 0x00FBA857, // FIP_KEY_PLAY, + 0x00FBB04F, // FIP_KEY_STOP, + + 0x00FBE817, // FIP_KEY_SLOW, + 0x00FB728D, // FIP_KEY_AUDIO, + 0x00FBAA55, // FIP_KEY_VMODE, + 0x00FB906F, // FIP_KEY_MUTE, + 0x00FBC837, // FIP_KEY_ZOOM, + 0x00FFBABA, // FIP_KEY_PROGRAM, // -------- + 0x00FFE0E0, // FIP_KEY_PBC, // -------- + 0x00FB4AB5, // FIP_KEY_ANGLE, + + 0xffffffff +}; + +#ifdef __cplusplus +} +#endif + + +#endif // of SP_FIP_CODES_H diff --git a/src/libsp/MP/sp_fip_codes-technosonic.h b/src/libsp/MP/sp_fip_codes-technosonic.h new file mode 100644 index 0000000..85a49b2 --- /dev/null +++ b/src/libsp/MP/sp_fip_codes-technosonic.h @@ -0,0 +1,119 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - FIP button internal codes header. + * For Technosonic-compatible players ('MP') + * \file sp_fip_codes-technosonic.h + * \author bombur + * \version 0.1 + * \date 4.05.2004 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#ifndef SP_FIP_CODES_H +#define SP_FIP_CODES_H + + +#ifdef __cplusplus +extern "C" { +#endif + +//////////////////////////////////// +/// Button codes definitions: + +static DWORD fip_code_offset[2] = { FIP_KEY_FRONT_EJECT, FIP_KEY_POWER }; + +/// 1) definitions for the buttons on the front panel: +static DWORD fip_panel_codes[] = +{ + 0x08000008, // FIP_KEY_FRONT_EJECT, + 0x00000080, // FIP_KEY_FRONT_PLAY, + 0x40000040, // FIP_KEY_FRONT_STOP, + 0x04000004, // FIP_KEY_FRONT_PAUSE, + 0x02000002, // FIP_KEY_FRONT_SKIP_PREV, + 0x20000020, // FIP_KEY_FRONT_SKIP_NEXT, + 0x10000010, // FIP_KEY_FRONT_REWIND, + 0x01000001, // FIP_KEY_FRONT_FORWARD, + + 0xffffffff +}; + +/// 2) definitions for the keys on the remote control: +static DWORD fip_remote_codes[] = +{ + 0x00FF30CF, // FIP_KEY_POWER, + 0x00FFB04F, // FIP_KEY_EJECT, + + 0x00FF00FF, // FIP_KEY_ONE, + 0x00FF807F, // FIP_KEY_TWO, + 0x00FF40BF, // FIP_KEY_THREE, + 0x00FFC03F, // FIP_KEY_FOUR, + 0x00FF20DF, // FIP_KEY_FIVE, + 0x00FFA05F, // FIP_KEY_SIX, + 0x00FF609F, // FIP_KEY_SEVEN, + 0x00FFE01F, // FIP_KEY_EIGHT, + 0x00FF10EF, // FIP_KEY_NINE, + 0x00FF906F, // FIP_KEY_ZERO, + + 0x00FF50AF, // FIP_KEY_CANCEL, + 0x00FFD02F, // FIP_KEY_SEARCH, + 0x00FF708F, // FIP_KEY_ENTER, + + 0x00FF7887, // FIP_KEY_OSD, + 0x00FFF807, // FIP_KEY_SUBTITLE, + 0x00FF38C7, // FIP_KEY_SETUP, + 0x00FFB847, // FIP_KEY_RETURN, + 0x00FF28D7, // FIP_KEY_TITLE, + 0x00FFA857, // FIP_KEY_PN, + 0x00FF6897, // FIP_KEY_MENU, + 0x00FFE817, // FIP_KEY_AB, + 0x00FF18E7, // FIP_KEY_REPEAT, + + 0x00FF08F7, // FIP_KEY_UP, + 0x00FF8877, // FIP_KEY_DOWN, + 0x00FF48B7, // FIP_KEY_LEFT, + 0x00FFC837, // FIP_KEY_RIGHT, + + 0x00FFD827, // FIP_KEY_VOLUME_DOWN, + 0x00FF58A7, // FIP_KEY_VOLUME_UP, + 0x00FF9867, // FIP_KEY_PAUSE, + + 0x00FF02FD, // FIP_KEY_REWIND, + 0x00FF827D, // FIP_KEY_FORWARD, + 0x00FF42BD, // FIP_KEY_SKIP_PREV, + 0x00FFC23D, // FIP_KEY_SKIP_NEXT, + + 0x00FF22DD, // FIP_KEY_PLAY, + 0x00FFA25D, // FIP_KEY_STOP, + + 0x00FF12ED, // FIP_KEY_SLOW, + 0x00FF926D, // FIP_KEY_AUDIO, + 0x00FF52AD, // FIP_KEY_VMODE, + 0x00FFD22D, // FIP_KEY_MUTE, + 0x00FF32CD, // FIP_KEY_ZOOM, + 0x00FFB24D, // FIP_KEY_PROGRAM, + 0x00FF728D, // FIP_KEY_PBC, + 0x00FFF20D, // FIP_KEY_ANGLE, + + 0xffffffff +}; + +#ifdef __cplusplus +} +#endif + + +#endif // of SP_FIP_CODES_H diff --git a/src/libsp/MP/sp_fip_ioctl.h b/src/libsp/MP/sp_fip_ioctl.h new file mode 100644 index 0000000..72becce --- /dev/null +++ b/src/libsp/MP/sp_fip_ioctl.h @@ -0,0 +1,63 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - Internal FIP IOCTL codes. + * For Technosonic-compatible players ('MP') + * \file sp_fip_ioctl.h + * \author bombur + * \version 0.1 + * \date 4.05.2004 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#ifndef SP_FIP_IOCTL_H +#define SP_FIP_IOCTL_H + +#ifdef __cplusplus +extern "C" { +#endif + +//////////////////////////////////// +/// Driver I/O defines: + + +/// Repeats current button press. +/// The button to be received is set to be received twice. +/// This will destroy unread queued up presses. +#define FIP_BUTTON_REPEAT 0x450000 + +/// Controls the display. +/// Used for setting the pulse width and turning the display on and off. +/// The flags you wish to set on the display where flags is one of the +/// FIP_PULSE_ defines. +#define FIP_DISPLAY_CONTROL 0x450001 + +/// Description: Displays the symbol on the display. +/// Sets given symbol data at given position: (data | (pos << 16)). +#define FIP_DISPLAY_SYMBOL 0x450002 + +/// Reads a button code (like getchar()). Can be blocked or not. +/// The button code is returned. +#define FIP_BUTTON_READ 0x450003 + +// #define FIP_??? 0x450004 + +#ifdef __cplusplus +} +#endif + + +#endif // of SP_FIP_IOCTL_H diff --git a/src/libsp/MP/sp_i2c.cpp b/src/libsp/MP/sp_i2c.cpp new file mode 100644 index 0000000..30bad99 --- /dev/null +++ b/src/libsp/MP/sp_i2c.cpp @@ -0,0 +1,93 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - I2C data transfer functions source file. + * For Technosonic-compatible players ('MP') + * \file sp_i2c.cpp + * \author bombur + * \version 0.1 + * \date 1.02.2005 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include + +#include "sp_misc.h" +#include "sp_io.h" +#include "sp_i2c.h" + +#include "arch-jasper/hardware.h" + + +BOOL i2c_data_in(BYTE addr, BYTE idx, BYTE *data, int num) +{ + outl(375, JASPER_I2C_MASTER_BASE+I2C_MASTER_CLK_DIV); + while ((inl(JASPER_I2C_MASTER_BASE+I2C_MASTER_STATUS) & 1) == 0) + ; + + for (int i = 0; i < num; i++) + { + outl(250, JASPER_I2C_MASTER_BASE+I2C_MASTER_CONFIG); + outl(0, JASPER_I2C_MASTER_BASE+I2C_MASTER_BYTE_COUNT); + outl(addr/2, JASPER_I2C_MASTER_BASE+I2C_MASTER_DEV_ADDR); + outl(idx++, JASPER_I2C_MASTER_BASE+I2C_MASTER_DATAOUT); + outl(4, JASPER_I2C_MASTER_BASE+I2C_MASTER_STARTXFER); + + while ((inl(JASPER_I2C_MASTER_BASE+I2C_MASTER_STATUS) & 2) == 0) + ; + while ((inl(JASPER_I2C_MASTER_BASE+I2C_MASTER_STATUS) & 1) == 0) + ; + + outl(250, JASPER_I2C_MASTER_BASE+I2C_MASTER_CONFIG); + outl(0, JASPER_I2C_MASTER_BASE+I2C_MASTER_BYTE_COUNT); + outl(addr/2, JASPER_I2C_MASTER_BASE+I2C_MASTER_DEV_ADDR); + outl(1, JASPER_I2C_MASTER_BASE+I2C_MASTER_STARTXFER); + + while ((inl(JASPER_I2C_MASTER_BASE+I2C_MASTER_STATUS) & 4) == 0) + ; + while ((inl(JASPER_I2C_MASTER_BASE+I2C_MASTER_STATUS) & 1) == 0) + ; + + *data++ = inl(JASPER_I2C_MASTER_BASE+I2C_MASTER_DATAIN); + } + return TRUE; +} + +BOOL i2c_data_out(BYTE addr, BYTE idx, BYTE *data, int num) +{ + outl(248, JASPER_I2C_MASTER_BASE+I2C_MASTER_CONFIG); + outl(375, JASPER_I2C_MASTER_BASE+I2C_MASTER_CLK_DIV); + outl(addr/2, JASPER_I2C_MASTER_BASE+I2C_MASTER_DEV_ADDR); + + while ((inl(JASPER_I2C_MASTER_BASE+I2C_MASTER_STATUS) & 1) == 0) + ; + + for (int i = 0; i < num; i++) + { + outl(idx++, JASPER_I2C_MASTER_BASE+I2C_MASTER_ADR); + outl(0, JASPER_I2C_MASTER_BASE+I2C_MASTER_BYTE_COUNT); + outl(*data++, JASPER_I2C_MASTER_BASE+I2C_MASTER_DATAOUT); + outl(0, JASPER_I2C_MASTER_BASE+I2C_MASTER_STARTXFER); + + while ((inl(JASPER_I2C_MASTER_BASE+I2C_MASTER_STATUS) & 2) == 0) + ; + } + return TRUE; +} + diff --git a/src/libsp/MP/sp_khwl.cpp b/src/libsp/MP/sp_khwl.cpp new file mode 100644 index 0000000..dd0c862 --- /dev/null +++ b/src/libsp/MP/sp_khwl.cpp @@ -0,0 +1,360 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - KHWL interface functions source file. + * For Technosonic-compatible players ('MP') + * \file sp_khwl.cpp + * \author bombur + * \version 0.1 + * \date 4.07.2004 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include + +#include "sp_misc.h" +#include "sp_khwl.h" +#include "sp_khwl_ioctl.h" +#include "sp_mpeg.h" +#include "sp_fip.h" +#include "sp_io.h" +#include "sp_i2c.h" + +#include "arch-jasper/hardware.h" + + +/// Muxed media packets +MpegPlayStruct *MPEG_PLAY_STRUCT = (MpegPlayStruct *)(0x17c0000 + 0x3fc00 + 768); +/// Video packets +MpegPlayStruct *MPEG_VIDEO_STRUCT = (MpegPlayStruct *)(0x2000000 - 0x800000 - 232); +/// Audio packets +MpegPlayStruct *MPEG_AUDIO_STRUCT = (MpegPlayStruct *)(0x2000000 - 0x800000 - 208); +/// Subpicture packets +MpegPlayStruct *MPEG_SPU_STRUCT = (MpegPlayStruct *)(0x2000000 - 0x800000 - 184); + +/// Buffers storage +BYTE *BUF_BASE = (BYTE *)0x01680000; +//BYTE *BUF_BASE = (BYTE *)0x016c0000; + + +/// KHWL module handle +int khwl_handle = -1; + +/// OSD properties used +KHWL_OSDSTRUCT osd = { NULL }; + +/// insmod flag +static BOOL module_applied = FALSE; + +/// If digital TV (HDTV) is installed. +static BOOL digital_tv = FALSE; + +int FRAME_WIDTH = 720; +int FRAME_HEIGHT = 480; + + +BOOL khwl_init(BOOL applymodule) +{ + khwl_inityuv(); + khwl_deinit(); + + if (module_applied == TRUE || applymodule == FALSE || module_apply("/drivers/khwl.o")) // insert + { + module_applied = TRUE; + + khwl_handle = open("/dev/realmagichwl0", 0, 0); + if (khwl_handle != -1) + { + //khwl_reset(); + + if (!khwl_restoreparams()) + return FALSE; + + return TRUE; + } + } + return FALSE; +} + +BOOL khwl_restoreparams() +{ + int val = 0; + khwl_setproperty(KHWL_VIDEO_SET, evMacrovisionFlags, sizeof(int), &val); + + int flickerval = 15; // flicker = max + khwl_setproperty(KHWL_DECODER_SET, edecOsdFlicker, sizeof(int), &flickerval); + + KHWL_WINDOW wnd, osdwnd; + khwl_getproperty(KHWL_VIDEO_SET, evMaxDisplayWindow, sizeof(wnd), &wnd); + khwl_setproperty(KHWL_VIDEO_SET, evDestinationWindow, sizeof(wnd), &wnd); + + osdwnd = wnd; + osdwnd.h = 30 * wnd.h / 31; + khwl_setproperty(KHWL_OSD_SET, eOsdDestinationWindow, sizeof(osdwnd), &osdwnd); + + val = 0; + khwl_setproperty(KHWL_VIDEO_SET, evForcedProgressiveAlways, sizeof(int), &val); + + KHWL_YUV_WRITE_PARAMS_TYPE yuvparams; + yuvparams.wWidth = wnd.w; + yuvparams.wHeight = wnd.h; + yuvparams.YUVFormat = KHWL_YUV_420_UNPACKED; + + khwl_setproperty(KHWL_VIDEO_SET, evYUVWriteParams, sizeof(yuvparams), &yuvparams); + + // do some HDTV-specific checks... + digital_tv = FALSE; + if ((inl(JASPER_QUASAR_BASE + QUASAR_DRAM_STARTUP1) & 0x2000) != 0) + { + BYTE data[4]; + // test for HDTV 1080I + i2c_data_in(112, 0, data, 4); + if (data[0] == 1 && data[1] == 0 && data[2] == 8 && data[3] == 0) + digital_tv = TRUE; + } + + return TRUE; +} + +BOOL khwl_deinit() +{ + if (khwl_handle == -1) + return FALSE; + close(khwl_handle); + khwl_handle = -1; + return TRUE; +} + +BOOL khwl_reset() +{ + if (khwl_handle == -1) + return FALSE; + return ioctl(khwl_handle, KHWL_HARDRESET, 0); +} + +int khwl_osd_switch(KHWL_OSDSTRUCT *_osd, BOOL autoupd) +{ + KHWL_WINDOW wnd; + int ret; + if (khwl_handle == -1) + return FALSE; + osd.flags = autoupd ? 1 : 0; + ret = ioctl(khwl_handle, KHWL_OSDFB_SWITCH, &osd); + + if (_osd != NULL) + { + khwl_getproperty(KHWL_VIDEO_SET, evMaxDisplayWindow, sizeof(wnd), &wnd); + khwl_setproperty(KHWL_OSD_SET, eOsdDestinationWindow, sizeof(wnd), &wnd); + *_osd = osd; + } + return ret; +} + +void khwl_get_osd_size(int *width, int *height) +{ + *width = 640;//osd.width; + *height = 480;//osd.height; +} + + +BOOL khwl_osd_update() +{ + ioctl(khwl_handle, KHWL_OSDFB_UPDATE, 0); + return TRUE; +} + +int khwl_osd_setalpha(int alpha) +{ + return ioctl(khwl_handle, KHWL_OSDFB_ALPHA, &alpha); +} + +void khwl_osd_setpalette(BYTE *pal, int entry, BYTE r, BYTE g, BYTE b, BYTE a) +{ + // assert(entry >= 0 && entry < 256); + BYTE y, u, v; + entry <<= 2; + khwl_vgargbtotvyuv(r, g, b, &y, &u, &v); + pal[entry++] = a; // alpha + pal[entry++] = y; + pal[entry++] = u; + pal[entry] = v; +} + +void khwl_osd_setfullscreen(BOOL is) +{ + KHWL_WINDOW wnd, osdwnd; + khwl_getproperty(KHWL_VIDEO_SET, evMaxDisplayWindow, sizeof(wnd), &wnd); + osdwnd = wnd; + if (!is) + osdwnd.h = 30 * wnd.h / 31; + khwl_setproperty(KHWL_OSD_SET, eOsdDestinationWindow, sizeof(osdwnd), &osdwnd); +} + +int khwl_displayYUV(KHWL_YUV_FRAME *f) +{ + int ret = ioctl(khwl_handle, KHWL_DISPLAY_YUV, f); + return ret; +} + +int khwl_setproperty(KHWL_PROPERTY_SET pset, int id, int size, void *value) +{ + KHWL_PROPERTY p; + p.pset = pset; + p.id = id; + p.size = size; + p.v = value; + return ioctl(khwl_handle, KHWL_SETPROP, &p); +} + +int khwl_getproperty(KHWL_PROPERTY_SET pset, int id, int size, void *value) +{ + KHWL_PROPERTY p; + p.pset = pset; + p.id = id; + p.size = size; + p.v = value; + return ioctl(khwl_handle, KHWL_GETPROP, &p); +} + +int khwl_audioswitch(BOOL ison) +{ + int val = ison ? 1 : 0; + return ioctl(khwl_handle, KHWL_AUDIOSWITCH, &val); +} + +int *khwl_get_samplerates() +{ + static int rates[2][12] = + { + // chip rev.A + { 16000, 22050, 24000, 32000, 44100, 48000, -1 }, + // chip rev.B supports more samplerates + { 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000, 64000, 88200, 96000, -1 }, + }; + return rates[inl(SYS_REVID_REG) & 1]; +} + +int khwl_play(int mode) +{ + return ioctl(khwl_handle, KHWL_PLAY, &mode); +} + +int khwl_stop() +{ + return ioctl(khwl_handle, KHWL_STOP, 0); +} + +int khwl_pause() +{ + return ioctl(khwl_handle, KHWL_PAUSE, 0); +} + +BOOL khwl_blockirq(BOOL block) +{ + unsigned irqstat; + irqstat = inl(JASPER_INT_CONTROLLER_BASE + INT_INTEN); + if (block) + irqstat &= ~Q2H_RISC_INT; + else + irqstat |= Q2H_RISC_INT; + outl(irqstat, JASPER_INT_CONTROLLER_BASE + INT_INTEN); + return TRUE; +} + +BOOL khwl_happeningwait(DWORD *mask) +{ + KHWL_WAITABLE w; + w.mask = *mask; + w.timeout_microsecond = 400000; + ioctl(khwl_handle, KHWL_WAIT, &w); + *mask = w.mask; + return TRUE; +} + +BOOL khwl_poll(WORD mask, DWORD timeout) +{ + pollfd pfd; + pfd.events = mask; + pfd.fd = khwl_handle; + return poll(&pfd, 1, timeout) >= 0; +} + +BOOL khwl_ideswitch(BOOL ison) +{ + KHWL_ADDR_DATA data; + // switch IDE? + data.Addr = 4; + data.Data = ison ? 1 : 0; + khwl_setproperty(KHWL_BOARDINFO_SET, ebiPIOAccess, sizeof(data), &data); + return TRUE; +} + +int khwl_getfrequency() +{ + int temp = inl(JASPER_QUASAR_BASE + QUASAR_DRAM_PLLCONTROL); + + int div = (temp >> 8) & 0xff; + int mul = (temp >> 2) & 63; + + int freq = (27 * (mul + 2)) / ((div + 2) * 2); + return freq; +} + +BOOL khwl_setfrequency(int freq) +{ + if (freq < 100 || freq > 202) + return FALSE; + + int temp = inl(JASPER_QUASAR_BASE + QUASAR_DRAM_PLLCONTROL); + temp = temp & 0xF000; + int div = (temp >> 8) & 0xff; + //int mul = (temp >> 2) & 63; + + int mul = (freq * ((10 * div+20)*20) / 270 - 20 /* + 5 */) / 10; + if (mul < 0 || mul > 63) + return FALSE; + + temp = temp | 0x02; + temp = temp | (div << 8); + temp = temp | (mul << 2); + + outl(temp & 0x7FFF, JASPER_QUASAR_BASE + QUASAR_DRAM_PLLCONTROL); + return TRUE; +} + +char *khwl_gethw() +{ + static char hw[256]; +/* + char b[128]; + b[0] = '\0'; + khwl_getproperty(KHWL_BOARDINFO_SET, ebiBoardNameString, 256, b); + + DWORD v_ebiDeviceId, v_ebiSubId, v_ebiBoardVersion, v_ebiHwLibVersion, v_ebiUcodeVersion; + khwl_getproperty(KHWL_BOARDINFO_SET, ebiDeviceId, sizeof(DWORD), &v_ebiDeviceId); + khwl_getproperty(KHWL_BOARDINFO_SET, ebiSubId, sizeof(DWORD), &v_ebiSubId); + khwl_getproperty(KHWL_BOARDINFO_SET, ebiBoardVersion, sizeof(DWORD), &v_ebiBoardVersion); + khwl_getproperty(KHWL_BOARDINFO_SET, ebiHwLibVersion, sizeof(DWORD), &v_ebiHwLibVersion); + khwl_getproperty(KHWL_BOARDINFO_SET, ebiUcodeVersion , sizeof(DWORD), &v_ebiUcodeVersion); +*/ + sprintf(hw, "%X.%c", inl(SYS_CHIPID_REG), inl(SYS_REVID_REG) + 'A'); + + return hw; +} diff --git a/src/libsp/MP/sp_khwl_ioctl.h b/src/libsp/MP/sp_khwl_ioctl.h new file mode 100644 index 0000000..3822c5d --- /dev/null +++ b/src/libsp/MP/sp_khwl_ioctl.h @@ -0,0 +1,65 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - Internal KHWL driver's IOCTL codes. + * For Technosonic-compatible players ('MP') + * \file sp_khwl_ioctl.h + * \author bombur + * \version 0.1 + * \date 4.07.2004 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#ifndef SP_KHWL_IOCTL_H +#define SP_KHWL_IOCTL_H + +#ifdef __cplusplus +extern "C" { +#endif + +//////////////////////////////////// +/// Driver I/O defines: + +#define KHWL_IO_BASE 0x3cc +#define KHWL_IO_PLAYER 0x3d0 +#define KHWL_IO_PROP 0x3d4 +#define KHWL_IO_OSD 0x3e0 +#define KHWL_IO_DISPLAY 0x3e3 + + +#define KHWL_HARDRESET KHWL_IO_BASE+3 + +#define KHWL_PLAY KHWL_IO_PLAYER +#define KHWL_STOP KHWL_IO_PLAYER+1 +#define KHWL_PAUSE KHWL_IO_PLAYER+2 +#define KHWL_AUDIOSWITCH KHWL_IO_PLAYER+3 + +#define KHWL_WAIT KHWL_IO_PROP+1 +#define KHWL_SETPROP KHWL_IO_PROP+2 +#define KHWL_GETPROP KHWL_IO_PROP+3 + +#define KHWL_OSDFB_SWITCH KHWL_IO_OSD +#define KHWL_OSDFB_UPDATE KHWL_IO_OSD+1 +#define KHWL_OSDFB_ALPHA KHWL_IO_OSD+2 // general alpha apply + +#define KHWL_DISPLAY_CLEAR KHWL_IO_DISPLAY +#define KHWL_DISPLAY_YUV KHWL_IO_DISPLAY+1 + +#ifdef __cplusplus +} +#endif + +#endif // of SP_HWL_IOCTL_H diff --git a/src/libsp/MP/sp_module.cpp b/src/libsp/MP/sp_module.cpp new file mode 100644 index 0000000..c18db37 --- /dev/null +++ b/src/libsp/MP/sp_module.cpp @@ -0,0 +1,198 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - Module system support source file. + * For Technosonic-compatible players ('MP') + * \file sp_module.cpp + * \author bombur + * \version 0.1 + * \date 10.12.2008 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +extern "C" +{ + +// portions of code from uClibc... +#ifdef COMPILE_MODULE + +void (*__app_fini)(void) = NULL; + +extern int main(int argc, char **argv, char **envp); + + +void __attribute__ ((__noreturn__)) +__uClibc_start_main(int argc, char **argv, char **envp, + void (*app_init)(void), void (*app_fini)(void)) +{ + /* Arrange for the application's dtors to run before we exit. */ + __app_fini = app_fini; + + /* Run all the application's ctors now. */ + if (app_init!=NULL) { + app_init(); + } + + exit(main(argc, argv, envp)); +} + +void exit(int rv) +{ + if (__app_fini != NULL) + (__app_fini)(); + + _exit(rv); +} + +void module_wait() +{ + kill(getpid(), SIGSTOP); +} + +/* Copy SRC to DEST. */ +char *strcpy (char *dest, const char *src) +{ + char c; + char *s = (char *) (src); + const int off = (dest) - s - 1; + + do + { + c = *s++; + s[off] = c; + } + while (c != '\0'); + + return dest; +} + +void abort(void) +{ + struct sigaction act; + sigset_t sset; + + (void) fflush (NULL); + + if ((sigemptyset (&sset) == 0) && (sigaddset (&sset, SIGABRT) == 0)) { + (void) sigprocmask (SIG_UNBLOCK, &sset, (sigset_t *) NULL); + } + + raise(SIGABRT); + + memset (&act, '\0', sizeof (struct sigaction)); + act.sa_handler = SIG_DFL; + sigfillset (&act.sa_mask); + act.sa_flags = 0; + sigaction (SIGABRT, &act, NULL); + + raise (SIGABRT); + + _exit (127); +} + +int atexit(void (*) (void)) +{ + // empty + return 0; +} + + +#undef LOAD_ARGS_1 +#define LOAD_ARGS_1(a1) \ + _a1 = (int) (a1); +#undef ASM_ARGS_1 +#define ASM_ARGS_1 , "r" (_a1) +#ifndef SYS_ify +# define SYS_ify(syscall_name) (__NR_##syscall_name) +#endif + +#undef INLINE_SYSCALL +#define INLINE_SYSCALL(name, nr, args...) \ + ({ unsigned int _sys_result; \ + { \ + register int _a1 asm ("a1"); \ + LOAD_ARGS_##nr (args) \ + asm volatile ("swi %1 @ syscall " #name \ + : "=r" (_a1) \ + : "i" (SYS_ify(name)) ASM_ARGS_##nr \ + : "memory"); \ + _sys_result = _a1; \ + } \ + (int) _sys_result; }) + + +void _exit(int status) +{ + INLINE_SYSCALL(exit, 1, status); +} + + +#else ///////////////////// server side + +int module_binary_load(char *fname, char *arg) +{ + char *args[3]; + args[0] = fname; + args[1] = arg; + args[2] = NULL; + + return exec_file(args[0], (const char **)args); +} + +int module_binary_unload(int pid) +{ + // kill it by force + kill(pid, SIGKILL); + int pstat; + waitpid(pid, &pstat, 0); + return 0; +} + +void module_copy_func(void *from, void *to) +{ + #define JMP_OPCODE_SIZE 2 + DWORD diff = (((DWORD)from)/4 - (((DWORD)to)/4 + JMP_OPCODE_SIZE)); + *((DWORD *)to) = 0xEA000000 | (diff & 0xFFFFFF); +} + +void module_clear_func(void *func) +{ + *((DWORD *)func) = 0; +} + + +#endif + +} diff --git a/src/libsp/MP/sp_module_crt0.S b/src/libsp/MP/sp_module_crt0.S new file mode 100644 index 0000000..7478630 --- /dev/null +++ b/src/libsp/MP/sp_module_crt0.S @@ -0,0 +1,125 @@ +/* When we enter this piece of code, the program stack looks like this: + argc argument counter (integer) + argv[0] program name (pointer) + argv[1...N] program args (pointers) + argv[argc-1] end of args (integer) + NULL + env[0...N] environment variables (pointers) + NULL + + For uClinux it looks like this: + + argc argument counter (integer) + argv char *argv[] + envp char *envp[] + argv[0] program name (pointer) + argv[1...N] program args (pointers) + argv[argc-1] end of args (integer) + NULL + env[0...N] environment variables (pointers) + NULL + + When we are done here, we want + a1=argc + a2=argv[0] + a3=argv[argc+1] + +ARM register quick reference: + + Name Number ARM Procedure Calling Standard Role + + a1 r0 argument 1 / integer result / scratch register / argc + a2 r1 argument 2 / scratch register / argv + a3 r2 argument 3 / scratch register / envp + a4 r3 argument 4 / scratch register + v1 r4 register variable + v2 r5 register variable + v3 r6 register variable + v4 r7 register variable + v5 r8 register variable + sb/v6 r9 static base / register variable + sl/v7 r10 stack limit / stack chunk handle / reg. variable + fp r11 frame pointer + ip r12 scratch register / new-sb in inter-link-unit calls + sp r13 lower end of current stack frame + lr r14 link address / scratch register + pc r15 program counter +*/ + +#include + +.text + .global _start + .type _start,%function +#if ! defined __UCLIBC_CTOR_DTOR__ + .type __uClibc_main,%function +#else + .weak _init + .weak _fini + .type __uClibc_start_main,%function +#endif +/* Stick in a dummy reference to main(), so that if an application + * is linking when the main() function is in a static library (.a) + * we can be sure that main() actually gets linked in */ + .type main,%function + + +.text +_start: + /* clear the frame pointer */ + mov fp, #0 + +#ifdef __UCLIBC_HAS_MMU__ + /* Load register r0 (argc) from the stack to its final resting place */ + ldr r0, [sp], #4 + + /* Copy argv pointer into r1 -- which its final resting place */ + mov r1, sp + + /* Skip to the end of argv and put a pointer to whatever + we find there (hopefully the environment) in r2 */ + add r2, r1, r0, lsl #2 + add r2, r2, #4 + +#else + /* + * uClinux stacks look a little different from normal + * MMU-full Linux stacks (for no good reason) + */ + /* pull argc, argv and envp off the stack */ + ldr r0,[sp, #0] + ldr r1,[sp, #4] + ldr r2,[sp, #8] +#endif + +#if defined __UCLIBC_CTOR_DTOR__ + /* Store the address of _init in r3 as an argument to main() */ + ldr r3, =_init + + /* Push _fini onto the stack as the final argument to main() */ + ldr r4, =_fini + stmfd sp!, {r4} + + /* Ok, now run uClibc's main() -- shouldn't return */ + bl __uClibc_start_main +#else + bl __uClibc_main +#endif + + /* Crash if somehow `exit' returns anyways. */ + bl abort + +/* We need this stuff to make gdb behave itself, otherwise + gdb will chokes with SIGILL when trying to debug apps. +*/ + .section ".note.ABI-tag", "a" + .align 4 + .long 1f - 0f + .long 3f - 2f + .long 1 +0: .asciz "GNU" +1: .align 4 +2: .long 0 + .long 2,0,0 +3: .align 4 + diff --git a/src/libsp/containers/clist.h b/src/libsp/containers/clist.h new file mode 100644 index 0000000..21f4c41 --- /dev/null +++ b/src/libsp/containers/clist.h @@ -0,0 +1,494 @@ +////////////////////////////////////////////////////////////////////////// +/** + * support lib linear classic (new-based) dynamic list template prototype + * \file libsp/containers/clist.h + * \author bombur + * \version x.xx + * \date xx.xx.xxxx + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#ifndef SP_CLIST_H +#define SP_CLIST_H + +const int SP_CLIST_BUFFER_SIZE = 2; +#define GROW(num) (9 * num / 8) + +/// Dynamic classic linear list template ('vector') +/// Does all needed classes handling correctly (but a little bit slower)! +/// \warning: Only single-threaded version!!! +/// \warning: List resizing is slower than SPList version! +/// \warning: Copying lists is SLOW 'cause copy ctors are called for items. +template +class SPClassicList +{ +public: + + /// comparison function prototype (see Sort()) + typedef int (*SPClassicListCompareFunc)(T *e1, T *e2); + + /// Ctor. Creates empty list + SPClassicList () + { + l = new T [SP_CLIST_BUFFER_SIZE]; + //SPCHECKMSG(l != NULL, "Memory error"); + lastnum = num = 0; + } + + /// Ctor. Creates list with n elements. + SPClassicList (int n) + { + lastnum = num = n; + l = new T [lastnum + SP_CLIST_BUFFER_SIZE]; + } + + /// Copy ctor. + SPClassicList (const SPClassicList & list) + { + lastnum = num = list.num; + l = new T [lastnum + SP_CLIST_BUFFER_SIZE]; + //SPCHECKMSG(l != NULL, "Memory error"); + if (num > 0) + { + for (int i = 0; i < num; i++) + { + l[i] = list.l[i]; // call elements' copy ctors + } + } + } + + SPClassicList & operator = (const SPClassicList & list) + { + lastnum = num = list.num; + delete [] l; + l = new T [lastnum + SP_CLIST_BUFFER_SIZE]; + //SPCHECKMSG(l != NULL, "Memory error"); + if (num > 0) + { + for (int i = 0; i < num; i++) + { + l[i] = list.l[i]; // call elements' copy ctors + } + } + return (*this); + } + + /// Dtor. Destroys only the list, not the elements. + ~SPClassicList () + { + SPSafeDeleteArray(l); + lastnum = num = 0; + } + + /// Reserve memory for N items + BOOL Reserve(int n) + { + lastnum = n + 1; + + T *newl = new T [lastnum + SP_CLIST_BUFFER_SIZE]; + for (int i = 0; i < num; i++) + { + newl[i] = l[i]; // call elements' copy ctors + } + delete [] l; + l = newl; + + if (l == NULL) + { + SPERRMES("Memory error"); + return FALSE; + } + return TRUE; + } + + /// Add item to the end of the list + int Add (const T & item) + { + if (num + 1 >= lastnum + SP_CLIST_BUFFER_SIZE) // need to expand buffer + { + lastnum = GROW(num) + 1; + + T *newl = new T [lastnum + SP_CLIST_BUFFER_SIZE]; + for (int i = 0; i < num; i++) + { + newl[i] = l[i]; // call elements' copy ctors + } + delete [] l; + l = newl; + + if (l == NULL) + { + SPERRMES("Memory error"); + return -1; + } + } + l[num] = item; + return num++; + } + + /// Add one list to the end of another list. + /// Returns the last element added. + int Add (const SPClassicList & list) + { + int ret = -1; + for (int i = 0; i < list.GetN(); i++) + ret = Add (list[i]); + return ret; + } + + /// Add unique item to the list, and return the existing or new index. + int Merge (const T & item) + { + int g = Get (item); + if (g == -1) + return Add (item); + return g; + } + + /// Merge lists (avoid item duplicates) + int Merge (const SPClassicList & list) + { + int ret = -1; + for (int i = 0; i < list.GetN(); i++) + { + T item = list[i]; + if (Get (item) == -1) + ret = Add (item); + } + return ret; + } + + /// Insert the 'item' element to the 'where' position in the list + bool Insert (T item, int where) + { + int n = num; + if (where < 0) + return FALSE; + if (where > (int)num) + n = where; + + if (n + 1 >= lastnum + SP_CLIST_BUFFER_SIZE) // need to expand buffer + { + lastnum = n + 1; + + T *newl = new T [lastnum + SP_CLIST_BUFFER_SIZE]; + for (int i = 0; i < num; i++) + { + newl[i] = l[i]; // call elements' copy ctors + } + delete [] l; + l = newl; + + if (l == NULL) + { + SPERRMES("Memory error"); + return FALSE; + } + } + + register int i; + for (i = num; i > where; i--) + l[i] = l[i - 1]; + + l[where] = item; + num = n + 1; + return TRUE; + } + + /// Replace item with new one; expand list if needed. + bool Put (T item, int where) + { + if (where < 0) + return FALSE; + if (where >= (int)num) + { + num = where + 1; + if (num >= lastnum + SP_CLIST_BUFFER_SIZE) // need to expand buffer + { + lastnum = num; + + T *newl = new T [lastnum + SP_CLIST_BUFFER_SIZE]; + for (int i = 0; i < num; i++) + { + newl[i] = l[i]; // call elements' copy ctors + } + delete [] l; + l = newl; + + if (l == NULL) + { + SPERRMES("Memory error"); + return FALSE; + } + } + } + l[where] = item; + return TRUE; + } + + /// Find item's index number + int Get (const T & item, int idx1 = 0, int idx2 = -1) + { + if (idx1 < 0) + return -1; + if (idx2 < 0) idx2 = num - 1; + for (int i = idx1; i <= idx2; i++) + if (l[i] == item) + return i; + return -1; + } + + /// Remove the item from the list + bool Remove (int n) + { + if (n < 0 || n >= (int)num) + return FALSE; + for (int i = n; i < num - 1; i++) + l[i] = l[i + 1]; + if (num >= 1) + { + if (num - 1 < lastnum) // need to shrink buffer + { + T *newl = new T [lastnum]; + for (int i = 0; i < num-1; i++) + { + newl[i] = l[i]; // call elements' copy ctors + } + delete [] l; + l = newl; + + if (lastnum > SP_CLIST_BUFFER_SIZE) + lastnum -= SP_CLIST_BUFFER_SIZE; + else + lastnum = 0; + if (l == NULL) + { + SPERRMES("Memory error"); + return FALSE; + } + } + num--; + return TRUE; + } + return FALSE; + } + + /// Remove item from the list (search used) + bool Remove (const T & item) + { + int i; + while ((i = Get (item)) != -1) + { + if (!Remove (i)) + return FALSE; + } + return TRUE; + } + + /// Remove elements from 'this' list found in the 'list'. + /// Returns the number of elements removed. + int Remove (const SPClassicList & list) + { + int numdel = 0; + for (int i = 0; i < list.GetN(); i++) + { + int ret = Get (list[i]); + if (ret != -1) + { + Remove (ret); + numdel++; + } + } + return numdel; + } + + /// Remove all items from the list + bool Clear () + { + delete [] l; + l = new T [SP_CLIST_BUFFER_SIZE]; + if (l == NULL) + return FALSE; + lastnum = num = 0; + return TRUE; + } + + /// Moves item in the list (delete + insert) + bool Move (int from, int to) + { + if (from < 0 || from >= (int)num) + return FALSE; + T item = l[from]; + if (Remove (from) == FALSE) + return FALSE; + if (Insert (item, to) == FALSE) + return FALSE; + return TRUE; + } + + /// Delete all objects themself and clear the list + bool DeleteObjects () + { + for (int i = 0; i < num; i++) + SPSafeDelete(l[i]); + return Clear (); + } + + /// The main item access operator + FORCEINLINE T & operator [] (int n) const + { + return l[n]; + } + + /// List concatenation and assign + SPClassicList & operator += (const SPClassicList & list) + { + Add (list); + return *this; + } + + /// Boolean 'and' operation - returns elements present in both of the lists + SPClassicList & operator &= (const SPClassicList & list) + { + for (int i = 0; i < num; i++) + { + if (list.Get(l[i]) == -1) + this->Delete (i); + } + return *this; + } + + /// Boolean 'or' operation - returns unique elements present in any of the lists + SPClassicList & operator |= (const SPClassicList & list) + { + Merge(list); + return *this; + } + + /// Add one list to another and return the sum. + SPClassicList operator + (const SPClassicList & list) + { + SPClassicList r(*this); + r.Add(list); + return r; + } + + /// Return the elements of the first list not present in the second. + SPClassicList operator - (const SPClassicList & list) + { + SPClassicList r; + for (int i = 0; i < num; i++) + { + if (list.Get(l[i]) == -1) + r.Add (l[i]); + } + return r; + } + + /// Sort list using user-defined comparison function + void Sort (SPClassicListCompareFunc compare) + { + qsort((void *)l, num, sizeof(T), + (int (/*__cdecl*/ *)(const void *, const void *))compare); + } + + /// Get items number (count) + FORCEINLINE const int & GetN () const + { + return num; + } + + /// Set items count (reallocate the memory but don't change the data) + BOOL SetN(int n) + { + int oldnum = num; + num = (int)n; + if ((num >= lastnum + SP_CLIST_BUFFER_SIZE) || // need to expand buffer + (num <= lastnum - SP_CLIST_BUFFER_SIZE)) // need to shrink buffer + { + lastnum = num; + T *newl = new T [lastnum + SP_CLIST_BUFFER_SIZE]; + for (int i = 0; i < (n < oldnum ? n : oldnum); i++) + { + newl[i] = l[i]; // call elements' copy ctors + } + delete [] l; + l = newl; + + if (l == NULL) + { + SPERRMES("Memory error"); + return FALSE; + } + } + return TRUE; + } + + // Set items count and clear them + BOOL SetClearN(int n) + { + if (!SetN(n)) + return FALSE; + memset(l, 0, n * sizeof(T)); + return TRUE; + } + + /// This is 'hack' method used by fast-loading operations + T *GetData() const + { + return l; + } + + /// List concatenation operator + template friend SPClassicList operator + (const SPClassicList &, const SPClassicList &); + /// Boolean 'or' operation - returns unique elements present in any of the lists + template friend SPClassicList operator | (const SPClassicList &, const SPClassicList &); + /// Boolean 'and' operation - returns elements present in both of the lists + template friend SPClassicList operator & (const SPClassicList &, const SPClassicList &); + +protected: + /// Dynamic array, contains all data + T *l; + /// Number of stored items. Use GetN() for retrieving + int num; + int lastnum; /// used for buffered memory reallocs +}; + +/// List concatenation operator +template inline SPClassicList operator + (const SPClassicList &a, const SPClassicList &b) +{ + return (SPClassicList (a) += (b)); +} + +/// Boolean 'or' operation - returns unique elements present in any of the lists +template inline SPClassicList operator | (const SPClassicList &a, const SPClassicList &b) +{ + return (SPClassicList(a) |= b); +} + +/// Boolean 'and' operation - returns elements present in both of the lists +template inline SPClassicList operator & (const SPClassicList &a, const SPClassicList &b) +{ + return (SPClassicList(a) &= b); +} + +/// string list +typedef SPClassicList SP_CLIST_STR; +/// dword values list +typedef SPClassicList SP_CLIST_DWORD; + +#endif // of SP_CLIST_H diff --git a/src/libsp/containers/dllist.h b/src/libsp/containers/dllist.h new file mode 100644 index 0000000..29c9ecf --- /dev/null +++ b/src/libsp/containers/dllist.h @@ -0,0 +1,695 @@ +////////////////////////////////////////////////////////////////////////// +/** + * support lib double-linked dynamic list template prototype + * \file libsp/containers/dllist.h + * \author bombur + * \version x.xx + * \date xx.xx.xxxx + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#ifndef SP_DLLIST_H +#define SP_DLLIST_H + +/// Double-linked list abstract container. Assumes that 'T' has 'next' and 'prev' fields. +/// For ordinary item classes, use SPDLinkedList instead. +/// \warning Only single-threaded version!!! +/// \warning Check the params! Must be (elem1 < elem2) and (from < to) ! +template +class SPDLinkedListAbstract +{ +protected: + /// the first and last elements + T *first, *last; + /// size for the stacks. + /// >0 means first deleting (FIFO), <0 means last one (FILO). + int size; + int num; /// real number of elements (for optimization only) + +public: + /// ctor + SPDLinkedListAbstract() + { + first = last = NULL; + num = size = 0; + } + + /// copy ctor + SPDLinkedListAbstract(const SPDLinkedListAbstract & dllist) + { + first = dllist.first; + last = dllist.last; + size = dllist.size; + num = dllist.num; + } + + /// copy ctor + SPDLinkedListAbstract(T *elem1, T *elem2 = NULL) + { + first = elem1; + if (elem2 != NULL) + last = elem2; + else + last = elem1; + size = 0; + num = GetNum(); + } + + /// dtor + ~SPDLinkedListAbstract() + { + ///Delete(); + } + + /// Get the first element of the list + T *GetFirst() + { + return first; + } + + /// Get the last element of the list + T *GetLast() + { + return last; + } + + /// Get(Find) the element by it's pointer + T *Get(/*const */BaseT *t) + { + T *cur = first; + for (int i = 0; cur != NULL; i++) + { + if (*cur == *t) + return cur; + cur = cur->next; + } + return NULL; + } + + /// Get the element with the given offset from the current + T *Get(T *from, int offset) + { + T *cur = from; + if (offset > 0) + { + for (int i = 0; i < offset && cur != NULL; i++) + { + cur = (T *)cur->next; + } + } else + { + for (int i = 0; i > offset && cur != NULL; i--) + { + cur = (T *)cur->prev; + } + } + return cur; + } + + /// Get the next element of the list + T *GetNext(T *from, int offset = 1) + { + if (from != NULL) + { + if (offset < 0) + offset = -offset; + if (offset == 1) + return (T *)from->next; + return Get(from, offset); + } + return NULL; + } + + /// Get the previous element of the list + T *GetPrev(T *from, int offset = 1) + { + if (from != NULL) + { + if (offset > 0) + offset = -offset; + if (offset == -1) + return from->prev; + return Get(from, offset); + } + return NULL; + } + + /// The numbered item access operator + T *operator [] (int n) + { + if (first == NULL || last == NULL) + return NULL; + T *cur = first; + for (int i = 0; i < n; i++) + { + if (cur->next == NULL) + break; + cur = cur->next; + } + return cur; + } + + /// Get the number of given elements (distance). NULLs = all list + /// ! If the order is reversed, the number is negative ! + int GetNum(T *from = NULL, T *to = NULL) + { + if (from == NULL) + from = first; + if (to == NULL) + to = last; + int n = 0; + BOOL found = FALSE; + if (from == first && to == last) + return num; + if (to != first && from != last) + { + for (T *cur = from; cur != NULL; n++) + { + if (cur == to) + { + n++; + found = TRUE; + break; + } + cur = (T *)cur->next; + } + } else + { + if (from == first || to == last) + return 1; + if (from == last && to == first) + return -num; + return 0; + } + + if (!found) + { + n = 0; + for (T *cur = to; cur != NULL; n--) + { + if (cur == from) + { + n--; + found = TRUE; + break; + } + cur = (T *)cur->prev; + } + } + //SPASSERT(found); + return n; + } + + /// Set the maximum list size + BOOL SetSize(int s) + { + size = s; + if (s == 0) + return TRUE; + int n = (int)num; + if (n > abs(size)) + { + if (size > 0) + Delete(first, n - size); + else + Delete(last, -n - size); + } + return TRUE; + } + + /// Insert the element range to the list after the 'from' element + BOOL InsertAfter(T *from, T *elem1, T *elem2 = NULL) + { + int addn = 1; + if (elem2 == NULL) + elem2 = elem1; + else + addn = (int)GetNum(elem1, elem2); + if (first == NULL || last == NULL) + { + first = elem1; + last = elem2; + num = addn; + return TRUE; + } + if (from == NULL || elem1 == NULL) + return FALSE; + + if (size != 0) + { + int n = num; + if (n + addn > Abs(size)) + { + if (size > 0) + Delete(first, n + addn - size - 1); + else + Delete(last, -n - addn - size - 1); + } + } + num += addn; + + if (from->next != NULL) + { + from->next->prev = elem1; + elem2->next = from->next; + } else + { + elem2->next = NULL; + last = elem2; + } + from->next = elem1; + elem1->prev = from; + return TRUE; + } + + /// Insert the elements in the list after the 'from' element + BOOL InsertAfter(T *from, const SPDLinkedListAbstract & ll) + { + return InsertAfter(from, ll.first, ll.last); + } + + /// Insert the element range to the list before the 'from' element + BOOL InsertBefore(T *from, T *elem1, T *elem2 = NULL) + { + int addn = 1; + if (elem2 == NULL) + elem2 = elem1; + else + addn = GetNum(elem1, elem2); + if (first == NULL || last == NULL) + { + first = elem1; + last = elem2; + num = addn; + return TRUE; + } + if (from == NULL || elem1 == NULL) + return FALSE; + + if (size != 0) + { + int n = num; + if (n + addn > Abs(size)) + { + if (size > 0) + Delete(first, n + addn - Abs(size)); + else + Delete(last, Abs(size) - n - addn); + } + } else + num += addn; + + if (from->prev != NULL) + { + from->prev->next = elem1; + elem1->prev = from->prev; + } else + { + elem1->prev = NULL; + first = elem1; + } + from->prev = elem2; + elem2->next = from; + return TRUE; + } + + /// Insert the elements in the list before the 'from' element + BOOL InsertBefore(T *from, const SPDLinkedListAbstract & ll) + { + return InsertBefore(from, ll.first, ll.last); + } + + /// Add the elements to the list + BOOL Add(T *elem1, T *elem2 = NULL) + { + return InsertAfter(last, elem1, elem2); + } + + /// Allocate and add element to the list (stack push) + BOOL Push(const BaseT & elem) + { + T *e = new T(elem); + return Add(e); + } + + /// Retreive the last element and remove it from the list (stack pop) + T & Pop() + { + T *e = GetLast(); + BaseT & t = e->item; + e->item = NULL; + Delete(e); + return t; + } + + + /// Remove the elements range from the list + BOOL Remove(T *from, T *to = NULL) + { + int remn = 1; + if (from == NULL) + { + if (to != NULL) + from = to; + else + return FALSE; + } + if (to == NULL) + to = from; + else + remn = GetNum(from, to); + + if (from == first) + first = (T *)to->next; + if (to == last) + last = (T *)from->prev; + if (from->prev != NULL) + from->prev->next = to->next; + if (to->next != NULL) + to->next->prev = from->prev; + from->prev = NULL; + to->next = NULL; + num -= remn; + return TRUE; + } + + /// Remove and delete the elements in the range + BOOL Delete(T *from = NULL, T *to = NULL) + { + if (from == NULL) + { + if(to != NULL) + { + from = to; + to = NULL; + } else // delete entire list + { + from = first; + to = last; + } + } + if (first == NULL || last == NULL) + return TRUE; + + if (Remove(from, to) == FALSE) + return FALSE; + + T *cur = from; + while (cur != NULL) + { + T *next = (T *)cur->next; + /////////// + if (cur != NULL) + delete cur; + /////////// + if (cur == to) + break; + cur = next; + } + + return TRUE; + } + + /// Remove and delete 'n' elements starting from the current + BOOL Delete(T *from, int n) + { + T *to = Get(from, n); + return Delete(from, to); + } + + /// Detach the elements range from the list and put them to the new one. + SPDLinkedListAbstract *Detach(T *from, T *to = NULL) + { + if (Remove(from, to) == FALSE) + return NULL; + SPDLinkedListAbstract *ll = new SPDLinkedListAbstract; + ll->size = size; + ll->start = from; + if (to != NULL) + ll->end = to; + else + ll->end = from; + return ll; + } + + /// Move elements in the list to the right or left (sign of 'offset') + BOOL Move(int offset, T *elem1, T *elem2 = NULL) + { + if (offset == 0) + return TRUE; + if (elem2 == NULL) + elem2 = elem1; + if (offset > 0) + { + T *to = Get(elem2, offset); + Remove(elem1, elem2); + if (to == NULL) + return InsertAfter(last, elem1, elem2); + return InsertAfter(to, elem1, elem2); + } + T *to = Get(elem1, offset); + Remove(elem1, elem2); + if (to == NULL) + return InsertBefore(first, elem1, elem2); + return InsertBefore(to, elem1, elem2); + } + + /// Move elements in the list to the specified place (instead of 'to') + /// \todo: optimise and check it!!! + BOOL Move(T *to, T *elem1, T *elem2 = NULL) + { + Remove(elem1, elem2); + if (to == NULL) + return InsertBefore(first, elem1, elem2); + if (to == last) + return InsertAfter(to, elem1, elem2); + if (GetNum(elem1, to) > 0) + return InsertAfter(to, elem1, elem2); + return InsertBefore(to, elem1, elem2); + } + + /// Swaps two elements, the order isn't important (elem1<>elem2) + BOOL Swap(T *elem1, T *elem2) + { + if (elem1 == NULL || elem2 == NULL) + return FALSE; + + BOOL closeto1 = (elem1->next == elem2); + BOOL closeto2 = (elem2->next == elem1); + + if (elem1->prev != NULL && !closeto2) + elem1->prev->next = elem2; + if (elem1->next != NULL && !closeto1) + elem1->next->prev = elem2; + if (elem2->prev != NULL && !closeto1) + elem2->prev->next = elem1; + if (elem2->next != NULL && !closeto2) + elem2->next->prev = elem1; + + if (elem1 == first) + first = elem2; + else if (elem2 == first) + first = elem1; + if (elem1 == last) + last = elem2; + else if (elem2 == last) + last = elem1; + + T *tmp = elem1->prev; + if (closeto1) + elem1->prev = elem2; + else + elem1->prev = elem2->prev; + if (closeto2) + elem2->prev = elem1; + else + elem2->prev = tmp; + + tmp = elem2->next; + if (closeto1) + elem2->next = elem1; + else + elem2->next = elem1->next; + if (closeto2) + elem1->next = elem2; + else + elem1->next = tmp; + return TRUE; + } + + /// Check if everything is ok with the list (integrity check) + BOOL Check() + { + if (first == NULL && last == NULL) // empty list + return TRUE; + T *cur = first; + int cnum = 1; + while (true) + { + if (cur->next != NULL) + { + cnum++; + if (cur->next == cur) // no cyclic chains + return FALSE; + if (cur->next->prev != cur) // breaked chain + return FALSE; + } else + break; + cur = cur->next; + } + if (cur != last) // invalid 'last' chain marker + return FALSE; + if (cnum != num) // invalid number of elements + return FALSE; + if (size != 0 && cnum > size) // number of elements exceeded + return FALSE; + return TRUE; + } + + /// Check if the element belongs to the list + BOOL IsInList(T *elem) + { + if (elem == NULL) + return FALSE; + T *cur = first; + while (cur != NULL) + { + if (cur == elem) + return TRUE; + cur = cur->next; + } + return FALSE; + } + + /*/// Check if the element belongs to the list + BOOL IsInList(const T & elem) + { + if (elem == NULL) + return FALSE; + T *cur = first; + while (cur != NULL) + { + if (cur == elem) + return TRUE; + cur = cur->next; + } + return FALSE; + }*/ + + /// Check if the list is empty + BOOL IsEmpty() + { + if (first == NULL && last == NULL) + { + //if (num != 0) + // SPASSERT("DLList damaged!"); + return TRUE; + } + return FALSE; + } +}; + +//////////////////////////////////////////////////////////////////////////// + +/// Double-Linked list element. Used as wrapper for ordinary data types. +/// \warning only single-threaded version!!! +template +class DLElement +{ +public: + + T item; + DLElement *next, *prev; + + /// ctor + DLElement() + { + next = prev = NULL; + } + /// ctor + DLElement(const T & t) + { + item = t; + next = prev = NULL; + } + + /// copy ctor + DLElement(const DLElement & e) + { + item = e.item; + next = e.next; + prev = e.prev; + } + + /// dtor + virtual ~DLElement() + { +// delete item; + } + + /// ref-counted copy from another DLElement + const DLElement & operator=(const DLElement & e) + { + item = e.item; + next = e.next; + prev = e.prev; + return *this; + } + + /// ref-counted copy from 'T' object + const DLElement & operator=(const T & src) + { + item = src; + next = NULL; + prev = NULL; + return *this; + } + + /// compare + bool operator == (const T & t) + { + return item == t; + } + + /// 'T' conversion operator + operator const T & ( ) const + { + return item; + } + + const T & GetItem() const + { + return item; + } + + void SetItem(const T & t) + { + item = t; + } + +}; + + +/// Double-linked list manager. Use it for any item classes/types. +/// \warning Only single-threaded version!!! +/// \warning Check the params! Must be (elem1 < elem2) and (from < to) ! +template +class SPDLinkedList : public SPDLinkedListAbstract, T > +{ +}; + +#endif // of SP_DLLIST_H diff --git a/src/libsp/containers/hashlist.h b/src/libsp/containers/hashlist.h new file mode 100644 index 0000000..bab3453 --- /dev/null +++ b/src/libsp/containers/hashlist.h @@ -0,0 +1,336 @@ +////////////////////////////////////////////////////////////////////////// +/** + * support lib hash list (search) template prototype + * \file libsp/containers/hashlist.h + * \author bombur + * \version x.xx + * \date xx.xx.xxxx + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#ifndef SP_HASHLIST_H +#define SP_HASHLIST_H + +#include + +/// Hash-table list (search-optimised) template. +/// Use SetN() first, then Add() or Merge() to add elements; +/// Get() searches for elements, and GetN() returns the actual number stored. +/// \warning only single-threaded version!!! +template +class SPHashListAbstract : public SPClassicList > +{ +public: + /////////////////////////////////////////////////////////////// + /// Hash function - declare it in derived class. + /// The return value should be in the range of [0..num-1] + /// DO NOT USE THIS IMPL. ON POINTERS!!! + virtual DWORD HashFunc(const BaseT & item) + { + return (DWORD)item % ((this->num > 0) ? this->num : 1); + } + + /// Comparison function - declare it in derived class + /// DO NOT USE THIS IMPL. ON POINTERS!!! + virtual BOOL Compare(const BaseT & item1, const BaseT & item2) + { + return ((T &)item1 == (T &)item2); + } + /////////////////////////////////////////////////////////////// + + /// Ctor. Creates list with N hash-size + SPHashListAbstract (int N = 1) : SPClassicList >() + { + this->SetN(N); + } + + /// Copy ctor. + SPHashListAbstract (const SPHashListAbstract & list) + : SPClassicList >(list) + { + } + + /// Dtor. Destroys only the list, not the elements. + virtual ~SPHashListAbstract () + { + DeleteObjects(); + } + + /// Add item to the list + /// Do not pass temporary variables!!! + BOOL Add (BaseT *item) + { + if (this->num == 0) + return FALSE; + DWORD f = HashFunc(*item); + return this->l[f].Add((T *)item); + } + + /// Add item to the list ONLY IF UNIQUE, and return the existing or new one. + /// Do not pass temporary variables!!! + + BaseT *Merge (const BaseT & item) + { + DWORD f = HashFunc((BaseT)item); + T *cur = this->l[f].GetFirst(); + while (cur != NULL) + { + if (Compare((BaseT)cur->GetItem(), (BaseT)item)) + return (BaseT *)&cur->GetItem(); + cur = cur->next; + } + if (this->l[f].Add((T *)item) == FALSE) + return NULL; + return &(BaseT)item; + } + + /// Find item (return pointer to existing item or NULL) + BaseT *Get (const BaseT & item) + { + DWORD f = HashFunc(item); + T *cur = this->l[f].GetFirst(); + while (cur != NULL) + { + if (Compare(*((BaseT *)cur->GetItem()), item)) + return (BaseT *)cur->GetItem(); + cur = (T *)cur->next; + } + return NULL; + } + + /// Find the first item of the hash-list + /// \WARNING: Works only with pointer items! + BaseT *GetFirst() + { + if (this->num == 0) + return NULL; + for (int f = 0; f < this->num; f++) + { + if (this->l[f].GetFirst() != NULL) + return (BaseT *)(this->l[f].GetFirst()->GetItem()); + } + return NULL; + } + + /// Find the next item of the specified one (or the first, if called with 'NULL') + /// \WARNING: Works only with pointer items! + BaseT *GetNext(const BaseT & item) + { + int f = HashFunc(item); + T *cur = this->l[f].GetFirst(); + while (cur != NULL) + { + if (Compare(*((BaseT *)cur->GetItem()), item)) + { + if (cur->next == NULL) // get from the next row + { + while (++f < this->num) + { + if (this->l[f].GetFirst() != NULL) + return (BaseT *)(this->l[f].GetFirst()->GetItem()); + } + return NULL; + } else + return (BaseT *)(cur->next->GetItem()); + } + cur = (T *)cur->next; + } + return NULL; // not in this list + } + + /// Get all items as a linear list + SPList GetList() + { + SPList list; + for (int f = 0; f < this->num; f++) + { + T *cur = this->l[f].GetFirst(); + while (cur != NULL) + { + list.Add(cur->GetItem()); + cur = cur->next; + } + } + return list; + } + + /// Remove item from the list (but don't delete the item itself!) + BOOL Remove (const BaseT & item) + { + DWORD f = HashFunc(item); + T *cur = this->l[f].GetFirst(); + while (cur != NULL) + { + if (Compare(*((BaseT *)cur->GetItem()), item)) + return this->l[f].Remove(cur); + cur = cur->next; + } + return FALSE; + } + + /// Delete item from the list (but don't delete the item itself!) + BOOL Delete (const BaseT & item) + { + DWORD f = HashFunc(item); + T *cur = this->l[f].GetFirst(); + while (cur != NULL) + { + if (Compare(*((BaseT *)cur->GetItem()), item)) + return this->l[f].Delete(cur); + cur = cur->next; + } + return FALSE; + } + + /// Remove all items from the list + BOOL Clear () + { + for (int i = 0; i < this->num; i++) + { + this->l[i].Remove(this->l[i].GetFirst(), this->l[i].GetLast()); + } + return TRUE; + } + + /// Delete all objects ourself and clear the list + BOOL DeleteObjects () + { + for (int i = 0; i < this->num; i++) + { + if (this->l[i].Delete() == FALSE) + return FALSE; + } + return TRUE; + } + + /// Get items number (count) + inline int GetN () + { + int n = 0; + for (int i = 0; i < this->num; i++) + n += this->l[i].GetN(); + return n; + } + +}; + + +/// Hash list for 'normal' elements, with built-in dllist-element class +template +class SPHashList : public SPHashListAbstract, T> +{ +public: + /// ctor + SPHashList (int N = 1) : SPHashListAbstract, T> (N) {} + /// Add item to the list + BOOL Add (const T & item) + { + if (this->num == 0) + return FALSE; + DWORD f = HashFunc(item); + DLElement *elem = new DLElement(item); + return this->l[f].Add(elem); + } + + /// Add item to the list ONLY IF UNIQUE, and return the existing or new one. + T *Merge (const T & item) + { + DWORD f = HashFunc(item); + DLElement *cur = this->l[f].GetFirst(); + while (cur != NULL) + { + if (Compare(*cur, item)) + return &cur->item; + cur = cur->next; + } + DLElement *elem = new DLElement(item); + if (this->l[f].Add(elem) == FALSE) + return NULL; + return &(elem->item); + } +}; + +/// Integer hash table implementation. +/// \warning For testing purposes only. +class SPIntHashList : public SPHashList +{ +public: + /// ctor + SPIntHashList() + { + } + + /// dtor + virtual ~SPIntHashList() + { + } + +}; + +// calculate djb2 (Bernstein) string hash +inline DWORD SPStringHashFunc(char * const & item, int /*num*/) +{ + char *s = item; + DWORD hash = 5381; + int c; + if (s != NULL && s[0] != '\0') + { + while ((c = *s++) != '\0') + hash = ((hash << 5) + hash) + c; // = hash * 33 + c; + } + return hash; +/* + DWORD hash = 0; + char *it = (char *)item; + for (int i = 0; *it; i++) + hash += ((DWORD)*it++) ^ (i % num); + return hash; +*/ +} + +/// String hash table implementation. +/// \warning For testing purposes only. +class SPStringHashList : public SPHashList +{ +public: + /// ctor + SPStringHashList() + { + } + + /// dtor + virtual ~SPStringHashList() + { + } + + /// Hash function - declare it in derived class. + /// The return value should be in the range of [0..num-1] + virtual DWORD HashFunc(char * const & item) // \todo Get better function + { + if (item == NULL) + return 0; + return SPStringHashFunc(item, num) % num; + } + + /// Comparison function - declare it in derived class + virtual BOOL Compare(char * const & item1, char* const & item2) + { + return strcmp(item1, item2) == 0; + } +}; + +#endif // of SP_HASHLIST_H diff --git a/src/libsp/containers/list.h b/src/libsp/containers/list.h new file mode 100644 index 0000000..072f0e7 --- /dev/null +++ b/src/libsp/containers/list.h @@ -0,0 +1,459 @@ +////////////////////////////////////////////////////////////////////////// +/** + * support lib linear dynamic list template prototype + * \file libsp/containers/list.h + * \author bombur + * \version x.x + * \date xx.xx.xxxx + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#ifndef SP_LIST_H +#define SP_LIST_H + +const int SP_LIST_BUFFER_SIZE = 2; + +#define GROW(num) (9 * num / 8) + +/// Dynamic linear list template ('vector') +/// \warning: Only single-threaded version!!! +/// \warning: Don't use SPList, where Object uses ctors for initialization. +/// Ctors will not be called! Use SPList or SPClassicList instead! +/// \warning: Copying lists is SLOW 'cause copy ctors are called for items. +template +class SPList +{ +public: + + /// comparison function prototype (see Sort()) + typedef int (*SPListCompareFunc)(T *e1, T *e2); + + /// Ctor. Creates empty list + SPList () + { + l = (T *)SPmalloc (sizeof(T) * SP_LIST_BUFFER_SIZE); + //SPCHECKMSG(l != NULL, "Memory error"); + lastnum = num = 0; + } + + /// Ctor. Creates list with n elements. + SPList (int n) + { + lastnum = num = n; + l = (T *)SPmalloc ((lastnum + SP_LIST_BUFFER_SIZE) * sizeof(T)); + } + + /// Copy ctor. + SPList (const SPList & list) + { + lastnum = num = list.num; + l = (T *)SPmalloc ((lastnum + SP_LIST_BUFFER_SIZE) * sizeof(T)); + //SPCHECKMSG(l != NULL, "Memory error"); + if (num > 0) + { + for (int i = 0; i < num; i++) + { + l[i] = list.l[i]; // call elements' copy ctors + } + } + } + + SPList & operator = (const SPList & list) + { + lastnum = num = list.num; + SPSafeFree(l); + l = (T *)SPmalloc ((lastnum + SP_LIST_BUFFER_SIZE) * sizeof(T)); + //SPCHECKMSG(l != NULL, "Memory error"); + if (num > 0) + { + for (int i = 0; i < num; i++) + { + l[i] = list.l[i]; // call elements' copy ctors + } + } + return (*this); + } + + /// Dtor. Destroys only the list, not the elements. + ~SPList () + { + SPSafeFree(l); + lastnum = num = 0; + } + + /// Reserve memory for N items + BOOL Reserve(int num) + { + if (lastnum <= num) + { + lastnum = num + 1; + l = (T *)SPrealloc ((void *)l, (lastnum + SP_LIST_BUFFER_SIZE) * sizeof(T)); + if (l == NULL) + { + SPERRMES("Memory error"); + return FALSE; + } + return TRUE; + } + return FALSE; + } + + /// Add item to the end of the list + int Add (T item) + { + if (num + 1 >= lastnum + SP_LIST_BUFFER_SIZE) // need to expand buffer + { + lastnum = GROW(num) + 1; + l = (T *)SPrealloc ((void *)l, (lastnum + SP_LIST_BUFFER_SIZE) * sizeof(T)); + if (l == NULL) + { + SPERRMES("Memory error"); + return -1; + } + } + l[num] = item; + return num++; + } + + /// Add one list to the end of another list. + /// Returns the last element added. + int Add (const SPList & list) + { + int ret = -1; + for (int i = 0; i < list.GetN(); i++) + ret = Add (list[i]); + return ret; + } + + /// Add unique item to the list, and return the existing or new index. + int Merge (T item) + { + int g = Get (item); + if (g == -1) + return Add (item); + return g; + } + + /// Merge lists (avoid item duplicates) + int Merge (const SPList & list) + { + int ret = -1; + for (int i = 0; i < list.GetN(); i++) + { + T item = list[i]; + if (Get (item) == -1) + ret = Add (item); + } + return ret; + } + + /// Insert the 'item' element to the 'where' position in the list + bool Insert (T item, int where) + { + int n = num; + if (where < 0) + return FALSE; + if (where > (int)num) + n = where; + + if (n + 1 >= lastnum + SP_LIST_BUFFER_SIZE) // need to expand buffer + { + lastnum = n + 1; + l = (T *)SPrealloc ((void *)l, (lastnum + SP_LIST_BUFFER_SIZE) * sizeof(T)); + if (l == NULL) + { + SPERRMES("Memory error"); + return FALSE; + } + } + + register int i; + for (i = num; i > where; i--) + l[i] = l[i - 1]; + + l[where] = item; + num = n + 1; + return TRUE; + } + + /// Replace item with new one; expand list if needed. + bool Put (T item, int where) + { + if (where < 0) + return FALSE; + if (where >= (int)num) + { + num = where + 1; + if (num >= lastnum + SP_LIST_BUFFER_SIZE) // need to expand buffer + { + lastnum = num; + l = (T *)SPrealloc ((void *)l, (lastnum + SP_LIST_BUFFER_SIZE) * sizeof(T)); + if (l == NULL) + { + SPERRMES("Memory error"); + return FALSE; + } + } + } + l[where] = item; + return TRUE; + } + + /// Find item's index number + int Get (const T & item, int idx1 = 0, int idx2 = -1) + { + if (idx1 < 0) + return -1; + if (idx2 < 0) idx2 = num - 1; + for (int i = idx1; i <= idx2; i++) + if (l[i] == item) + return i; + return -1; + } + + /// Remove the item from the list + bool Remove (int n) + { + if (n < 0 || n >= (int)num) + return FALSE; + for (int i = n; i < num - 1; i++) + l[i] = l[i + 1]; + if (num >= 1) + { + if (num - 1 < lastnum) // need to shrink buffer + { + l = (T *)SPrealloc ((void *)l, lastnum * sizeof(T)); + if (lastnum > SP_LIST_BUFFER_SIZE) + lastnum -= SP_LIST_BUFFER_SIZE; + else + lastnum = 0; + if (l == NULL) + { + SPERRMES("Memory error"); + return FALSE; + } + } + num--; + return TRUE; + } + return FALSE; + } + + /// Remove item from the list (search used) + bool Remove (const T & item) + { + int i; + while ((i = Get (item)) != -1) + { + if (!Remove (i)) + return FALSE; + } + return TRUE; + } + + /// Remove elements from 'this' list found in the 'list'. + /// Returns the number of elements removed. + int Remove (const SPList & list) + { + int numdel = 0; + for (int i = 0; i < list.GetN(); i++) + { + int ret = Get (list[i]); + if (ret != -1) + { + Remove (ret); + numdel++; + } + } + return numdel; + } + + /// Remove all items from the list + bool Clear () + { + if (lastnum > 1) + l = (T *)SPrealloc ((void *)l, sizeof(T) * SP_LIST_BUFFER_SIZE); + if (l == NULL) + return FALSE; + lastnum = num = 0; + return TRUE; + } + + /// Moves item in the list (delete + insert) + bool Move (int from, int to) + { + if (from < 0 || from >= (int)num) + return FALSE; + T item = l[from]; + if (Remove (from) == FALSE) + return FALSE; + if (Insert (item, to) == FALSE) + return FALSE; + return TRUE; + } + + /// Delete all objects themself and clear the list + bool DeleteObjects () + { + for (int i = 0; i < num; i++) + SPSafeDelete(l[i]); + return Clear (); + } + + /// The main item access operator + FORCEINLINE T & operator [] (int n) const + { + return l[n]; + } + + /// List concatenation and assign + SPList & operator += (const SPList & list) + { + Add (list); + return *this; + } + + /// Boolean 'and' operation - returns elements present in both of the lists + SPList & operator &= (const SPList & list) + { + for (int i = 0; i < num; i++) + { + if (list.Get(l[i]) == -1) + this->Delete (i); + } + return *this; + } + + /// Boolean 'or' operation - returns unique elements present in any of the lists + SPList & operator |= (const SPList & list) + { + Merge(list); + return *this; + } + + /// Add one list to another and return the sum. + SPList operator + (const SPList & list) + { + SPList r(*this); + r.Add(list); + return r; + } + + /// Return the elements of the first list not present in the second. + SPList operator - (const SPList & list) + { + SPList r; + for (int i = 0; i < num; i++) + { + if (list.Get(l[i]) == -1) + r.Add (l[i]); + } + return r; + } + + /// Sort list using user-defined comparison function + void Sort (SPListCompareFunc compare) + { + qsort((void *)l, num, sizeof(T), + (int (/*__cdecl*/ *)(const void *, const void *))compare); + } + + /// Sort list range using user-defined comparison function + void Sort (SPListCompareFunc compare, int idx1, int idx2) + { + qsort((void *)(l + idx1), idx2 - idx1 + 1, sizeof(T), + (int (/*__cdecl*/ *)(const void *, const void *))compare); + } + + /// Get items number (count) + FORCEINLINE const int & GetN () const + { + return num; + } + + /// Set items count (reallocate the memory but don't change the data) + BOOL SetN(int n) + { + num = n; + if ((num >= lastnum + SP_LIST_BUFFER_SIZE) || // need to expand buffer + (num <= lastnum - SP_LIST_BUFFER_SIZE)) // need to shrink buffer + { + lastnum = num; + l = (T *)SPrealloc ((void *)l, (lastnum + SP_LIST_BUFFER_SIZE) * sizeof(T)); + if (l == NULL) + { + SPERRMES("Memory error"); + return FALSE; + } + } + return TRUE; + } + + // Set items count and clear them + BOOL SetClearN(int n) + { + if (!SetN(n)) + return FALSE; + memset(l, 0, n * sizeof(T)); + return TRUE; + } + + /// This is 'hack' method used by fast-loading operations + T *GetData() const + { + return l; + } + + /// List concatenation operator + template friend SPList operator + (const SPList &, const SPList &); + /// Boolean 'or' operation - returns unique elements present in any of the lists + template friend SPList operator | (const SPList &, const SPList &); + /// Boolean 'and' operation - returns elements present in both of the lists + template friend SPList operator & (const SPList &, const SPList &); + +protected: + /// Dynamic array, contains all data + T *l; + /// Number of stored items. Use GetN() for retrieving + int num; + int lastnum; /// used for bufferized memory reallocs +}; + +/// List concatenation operator +template inline SPList operator + (const SPList &a, const SPList &b) +{ + return (SPList (a) += (b)); +} + +/// Boolean 'or' operation - returns unique elements present in any of the lists +template inline SPList operator | (const SPList &a, const SPList &b) +{ + return (SPList(a) |= b); +} + +/// Boolean 'and' operation - returns elements present in both of the lists +template inline SPList operator & (const SPList &a, const SPList &b) +{ + return (SPList(a) &= b); +} + +/// string list +typedef SPList SP_LIST_STR; +/// dword values list +typedef SPList SP_LIST_DWORD; + +#endif // of SP_LIST_H diff --git a/src/libsp/containers/membin.cpp b/src/libsp/containers/membin.cpp new file mode 100644 index 0000000..96bdae4 --- /dev/null +++ b/src/libsp/containers/membin.cpp @@ -0,0 +1,322 @@ +////////////////////////////////////////////////////////////////////////// +/** + * support lib small memory bin implementation + * \file libsp/containers/membin.cpp + * \author bombur + * \version 1.0 + * \date xx.xx.xxxx + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#include +#include + +#include + + +// bin size, in bytes (incl. all headers) +const int binsize = 65536; +// end of list +const WORD EOL = (WORD)0xffff; + +#define IS_FREE(hdr) ((hdr->binidx & 0x8000) == 0) +#define MARK_FREE(hdr) { hdr->binidx &= 0x7fff; } +#define MARK_BUSY(hdr) { hdr->binidx |= 0x8000; } +#define BINIDX(hdr) (hdr->binidx & 0x7fff) + +#define GET_HDR(data) ((BinHeader *)((char *)data - sizeof(BinHeader))) +#define GET_PTR(hdr) ((char *)hdr + sizeof(BinHeader)) + + +#ifdef WIN32 +#pragma pack(1) +#endif +typedef struct BinHeader +{ +public: + // bin index; high bit of binidx is 'busy' flag + WORD binidx; + + // real data size, in bytes + WORD size; + + // offset to prev. BinHeader, in WORDs + WORD prev; + + // offset to next BinHeader, in WORDs + WORD next; + +} ATTRIBUTE_PACKED BinHeader; +#ifdef WIN32 +#pragma pack() +#endif + + +/// Memory bin +class MemBin +{ +public: + /// ctor + MemBin() + { + bin = NULL; + last = EOL; + } + + // a little hack! + MemBin & operator = (MemBin & mb) + { + bin = mb.bin; + mb.bin = NULL; + last = mb.last; + return *this; + } + + /// Allocate + BOOL Alloc(WORD idx) + { + bin = (WORD *)SPmalloc(binsize * sizeof(char)); + if (bin != NULL) + { + BinHeader *hdr = (BinHeader *)bin; + hdr->binidx = idx; + hdr->size = (binsize - sizeof(BinHeader)); + hdr->prev = hdr->next = EOL; + + last = 0; + return TRUE; + } else + { + last = EOL; + return FALSE; + } + } + + ~MemBin() + { + SPSafeFree(bin); + } + + // find a free chunk of 'size' bytes + BinHeader *Find(int size) + { + // the real size is greater + WORD s = (WORD)(size + sizeof(BinHeader)); + BinHeader *hdr = (BinHeader *)(bin + last); + if (IS_FREE(hdr) && hdr->size >= s) + return hdr; + WORD cur = 0; + while (cur != EOL) + { + BinHeader *hdr = (BinHeader *)(bin + cur); + if (IS_FREE(hdr) && hdr->size >= s) + return hdr; + cur = hdr->next; + } + return NULL; + } + + BOOL Occupy(BinHeader *hdr, WORD size) + { + if (hdr->size > size + 1 + sizeof(BinHeader)) + { + BinHeader *nexthdr = (BinHeader *)((char *)hdr + sizeof(BinHeader) + size); + BinHeader *oldnexthdr = hdr->next == EOL ? NULL : (BinHeader *)(bin + hdr->next); + hdr->next = (WORD)((WORD *)nexthdr - bin); + if (oldnexthdr != NULL) + { + oldnexthdr->prev = hdr->next; + nexthdr->next = (WORD)((WORD *)oldnexthdr - bin); + } else + { + nexthdr->next = EOL; + last = hdr->next; + } + nexthdr->binidx = hdr->binidx; + nexthdr->prev = (WORD)((WORD *)hdr - bin); + nexthdr->size = (WORD)(hdr->size - size - sizeof(BinHeader)); + hdr->size = size; + } + MARK_BUSY(hdr); + return TRUE; + } + + BOOL Free(BinHeader *hdr) + { + BinHeader *prev = hdr->prev != EOL ? (BinHeader *)(bin + hdr->prev) : NULL; + BinHeader *next = hdr->next != EOL ? (BinHeader *)(bin + hdr->next) : NULL; + // combine with left chunk if it's free + + if (prev != NULL && IS_FREE(prev)) + { + prev->size = (WORD)(prev->size + hdr->size + sizeof(BinHeader)); + prev->next = hdr->next; + if (next != NULL) + { + // add right chunk if it's also free + if (IS_FREE(next)) + { + BinHeader *nextnext = next->next != EOL ? (BinHeader *)(bin + next->next) : NULL; + if (nextnext != NULL) + nextnext->prev = hdr->prev; + else + last = hdr->prev; + prev->next = next->next; + prev->size = (WORD)(prev->size + next->size + sizeof(BinHeader)); + } else + next->prev = hdr->prev; + } + return TRUE; + } + + MARK_FREE(hdr); + + // add right chunk if it's also free + if (next != NULL && IS_FREE(next)) + { + hdr->size = (WORD)(hdr->size + next->size + sizeof(BinHeader)); + hdr->next = next->next; + BinHeader *nextnext = next->next != EOL ? (BinHeader *)(bin + next->next) : NULL; + if (nextnext != NULL) + nextnext->prev = next->prev; + else + last = next->prev; + } + + return TRUE; + } + +public: + WORD *bin; + // offset to the last BinHeader, in WORDs + WORD last; +}; + +SPClassicList bins; +static int curbin = -1; + +void *membin_alloc(void *data, size_t size) +{ + // huge block + if (size > binsize - sizeof(BinHeader)) + { + BinHeader *hdr = (BinHeader *)SPmalloc(size + sizeof(BinHeader)); + if (hdr != NULL) + { + hdr->binidx = EOL; + hdr->size = 0; // special 'huge' external chunk + hdr->prev = hdr->next = EOL; + } + return GET_PTR(hdr); + } + + // size should be positive and DWORD-aligned + WORD siz = (size > 0) ? (WORD)((size + 3) & ~3) : (WORD)4; + + BinHeader *oldhdr = NULL; + if (data != NULL) // first, see what we have... + { + oldhdr = GET_HDR(data); + // we don't resize free blocks + if (!IS_FREE(oldhdr) && BINIDX(oldhdr) < bins.GetN()) + { + // no shrinking, sorry... + if (siz <= oldhdr->size) + return data; + } else + oldhdr = NULL; + } + + // first in the current bin + BinHeader *freehdr = NULL; + if (curbin != -1) + { + freehdr = bins[curbin].Find(siz); + } + if (freehdr == NULL) + { + // we don't use reverse order because last 'curbin' was at the end already... + for (curbin = 0; curbin < bins.GetN(); curbin++) + { + freehdr = bins[curbin].Find(siz); + if (freehdr != NULL) + break; + } + } + // no free space left, so allocate a new bin + if (freehdr == NULL) + { + curbin = -1; + // already too many bins - we failed... + if (bins.GetN() > 32767) + return NULL; + + membin_next(); + freehdr = bins[curbin].Find(siz); + } + // absolute failure... + if (freehdr == NULL || curbin < 0) + return NULL; + + // now we have a new hdr, so split & mark + bins[curbin].Occupy(freehdr, siz); + + // return to user + void *ptr = GET_PTR(freehdr); + + if (oldhdr != NULL) + { + // copy old data after "resize" + memcpy(ptr, data, oldhdr->size); + // free old data + bins[BINIDX(oldhdr)].Free(oldhdr); + } + + return ptr; +} + +void membin_free(void *data) +{ + if (data == NULL) + return; + + BinHeader *hdr = GET_HDR(data); + // if huge block + if (hdr->binidx == EOL && hdr->size == 0 && hdr->prev == 0 && hdr->next == 0) + { + SPfree(hdr); + return; + } + + if (IS_FREE(hdr)) // it's already free + return; + // a valid bin + if (BINIDX(hdr) < bins.GetN()) + { + // mark hdr as free block + bins[BINIDX(hdr)].Free(hdr); + } +} + +void membin_next() +{ + curbin = bins.GetN(); + if (curbin >= 10) + return; + bins.SetN(curbin + 1); + bins[curbin].Alloc((WORD)curbin); +} diff --git a/src/libsp/containers/membin.h b/src/libsp/containers/membin.h new file mode 100644 index 0000000..bb1a7ec --- /dev/null +++ b/src/libsp/containers/membin.h @@ -0,0 +1,45 @@ +////////////////////////////////////////////////////////////////////////// +/** + * support lib small memory bin header + * \file libsp/containers/membin.h + * \author bombur + * \version 1.0 + * \date xx.xx.xxxx + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#ifndef SP_MEMORY_BIN_H +#define SP_MEMORY_BIN_H + +/* +/////////////////////////////////////////////////////////////////////// + Used for small memory (<64k) chunks to decrease memory fragmentation. + All mem.blocks are WORD-aligned. + Warning! No bounds-check/mem.leak/debug control! +/////////////////////////////////////////////////////////////////////// +*/ + +/// Allocate or reallocate small memory chunk +void *membin_alloc(void *, size_t); + +/// Free small memory chunk +void membin_free(void *); + +/// Use next memory bit for new allocs (faster?) +void membin_next(); + +#endif // of SP_MEMORY_BIN_H diff --git a/src/libsp/containers/salist.h b/src/libsp/containers/salist.h new file mode 100644 index 0000000..ca9b9ed --- /dev/null +++ b/src/libsp/containers/salist.h @@ -0,0 +1,259 @@ +////////////////////////////////////////////////////////////////////////// +/** + * support lib self-allocated double-linked list template prototype + * \file libsp/containers/salist.h + * \author bombur + * \version x.x + * \date xx.xx.xxxx + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#ifndef SP_SALIST_H +#define SP_SALIST_H + +enum SALIST_ITEM_TYPE +{ + SALIST_FREE = 0, + SALIST_USED = 1, +}; + +/// Self-allocated dynamic double-linked list template +/// with fast insertion/deletion of items. +/// STORES ITEMS OF DIFFERENT TYPES IN THE SAME POINTER LIST! +/// Used to store constantly created/deleted objects of several different types/sizes +/// WITHOUT frequent memory allocs/frees & memory fragmentation. +/// \Parameters: 'T' type must be a base class of 'U'! +/// 'num_slots' is the estimated number of stored items of the same type/size. +/// \warning: Only single-threaded version!!! +template +class SPSAList : public SPList +{ +protected: + /// internal item linkage class + class ListPrevNext + { + public: + ListPrevNext() + { + prev[SALIST_FREE] = next[SALIST_FREE] = -1; + prev[SALIST_USED] = next[SALIST_USED] = -1; + } + + int prev[2], next[2]; + }; + + /// Corresponds to the group of items of the same type/size. + struct ListAllocType + { + // all these are relative indexes (0..num_slots-1) + SPClassicList dllist; + int first[2], last[2]; + int size; // sizeof(element) + int num[2]; // number of elements + // these are abs. indexes + int begin, end; + }; + + /// Does double-linked item insertion/deletion + /// 'idx' is relative to the start of alloc: [0..num_slots]! + inline bool MoveFreeUsed(int alloc_idx, int idx, int from, int to) + { + int &from_prev = allocs[alloc_idx].dllist[idx].prev[from]; + int &from_next = allocs[alloc_idx].dllist[idx].next[from]; + // 1. remove + if (allocs[alloc_idx].first[from] == idx) + allocs[alloc_idx].first[from] = from_next; + if (allocs[alloc_idx].last[from] == idx) + allocs[alloc_idx].last[from] = from_prev; + if (from_prev >= 0) + allocs[alloc_idx].dllist[from_prev].next[from] = from_next; + if (from_next >= 0) + allocs[alloc_idx].dllist[from_next].prev[from] = from_prev; + // detach item + from_prev = from_next = -1; + + // 2. insert + int &to_prev = allocs[alloc_idx].dllist[idx].prev[to]; + int &to_next = allocs[alloc_idx].dllist[idx].next[to]; + // if the list is empty... + if (allocs[alloc_idx].first[to] < 0 || allocs[alloc_idx].last[to] < 0) + { + allocs[alloc_idx].first[to] = allocs[alloc_idx].last[to] = idx; + to_prev = -1; + to_next = -1; + allocs[alloc_idx].num[from]--; + allocs[alloc_idx].num[to]++; // = 1 + return true; + } + // additional check if item was already freed/used + if (to_prev < 0 && to_next < 0) + { + // add 'idx' after the last one + allocs[alloc_idx].dllist[allocs[alloc_idx].last[to]].next[to] = idx; + to_prev = allocs[alloc_idx].last[to]; + to_next = -1; + allocs[alloc_idx].last[to] = idx; + + allocs[alloc_idx].num[from]--; + allocs[alloc_idx].num[to]++; + return true; + } + return false; + } + + +public: + /// Ctor. Creates empty list + SPSAList() : SPList() + { + } + + ~SPSAList() + { + for (int i = 0; i < allocs.GetN(); i++) + { + //delete [] l[allocs[i].begin]; + SPfree(this->l[allocs[i].begin]); + } + } + + /// Allocates and returns a new item via parameter. + /// Item index (abs.) is returned to free the item afterwards. + template + int Allocate(U **newobj) + { + int i, alloc_idx = -1; + ListAllocType at; + // \todo: Optimize more! Use LUTs or hash... + for (i = 0; i < allocs.GetN(); i++) + { + if (sizeof(U) == allocs[i].size) + { + if (allocs[i].first[SALIST_FREE] >= 0) // at least 1 free item left + { + alloc_idx = i; + break; + } + } + } + if (alloc_idx == -1) // allocate new... + { + //U *arr = new U [num_slots]; + U *arr = (U *)SPmalloc(sizeof(U) * num_slots); + at.size = sizeof(U); + at.begin = SPList::GetN(); + at.end = at.begin + num_slots - 1; + SetN(at.begin + num_slots); + + int abs_cur = at.begin; + int cur_first = 0; + at.num[SALIST_USED] = 1; + at.num[SALIST_FREE] = num_slots - 1; + at.first[SALIST_USED] = at.last[SALIST_USED] = cur_first; + at.first[SALIST_FREE] = ++cur_first; + at.dllist.SetN(num_slots); + this->l[abs_cur++] = (T *)(arr); + for (i = 1; i < num_slots - 1; i++) + { + this->l[abs_cur++] = (T *)(arr + i); + at.dllist[i + 1].prev[SALIST_FREE] = cur_first; + at.dllist[i].next[SALIST_FREE] = ++cur_first; + } + this->l[abs_cur] = (T *)(arr + i); + at.last[SALIST_FREE] = cur_first; + allocs.Add(at); + if (newobj != NULL) + { + *newobj = arr; + new (*newobj) U(); + } + return at.begin + at.first[SALIST_USED]; + } + // find free item... + int idx = allocs[alloc_idx].first[SALIST_FREE]; + // mark one of free items as used and return it + MoveFreeUsed(alloc_idx, idx, SALIST_FREE, SALIST_USED); + idx += allocs[alloc_idx].begin; + if (newobj != NULL) + { + *newobj = (U *)this->l[idx]; + new (*newobj) U(); + } + return idx; + } + + /// Free used item. + bool Free(int idx) + { + if (idx < 0 || idx >= SPList::GetN()) + return false; + this->l[idx]->~T(); + int alloc_idx = idx / num_slots; + if (alloc_idx >= allocs.GetN()) + return false; + return MoveFreeUsed(alloc_idx, idx % num_slots, SALIST_USED, SALIST_FREE); + } + + /// Get the FIRST allocated element stored in the list. + /// Initialize 'cur_idx' to the value returned from Allocate(). + T *GetFirst(int *idx) const + { + *idx = -1; + return GetNext(idx); + } + + /// Get the NEXT allocated element stored in the list. + /// Set 'idx' to -1 for the first element or just pass the current value to advance. + T *GetNext(int *idx) const + { + int alloc_idx; + if (*idx >= 0) + { + alloc_idx = *idx / num_slots; + if (alloc_idx >= allocs.GetN()) + return NULL; + *idx = allocs[alloc_idx].dllist[*idx % num_slots].next[SALIST_USED]; + } else + alloc_idx = -1; + // search for the first item of the next group + while (*idx < 0) + { + alloc_idx++; + if (alloc_idx >= allocs.GetN()) + return NULL; + *idx = allocs[alloc_idx].first[SALIST_USED]; + } + *idx += allocs[alloc_idx].begin; + return this->l[*idx]; + } + + /// Get total number of free or used items. Used for stats. + int GetN(SALIST_ITEM_TYPE type) + { + int num = 0; + for (int i = 0; i < allocs.GetN(); i++) + num += allocs[i].num[type]; + return num; + } + +protected: + + SPClassicList allocs; +}; + + +#endif // of SP_SALIST_H diff --git a/src/libsp/containers/string.cpp b/src/libsp/containers/string.cpp new file mode 100644 index 0000000..ff0e0bf --- /dev/null +++ b/src/libsp/containers/string.cpp @@ -0,0 +1,290 @@ +////////////////////////////////////////////////////////////////////////// +/** + * support lib string implementation + * \file libsp/containers/string.cpp + * \author bombur et al. + * \version x.xx + * \date xx.xx.xxxx + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +//#define USE_MEMBIN + +#ifndef USE_MEMBIN +#define membin_alloc(s, n) SPrealloc(s, n) +#define membin_free(s) SPfree(s) +#else +#include +#endif + +#ifdef _MSC_VER + +#pragma warning(disable : 4706) + +#else + +char * strlwr(char *string) +{ + char *str = string; + if (str == NULL) + return NULL; + + while (*str != 0) + { + *str = tolower(*str); + str++; + } + + return string; +} + +char * strupr (char * string) +{ + char *str = string; + if (str == NULL) + return NULL; + + while (*str != 0) + { + *str = toupper(*str); + str++; + } + + return string; +} + +char * strrev (char * string) +{ + char *start = string; + char *left = string; + + while (*string++) + ; + string -= 2; + + while (left < string) + { + char ch = *left; + *left++ = *string; + *string-- = ch; + } + + return (start); +} + +char * strdup (char * string) +{ + char *memory; + if (!string) + return(NULL); + if ((memory = (char *)SPmalloc(strlen(string) + 1)) != NULL) + return strcpy(memory, string); + return NULL; +} + +int strcasecmp (char *dst, char *src) +{ + int f, l; + do + { + f = tolower((unsigned char)(*(dst++))); + l = tolower((unsigned char)(*(src++))); + } while (f != 0 && (f == l)); + return(f - l); +} + +#endif + +SPString::~SPString () +{ + if (str != NULL) + membin_free(str); + str = NULL; +} + +void SPString::Printf (const char *string, ...) +{ + va_list l; + va_start(l, string); + char *tmp = (char *)SPalloca (4096); // in stack + vsprintf(tmp, string, l); + Insert(GetLength()+1, tmp); + va_end(l); +} + +void SPString::Strftime (const char *string, time_t tim) +{ + struct tm *tmtim = localtime(&tim); + char *tmp = (char *)SPalloca (4096); // in stack + strftime(tmp, 4096, string, tmtim); + Insert(GetLength()+1, tmp); +} + +void SPString::Realloc() +{ + str = (char *)membin_alloc(str, (max + 1) * sizeof(char)); +} + +// Compare helpers +bool operator== (const SPString & s1, const SPString & s2) +{ + return s1.Compare(*s2) == 0; +} +bool operator== (const SPString & s1, const char *s2) +{ + if (s2 == NULL) + { + return (s1.str == NULL); + } + return s1.Compare(s2) == 0; +} +bool operator== (const char *s1, const SPString & s2) +{ + return s2.Compare(s1) == 0; +} + +bool operator!= (const SPString & s1, const SPString & s2) +{ + return s1.Compare(*s2) != 0; +} +bool operator!= (const SPString & s1, const char *s2) +{ + if (s2 == NULL) + return s1.str != NULL; + return s1.Compare(s2) != 0; +} +bool operator!= (const char *s1, const SPString & s2) +{ + return s2.Compare(s1) != 0; +} + +bool operator< (const SPString & s1, const SPString & s2) +{ + return s1.Compare(*s2) < 0; +} +bool operator< (const SPString & s1, const char *s2) +{ + return s1.Compare(s2) < 0; +} +bool operator< (const char *s1, const SPString & s2) +{ + return s2.Compare(s1) > 0; +} + +bool operator> (const SPString & s1, const SPString & s2) +{ + return s1.Compare(*s2) > 0; +} +bool operator> (const SPString & s1, const char *s2) +{ + return s1.Compare(s2) > 0; +} +bool operator> (const char *s1, const SPString & s2) +{ + return s2.Compare(s1) < 0; +} + +bool operator<= (const SPString & s1, const SPString & s2) +{ + return s1.Compare(*s2) <= 0; +} +bool operator<= (const SPString & s1, const char *s2) +{ + return s1.Compare(s2) <= 0; +} +bool operator<= (const char *s1, const SPString & s2) +{ + return s2.Compare(s1) >= 0; +} + +bool operator>= (const SPString & s1, const SPString & s2) +{ + return s1.Compare(*s2) >= 0; +} +bool operator>= (const SPString & s1, const char *s2) +{ + return s1.Compare(s2) >= 0; +} +bool operator>= (const char *s1, const SPString & s2) +{ + return s2.Compare(s1) <= 0; +} + + +////////////////////////////////////////////////////////// + +static void similar_str(const char *txt1, int len1, const char *txt2, int len2, int *pos1, int *pos2, int *max) +{ + char *end1 = (char *)txt1 + len1; + char *end2 = (char *)txt2 + len2; + int l; + + *max = 0; + for (char *p = (char *)txt1; p < end1; p++) + { + for (char *q = (char *)txt2; q < end2; q++) + { + for (l = 0; p + l < end1 && q + l < end2 && p[l] == q[l]; l++) + ; + if (l > *max) + { + *max = l; + *pos1 = p - txt1; + *pos2 = q - txt2; + } + } + } +} + +static int similar_char(const char *txt1, int len1, const char *txt2, int len2) +{ + int sum; + int pos1, pos2, max; + + similar_str(txt1, len1, txt2, len2, &pos1, &pos2, &max); + if ((sum = max)) + { + if (pos1 && pos2) + { + sum += similar_char(txt1, pos1, txt2, pos2); + } + if (pos1 + max < len1 && pos2 + max < len2) + { + sum += similar_char(txt1 + pos1 + max, len1 - pos1 - max, txt2 + pos2 + max, len2 - pos2 - max); + } + } + return sum; +} + +int SPString::Similar(const SPString & s2) +{ + if (len == 0 || s2.len == 0) + return 0; + int num = similar_char(str, len, s2.str, s2.len); + return num * 200 / (len + s2.len); +} diff --git a/src/libsp/containers/string.h b/src/libsp/containers/string.h new file mode 100644 index 0000000..e699536 --- /dev/null +++ b/src/libsp/containers/string.h @@ -0,0 +1,816 @@ +////////////////////////////////////////////////////////////////////////// +/** + * support lib string header + * \file libsp/containers/string.h + * \author bombur et al. + * \version x.xx + * \date xx.xx.xxxx + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#ifndef SP_STRING_H +#define SP_STRING_H + +#include +#include +#include +#include + +#define GROW(num) (9 * num / 8) + +#ifdef _MSC_VER +#pragma warning (disable : 4239) +#endif + +/// String manipulation class +class SPString +{ +public: + /// empty ctor + SPString () + { + str = NULL; + len = 0; + max = 0; + } + + /// dtor + ~SPString (); + + /// copy ctor + SPString (const SPString & src) + { + str = NULL; + len = 0; + max = 0; + + if (src.len > 0) + { + Add(src.len); + if (src.str != NULL) + strcpy(str, src.str); + } + // don't copy mbstr + } + + /// from a single character + SPString (char ch, int repeat = 1) + { + str = NULL; + len = 0; + max = 0; + + if (repeat < 0) + repeat = 0; + Add(repeat); + int i; + for (i = 0; i < repeat; i++) + str[i] = ch; + str[i] = '\0'; + } + + /// from a string (converts to char) + SPString (char *src) + { + str = NULL; + len = 0; + max = 0; + + if (src != NULL) + { + Add(strlen(src)); + strcpy(str, src); + } + } + + /// from a string (converts to char) + SPString (const char *src) + { + str = NULL; + len = 0; + max = 0; + + if (src != NULL) + { + Add(strlen(src)); + strcpy(str, src); + } + } + + /// subset of characters from an string (converts to char) + SPString (char *pitch, int inLen) + { + str = NULL; + len = 0; + max = 0; + + if (pitch != NULL && inLen > 0) + { + Add(inLen); + int i; + for(i = 0; i < len; i++) + str[i] = pitch[i]; + str[i] = '\0'; + } + } + + // Attributes & Operations + + /// get data length + int GetLength () const + { + return len; + } + + /// TRUE if zero length + BOOL IsEmpty () const + { + return !len; + } + + /// clear contents to empty + void Empty () + { + len = 0; + } + + /// cleans addition memory allocated + void Shrink () + { + max = len; + Realloc(); + } + + /// return single character at zero-based index + char GetAt (int index) const + { + if (index < 0 || index > len) + return *str; + return *(str + index); + } + + /// return single character at zero-based index + char & operator [] (int index) const + { + if (index < 0 || index > len) + return *str; + return *(str + index); + } + + /// set a single character at zero-based index + void SetAt (int index, char ch) + { + if (index < 0 || index >= len) + *(str + index) = ch; + } + + /// return pointer to const string (will be freed in dtor) + const char* operator * () const + { + return len ? str : ""; + } + + /// return pointer to const string (will be freed in dtor) + operator char* () const + { + return len ? str : (char *)""; + } + + // overloaded assignment + + /// ref-counted copy from another SPString + const SPString & operator = (const SPString & src) + { + if (this != &src) + { + len = max = src.len; + Realloc(); + if (len > 0) + { + strcpy(str, src.str); + } + } + return *this; + } + + /// set string content to single character + const SPString & operator = (char ch) + { + len = max = 1; + Realloc(); + *str = ch; + str[1] = '\0'; + return *this; + } + + /// copy string content from string + const SPString & operator = (const char *src) + { + if (str != src) + { + for (len = 0; *(src + len) != '\0'; len++) + ; + max = len; + Realloc(); + if (len != 0) + strcpy(str, src); + } + return *this; + } + + // string concatenation + + /// concatenate a character after converting it to char + const SPString & operator += (const char *src) + { + if (max != 0) + { + int index = len; + Add(strlen(src)); + strcpy(str + index, src); + } + else if (*src != '\0') + { + Add(strlen(src)); + strcpy(str, src); + } + return *this; + + } + + /// concatenate from another SPString + const SPString & operator += (const SPString & src) + { + return operator+=(*src); + } + + friend SPString operator + (const SPString & string1, const SPString & string2) + { + return SPString(string1) += string2; + } + + friend SPString operator + (const SPString & string, const char *str) + { + return SPString(string) += str; + } + + friend SPString operator + (const char *str, const SPString & string) + { + return SPString((char*)str) += string; + } + + // string comparison + + /// straight character comparison + int Compare (const SPString & string) const + { + if (len == 0) + { + if (string.str == NULL || string.len < 1) + return 0; + return string.str[0] == '\0' ? 0 : 1; + } + else if (string.len == 0 || string.str == NULL) + return -1; + return strcmp(str, string.str); + } + + /// compare ignoring case + int CompareNoCase (const SPString & string) const + { + if (len == 0) + { + if (string.str == NULL || string.len < 1) + return 0; + return string.str[0] == '\0' ? 0 : 1; + } + else if (string.len == 0 || string.str == NULL) + return -1; + return strcasecmp(str, string.str); + } + + // simple sub-string extraction + + /// return count characters starting at zero-based first + SPString Mid (int first, int count) const + { + if (first < 0) + first = 0; + if (first >= len) + return SPString(); + if (first + count > len) + count = len - first; + return SPString(str + first, count); + } + + /// return all characters starting at zero-based first + SPString Mid (int first) const + { + if (first < 0) + first = 0; + if (first >= len) + return SPString(); + return SPString(str + first); + } + + /// return first count characters in string + SPString Left (int count) const + { + if (count > len) + return SPString(str, len); + return SPString(str, count); + } + + /// return nCount characters from end of string + SPString Right (int count) const + { + if (count > len) + return SPString(); + return SPString(str + len - count, count); + } + + // upper/lower/reverse conversion +#ifdef _MSC_VER + /// NLS aware conversion to uppercase + void MakeUpper () + { + if (str == NULL) + return; + _strupr(str); + } + + /// NLS aware conversion to lowercase + void MakeLower () + { + if (str == NULL) + return; + _strlwr(str); + } + + /// reverse string right-to-left + void MakeReverse () + { + if (str == NULL) + return; + _strrev(str); + } +#endif + // trimming anything (either side) + + /// remove continuous occurrences of target starting from right + void TrimLeft (char target = ' ') + { + if (str == NULL) + return; + int i; + for (i = 0; i < len && str[i] == target; i++) + ; + if (i != 0) + { + memmove(str, str + i, (len - i + 1) * sizeof(char)); + len -= i; + } + } + + /// remove continuous occurrences of target starting from left + void TrimRight (char target = ' ') + { + if (str == NULL) + return; + int i; + for (i = len - 1; i > 0 && str[i] == target; i--) + ; + len = i + 1; + str[len] = '\0'; + } + + /// remove continuous occurrences of one of charSet starting from right + void TrimLeft (const char *charSet) + { + if (str == NULL) + return; + int i; + for (i = 0; i < len; i++) + { + bool found = false; + for (int j = 0; charSet[j] != '\0'; j++) + { + if (str[i] == charSet[j]) + { + found = true; + break; + } + } + if (!found) + break; + } + + if (i != 0) + { + memmove(str, str + i, (len - i + 1) * sizeof(char)); + len -= i; + } + } + + /// remove continuous occurrences of one of charSet starting from left + void TrimRight (const char *charSet) + { + if (str == NULL) + return; + int i; + for (i = len - 1; i; i--) + { + bool found = false; + for (int j = 0; charSet[j]; j++) + { + if (str[i] == charSet[j]) + { + found = true; + break; + } + } + if (!found) + break; + } + len = i + 1; + str[len] = '\0'; + } + + void TrimLines(int numlines) + { + if (str == NULL) + return; + int j = 0; + for (int i = 0; str[j] && i < numlines; i++) + { + for(; j < len && str[j] != '\n'; j++) + ; + if (str[j] == '\n') + j++; + } + len = j; + str[len] = '\0'; + } + + // advanced manipulation + + /// replace occurrences of oldS with newS + int Replace (char oldS, char newS) + { + if (str == NULL) + return 0; + int ret = 0; + for (int i = 0; i < len; i++) + { + if (str[i] == oldS) + { + str[i] = newS; + ret++; + } + } + return ret; + } + + /// replace occurrences of substring oldString with newString; + /// empty newString removes instances of oldString + int Replace (const char *oldString, const char *newString) + { + if (str == NULL) + return 0; + int last = 0; + int ret = 0; + int oldLen = strlen(oldString); + int newLen = strlen(newString); + + while (last < len && (last = Find(oldString, last)) != -1) + { + Delete(last, oldLen); + Insert(last, newString); + ret++; + last += newLen; + } + return ret; + } + + /// insert substring at zero-based index; concatenates + /// if index is past end of string + /// add to existing string + int Insert (int index, const char *string) + { + if (index < 0) + index = 0; + + int stringLen = strlen(string); + if (len == 0) + { + Add(stringLen); + strcpy(str, string); + return index; + } + + if (index >= len) + index = len; + + int l = len - index + 1; + + Add(stringLen); + + memmove(str + index + stringLen, str + index, l * sizeof(char)); + memmove(str + index, string, stringLen * sizeof(char)); + + return index; + } + + /// delete count characters starting at zero-based index + int Delete (int index, int count = 1) + { + if (index >= len || index < 0) + return -1; + if (index + count > len) + { + len = index; + return count; + } + memmove(str + index, str + index + count, (len - index - count + 1) * sizeof(char)); + len -= count; + return count; + } + + // searching + + /// find character starting at left, -1 if not found + int Find (char ch) const + { + for (int i = 0; i < len; i++) + { + if (str[i] == ch) + return i; + } + return -1; + } + + /// find character starting at right + int ReverseFind (char ch) const + { + for(int i = len - 1; i >= 0; i--) + { + if (str[i] == ch) + return i; + } + return -1; + } + + /// find character starting at zero-based index and going right + int Find (char ch, int start) const + { + for (int i = start; i < len; i++) + { + if (str[i] == ch) + return i; + } + return -1; + } + + /// find first instance of any character in passed string + int FindOneOf (const char *charSet) const + { + int charSetLen = strlen(charSet); + for (int i = 0; i= 0; i--) + { + for (int j = 0; j < charSetLen; j++) + { + if (str[i] == charSet[j]) + return i; + } + } + return -1; + } + + /// reverse find the first character not contained in passed string + int ReverseFindNoneOf (const char *charSet, int start = -1) const + { + int charSetLen = strlen(charSet); + if (start == -1) + start = len - 1; + for (int i = start; i >= 0; i--) + { + bool notfound = true; + for (int j = 0; j < charSetLen; j++) + { + if (str[i] == charSet[j]) + { + notfound = false; + break; + } + } + if (notfound) + return i; + } + return -1; + } + + /// find first instance of substring starting at zero-based index + int Find (const char *string, int start = 0) const + { + if (start < 0 || start >= len || string == NULL) + return -1; + + char *ret = strstr(str + start, string); + return ret ? ret - str : -1; + } + + /// find first instance of substring starting at zero-based index + int FindNoCase (const char *string, int start = 0) const + { + if (start < 0 || start >= len || string == NULL) + return -1; + + char *cp = str + start; + char *s1, *s2; + + if (!*string) + return start; + + while (*cp) + { + s1 = cp; + s2 = (char *) string; + + while (*s1 && *s2 && !(tolower(*s1) - tolower(*s2))) + s1++, s2++; + if (!*s2) + return cp - str; + cp++; + } + return -1; + } + + /// calculate djb2 (Bernstein) string hash + DWORD Hash() const + { + const char *s = str; + DWORD hash = 5381; + int c; + if (len > 0) + { + while ((c = *s++) != '\0') + hash = ((hash << 5) + hash) + c; // = hash * 33 + c; + } + return hash; + } + + // simple formatting + + /// printf-like formatting using passed string + void Printf (const char *string, ...); + void Strftime (const char *string, time_t tim); + + /// Set string from integer + void FromInteger(int i) + { + if (max < 12) + { + max = 12; + Realloc(); + } + char *s = str; + if (i < 0) + { + *(s++) = '-'; + i = -i; + } + char *b = s; + do + { + *s++ = (char)((i % 10) + '0'); + i /= 10; + } while (i > 0); + len = s - str; + *s-- = '\0'; + do + { + char temp = *s; + *s = *b; + *b = temp; + --s; + ++b; + } while (b < s); + } + + /// Calculate the similarity between two strings, in percents. [algorithm (c) Oliver, 1993] + /// \Warning! Recursive! + int Similar(const SPString & s2); + + +protected: + char *str; /// pointer to ref counted string data + int len; /// length in chars + int max; /// max length in chars + + /// reallocates memory + void Realloc(); + + /// adds count symbols, returns index of new first symbol + int Add(int count) + { + if (count < 0 || len < 0 || max < len) + return -1; + + int index = len; + if ((len += count) >= max) + { + // allocate more memory + max = GROW(len) + 8; + Realloc(); + } + return index; + } + +protected: + friend bool operator == (const SPString &, const char *); + friend bool operator != (const SPString &, const char *); +}; + +/* +static int SPListStringsCompare(SPString *e1, SPString *e2) +{ + return e1->Compare(*e2); +} + +static int SPListStringsCompareNoCase(SPString *e1, SPString *e2) +{ + return e1->CompareNoCase(*e2); +} +*/ + +// Compare helpers +bool operator== (const SPString & s1, const SPString & s2); +bool operator== (const SPString & s1, const char *s2); +bool operator== (const char *s1, const SPString & s2); +bool operator!= (const SPString & s1, const SPString & s2); +bool operator!= (const SPString & s1, const char *s2); +bool operator!= (const char *s1, const SPString & s2); +bool operator< (const SPString & s1, const SPString & s2); +bool operator< (const SPString & s1, const char *s2); +bool operator< (const char *s1, const SPString & s2); +bool operator> (const SPString & s1, const SPString & s2); +bool operator> (const SPString & s1, const char *s2); +bool operator> (const char *s1, const SPString & s2); +bool operator<= (const SPString & s1, const SPString & s2); +bool operator<= (const SPString & s1, const char *s2); +bool operator<= (const char *s1, const SPString & s2); +bool operator>= (const SPString & s1, const SPString & s2); +bool operator>= (const SPString & s1, const char *s2); +bool operator>= (const char *s1, const SPString & s2); + +#endif // of SP_STRING_H diff --git a/src/libsp/sp_bswap.h b/src/libsp/sp_bswap.h new file mode 100644 index 0000000..2b62817 --- /dev/null +++ b/src/libsp/sp_bswap.h @@ -0,0 +1,48 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - optimized byte-swap header file + * \file sp_bswap.h + * \author bombur + * \version 0.1 + * \date 4.05.2004 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#ifndef SP_BSWAP_H +#define SP_BSWAP_H + + +#ifdef SP_ARM + +#define BSWAP(d) \ + asm volatile ("eor r3, %0, %0, ROR #16\n" \ + "bic r3, r3, #0xff0000\n" \ + "mov %0, %0, ROR #8\n" \ + "eor %0, %0, r3, LSR #8\n" \ + : "=r" (d) : "0" (d) : "r3"); + +#else + +#define BSWAP(d) \ + ((d) = (((d) & 0xff) << 24) | (((d) & 0xff00) << 8) | \ + (((d) >> 8) & 0xff00) | (((d) >> 24) & 0xff)) + +#endif + +#define BWSWAP(w) (((w) >> 8) | (((w) & 0xff) << 8)) + +#endif // of SP_BSWAP_H diff --git a/src/libsp/sp_cdrom.h b/src/libsp/sp_cdrom.h new file mode 100644 index 0000000..54abd6c --- /dev/null +++ b/src/libsp/sp_cdrom.h @@ -0,0 +1,100 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - CDROM driver's interface header. + * \file sp_cdrom.h + * \author bombur + * \version 0.1 + * \date 4.05.2004 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#ifndef SP_CDROM_H +#define SP_CDROM_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +//////////////////////////////////// +/// Enums: + +typedef enum +{ + CDROM_STATUS_CURRENT = -1, + + CDROM_STATUS_UNKNOWN = 0, + CDROM_STATUS_NODISC, + CDROM_STATUS_TRAYOPEN, + + CDROM_STATUS_HASDISC, + + CDROM_STATUS_HAS_ISO = CDROM_STATUS_HASDISC | (1 << 4), + CDROM_STATUS_HAS_DVD = CDROM_STATUS_HASDISC | (2 << 4), + CDROM_STATUS_HAS_AUDIO = CDROM_STATUS_HASDISC | (3 << 4), + CDROM_STATUS_HAS_MIXED = CDROM_STATUS_HASDISC | (4 << 4), + +} CDROM_STATUS; + +//////////////////////////////////// +/// Functions: + +/// Init (open) CDROM driver +BOOL cdrom_init(); + +/// Release CDROM driver +BOOL cdrom_deinit(); + +/// Get device path (set src_path to NULL to get root device path) +const char *cdrom_getdevicepath(const char *src_path); + +/// Eject CD-ROM +int cdrom_eject(BOOL open); + +/// Mount or remount CD-ROM (if language changed) +int cdrom_mount(char *language, BOOL cd_only); +/// Unmount CD-ROM +int cdrom_umount(); +/// Return TRUE if CD-ROM is mounted +BOOL cdrom_ismounted(); + +/// Get CDROM status +CDROM_STATUS cdrom_getstatus(BOOL *force_update); + +/// Return TRUE if CDROM is ready (use additional checks) +BOOL cdrom_isready(); + +/// Switch CDROM on/off +BOOL cdrom_switch(BOOL on); + +int cdrom_stat(const char *, struct stat64 *); + +DIR *cdrom_opendir(const char *); +struct dirent *cdrom_readdir(DIR *); +int cdrom_closedir(DIR *); + +int cdrom_open(const char *fname, int flags); + +char *cdrom_getrealpath(const char *); + +#ifdef __cplusplus +} +#endif + +#endif // of SP_CDROM_H diff --git a/src/libsp/sp_eeprom.h b/src/libsp/sp_eeprom.h new file mode 100644 index 0000000..ec6d06b --- /dev/null +++ b/src/libsp/sp_eeprom.h @@ -0,0 +1,48 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - EEPROM interface header. + * \file sp_eeprom.h + * \author bombur + * \version 0.1 + * \date 4.05.2004 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#ifndef SP_EEPROM_H +#define SP_EEPROM_H + + +#ifdef __cplusplus +extern "C" { +#endif + +//////////////////////////////////// +/// Functions: + +/// Sets EEPROM value at given address. +BOOL eeprom_set_value(DWORD addr, DWORD val, int size = 4); + +/// Gets EEPROM value from given address. +DWORD eeprom_get_value(DWORD addr, int size = 4); + + +#ifdef __cplusplus +} +#endif + + +#endif // of SP_EEPROM_H diff --git a/src/libsp/sp_fip.h b/src/libsp/sp_fip.h new file mode 100644 index 0000000..d2531bc --- /dev/null +++ b/src/libsp/sp_fip.h @@ -0,0 +1,170 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - FIP (front panel & remote) driver's header. + * \file sp_fip.h + * \author bombur + * \version 0.1 + * \date 4.05.2004 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#ifndef SP_FIP_H +#define SP_FIP_H + + +#ifdef __cplusplus +extern "C" { +#endif + +//////////////////////////////////// +/// Functions: + +/// Loads driver module and opens it. +BOOL fip_init(BOOL applymodule); + +/// Closes driver handle and unloads module +BOOL fip_deinit(); + +/// Clears the FIP display. +BOOL fip_clear(); + +/// Writes character to FIP display at given position. +BOOL fip_write_char(int ch, int pos); + +/// Writes right-aligned string to display (up to 7 symbols) +BOOL fip_write_string(const char *str); + +/// Writes special display indicators (see FIP_SPECIAL_*) +BOOL fip_write_special(int id, BOOL onoff); + +BOOL fip_get_special(int id); + +/// Gets pressed button code (see FIP_KEY_*) if available or waits for it +int fip_read_button(BOOL blocked = TRUE); + +//////////////////////////////////// +/// Button codes definitions: +enum +{ + /// 1) definitions for the buttons on the front panel: + FIP_KEY_NONE = 0, + FIP_KEY_FRONT_EJECT, + FIP_KEY_FRONT_PLAY, + FIP_KEY_FRONT_STOP, + FIP_KEY_FRONT_PAUSE, + FIP_KEY_FRONT_SKIP_PREV, + FIP_KEY_FRONT_SKIP_NEXT, + FIP_KEY_FRONT_REWIND, + FIP_KEY_FRONT_FORWARD, + + /// 2) definitions for the keys on the remote control: + FIP_KEY_POWER, + FIP_KEY_EJECT, + + FIP_KEY_ONE, + FIP_KEY_TWO, + FIP_KEY_THREE, + FIP_KEY_FOUR, + FIP_KEY_FIVE, + FIP_KEY_SIX, + FIP_KEY_SEVEN, + FIP_KEY_EIGHT, + FIP_KEY_NINE, + FIP_KEY_ZERO, + + FIP_KEY_CANCEL, + FIP_KEY_SEARCH, + FIP_KEY_ENTER, + + FIP_KEY_OSD, + FIP_KEY_SUBTITLE, + FIP_KEY_SETUP, + FIP_KEY_RETURN, + FIP_KEY_TITLE, + FIP_KEY_PN, + FIP_KEY_MENU, + FIP_KEY_AB, + FIP_KEY_REPEAT, + + FIP_KEY_UP, + FIP_KEY_DOWN, + FIP_KEY_LEFT, + FIP_KEY_RIGHT, + + FIP_KEY_VOLUME_DOWN, + FIP_KEY_VOLUME_UP, + FIP_KEY_PAUSE, + + FIP_KEY_REWIND, + FIP_KEY_FORWARD, + FIP_KEY_SKIP_PREV, + FIP_KEY_SKIP_NEXT, + + FIP_KEY_PLAY, + FIP_KEY_STOP, + + FIP_KEY_SLOW, + FIP_KEY_AUDIO, + FIP_KEY_VMODE, + FIP_KEY_MUTE, + FIP_KEY_ZOOM, + FIP_KEY_PROGRAM, + FIP_KEY_PBC, + FIP_KEY_ANGLE, +}; + +enum +{ + FIP_SPECIAL_PBC = 0, + FIP_SPECIAL_MP3 = 1, + FIP_SPECIAL_CAMERA = 2, + FIP_SPECIAL_COLON1 = 3, + FIP_SPECIAL_DOLBY = 4, + FIP_SPECIAL_COLON2 = 5, + FIP_SPECIAL_DTS = 6, + + FIP_SPECIAL_S = 7, + FIP_SPECIAL_V = 8, + FIP_SPECIAL_CD = 9, + + FIP_SPECIAL_PLAY = 10, + FIP_SPECIAL_PAUSE = 11, + FIP_SPECIAL_ALL = 12, + FIP_SPECIAL_REPEAT = 13, + + FIP_SPECIAL_DVD = 14, + + FIP_SPECIAL_CIRCLE_1 = 15, + FIP_SPECIAL_CIRCLE_2, + FIP_SPECIAL_CIRCLE_3, + FIP_SPECIAL_CIRCLE_4, + FIP_SPECIAL_CIRCLE_5, + FIP_SPECIAL_CIRCLE_6, + FIP_SPECIAL_CIRCLE_7, + FIP_SPECIAL_CIRCLE_8, + FIP_SPECIAL_CIRCLE_9, + FIP_SPECIAL_CIRCLE_10, + FIP_SPECIAL_CIRCLE_11, + FIP_SPECIAL_CIRCLE_12, +}; + +#ifdef __cplusplus +} +#endif + + +#endif // of SP_FIP_H diff --git a/src/libsp/sp_flash.cpp b/src/libsp/sp_flash.cpp new file mode 100644 index 0000000..6a9bfa6 --- /dev/null +++ b/src/libsp/sp_flash.cpp @@ -0,0 +1,276 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - FLASH-ROM interface functions source file. + * \file sp_flash.cpp + * \author bombur + * \version 0.2 + * \date 30.03.2010 4.05.2004 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include + +#include +#include + +#include "sp_misc.h" +#include "sp_fip.h" +#include "sp_cdrom.h" +#include "sp_flash.h" +#include "sp_msg.h" + +static int file_handle = -1, mtd_handle = -1; +static DWORD file_read_cnt, num_read = 0, num_written; +static DWORD flen; +static void *buf = NULL; +static BYTE *curbuf = NULL; +static BYTE *cur_addr, *cur_a; + +static struct region_info_user riu[16]; +static int reg_cnt = 0; + +static char fwver[16]; + +int flash_end() +{ + if (buf != NULL) + SPSafeFree(buf); + if (file_handle >= 0) + { + close(file_handle); + file_handle = -1; + } + if (mtd_handle >= 0) + { + close(mtd_handle); + mtd_handle = -1; + } + return 0; +} + + +int flash_file(char *fname, DWORD address) +{ + flash_end(); + + int mtdpos; + int err = 0; + + file_handle = cdrom_open(fname, O_RDONLY | O_BINARY); + if (file_handle == -1) + return -1; + flen = lseek(file_handle, 0, SEEK_END); + lseek(file_handle, 0, SEEK_SET); + +#ifdef WIN32 + char *mtdpath = "mtd0"; + mtd_handle = open(mtdpath, O_RDWR | O_BINARY | O_CREAT | O_TRUNC); + chmod(mtdpath, 0777); + static BYTE mtdbuf[2*1024*1024]; + cur_addr = mtdbuf; +#else + mtd_handle = open("/dev/mtd/0", O_RDWR); + cur_addr = (BYTE *)address; +#endif + + if (mtd_handle == -1) + { + err = -2; + goto err; + } + + mtdpos = lseek(mtd_handle, address, SEEK_SET); + if (mtdpos == -1) + { + err = -3; + goto err; + } + + buf = SPmalloc(0x10000); + if (buf == NULL) + { + err = -4; + goto err; + } + + file_read_cnt = 0; + num_read = 0; + + // gather info + struct mtd_info_user miu; + if (ioctl(mtd_handle, MEMGETINFO, &miu) == 0) + { + msg("Flash: MTD size=%d erase_size=%d\n", miu.size, miu.erasesize); + } + if (ioctl(mtd_handle, MEMGETREGIONCOUNT, ®_cnt) == 0) + { + msg("Flash: %d erase regions:\n", reg_cnt); + if (reg_cnt > 16) + reg_cnt = 16; + for (int i = 0; i < reg_cnt; i++) + { + riu[i].regionindex = i; + if (ioctl(mtd_handle, MEMGETREGIONINFO, &riu[i]) == 0) + { + msg(" [%d] %08x %08x num=%d\n", i, riu[i].offset, riu[i].erasesize, riu[i].numblocks); + } + } + } + + num_read = read(file_handle, buf, 32); + if (num_read < 32 || memcmp(buf, "-rom1fs-", 8) != 0) + { + msg("Flash: Wrong firmware file type or file error.\n"); + err = -5; + goto err; + } + + lseek(file_handle, 0, SEEK_SET); + num_read = 0; + strncpy(fwver, (char *)buf + 16, 16); + + // test size + if (flen > address + miu.size) + { + msg("Flash: Firmware size (%d) exceeds flash size (%d) for given offset (%d)\n", flen, miu.size, address); + err = -6; + goto err; + } + + msg("Flash: Found '%s'. Starting flash...\n", fwver); + + return 0; + +err: + flash_end(); + + return err; +} + +int flash_cycle() +{ + if (buf == NULL || file_handle < 0 || mtd_handle < 0) + return -1; + + if (num_read == 0) + { + // find corresponding erase zone + int len = 0; +#ifdef WIN32 + len = 0x1000; +#else + for (int i = 0; i < reg_cnt; i++) + { + if ((DWORD)cur_addr >= riu[i].offset && (DWORD)cur_addr < riu[i].offset + riu[i].erasesize * riu[i].numblocks) + { + len = riu[i].erasesize; + break; + } + } +#endif + if (len == 0) + { + msg("Flash: Cannot find erase region for address %08x", (DWORD)cur_addr); + return -1; + } + num_read = read(file_handle, buf, len); + curbuf = (BYTE *)buf; + if (num_read <= 0) + { + flash_end(); + return 100; + } + + // test + if (memcmp(cur_addr, buf, num_read) != 0) + { + // erase FLASH memory + struct erase_info_user eiu; + eiu.start = (DWORD)cur_addr; + eiu.length = len; + ioctl(mtd_handle, MEMUNLOCK, &eiu); + + if (ioctl(mtd_handle, MEMERASE, &eiu) != 0) + { + msg("Flash: MEMERASE ERR(%x, %x)\n", eiu.start, eiu.length); + goto err; + } + + lseek(mtd_handle, (DWORD)cur_addr, SEEK_SET); + + cur_a = cur_addr; + cur_addr += len; + } + else + { + cur_addr += len; + file_read_cnt += num_read; + + num_read = 0; + } + } + + if (num_read > 0) + { + int len = Min(0x1000, (int)num_read); + num_written = write(mtd_handle, curbuf, len); + if (num_written != (DWORD)len) + { + msg("Flash: write ERR(%x, %x)\n", file_read_cnt, num_written); + goto err; + } +#ifndef WIN32 + if (memcmp(cur_a, curbuf, len) != 0) + { + msg("Flash: Verify error @ %08x...\n", cur_a); + return -2; + } +#endif + cur_a += len; + curbuf += len; + num_read -= len; + file_read_cnt += len; + } + + if (file_read_cnt > flen) + file_read_cnt = flen; + + return file_read_cnt * 100 / flen; +err: + flash_end(); + return -1; +} + +DWORD flash_get_memory_size() +{ +#ifdef WIN32 + return 1*1024*1024; +#else + int mtd_handle = open("/dev/mtd/0", O_RDWR); + if (mtd_handle == -1) + return 0; + struct mtd_info_user miu; + int size = 0; + if (ioctl(mtd_handle, MEMGETINFO, &miu) == 0) + size = miu.size; + close(mtd_handle); + return size; +#endif +} diff --git a/src/libsp/sp_flash.h b/src/libsp/sp_flash.h new file mode 100644 index 0000000..7883cd3 --- /dev/null +++ b/src/libsp/sp_flash.h @@ -0,0 +1,51 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - FLASH interface header. + * \file sp_flash.h + * \author bombur + * \version 0.1 + * \date 4.05.2004 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#ifndef SP_FLASH_H +#define SP_FLASH_H + + +#ifdef __cplusplus +extern "C" { +#endif + +//////////////////////////////////// +/// Functions: + +/// Starts file upload to FLASH-ROM address. +/// uClinux kernel compiled with 'CONFIG_MTD=y' (and 'CONFIG_MTD_CHAR=y') is required. +int flash_file(char *fname, DWORD address); + +/// Call this in cycle to upload the file. Progress is returned, in per cents. +/// When upload is finished, 100 is returned. +int flash_cycle(); + +DWORD flash_get_memory_size(); + +#ifdef __cplusplus +} +#endif + + +#endif // of SP_FLASH_H diff --git a/src/libsp/sp_i2c.h b/src/libsp/sp_i2c.h new file mode 100644 index 0000000..103b384 --- /dev/null +++ b/src/libsp/sp_i2c.h @@ -0,0 +1,42 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - I2C data transfer functions header file. + * \file sp_i2c.h + * \author bombur + * \version 0.1 + * \date 4.05.2004 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#ifndef SP_I2C_H +#define SP_I2C_H + +#ifdef __cplusplus +extern "C" { +#endif + +/// Reads data from the I2C bus +BOOL i2c_data_in(BYTE addr, BYTE idx, BYTE *data, int num); + +/// Writes data to the I2C bus +BOOL i2c_data_out(BYTE addr, BYTE idx, BYTE *data, int num); + +#ifdef __cplusplus +} +#endif + +#endif // of SP_I2C_H diff --git a/src/libsp/sp_io.h b/src/libsp/sp_io.h new file mode 100644 index 0000000..d0ec4bc --- /dev/null +++ b/src/libsp/sp_io.h @@ -0,0 +1,55 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - Port IO header. Stripped from + * \file sp_io.h + * \author bombur + * \version 0.1 + * \date 4.05.2004 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#ifndef SP_IO_H +#define SP_IO_H +#ifndef __ASM_ARM_IO_H + +#define __arch_getb(a) (*(volatile unsigned char *)(a)) +#define __arch_getw(a) (*(volatile unsigned short *)(a)) +#define __arch_getl(a) (*(volatile unsigned int *)(a)) + + +#define __arch_putb(v,a) (*(volatile unsigned char *)(a) = (v)) +#define __arch_putw(v,a) (*(volatile unsigned short *)(a) = (v)) +#define __arch_putl(v,a) (*(volatile unsigned int *)(a) = (v)) + +#define __raw_writeb(v,a) __arch_putb(v,a) +#define __raw_writew(v,a) __arch_putw(v,a) +#define __raw_writel(v,a) __arch_putl(v,a) + +#define __raw_readb(a) __arch_getb(a) +#define __raw_readw(a) __arch_getw(a) +#define __raw_readl(a) __arch_getl(a) + +#define outb(v,p) __raw_writeb(v,p) +#define outw(v,p) __raw_writew(v,p) +#define outl(v,p) __raw_writel(v,p) + +#define inb(p) __raw_readb(p) +#define inw(p) __raw_readw(p) +#define inl(p) __raw_readl(p) + +#endif +#endif // of SP_IO_H diff --git a/src/libsp/sp_khwl.h b/src/libsp/sp_khwl.h new file mode 100644 index 0000000..bfb67df --- /dev/null +++ b/src/libsp/sp_khwl.h @@ -0,0 +1,190 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - KHWL driver's header. + * \file sp_khwl.h + * \author bombur + * \version 0.1 + * \date 4.07.2004 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#ifndef SP_KHWL_H +#define SP_KHWL_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "sp_khwl_prop.h" +#include "sp_video.h" + + +// This trick transforms [0..255] range into [0..65535] range (instead of about 0..255*256) +#define RANGE8TO16(x) (((x) << 8) | (x)) + +/// Play modes +enum KHWL_PLAY_MODE_TYPE +{ + KHWL_PLAY_MODE_NORMAL = 0, + KHWL_PLAY_MODE_STEP = 1, + KHWL_PLAY_MODE_IFRAME = 2, + KHWL_PLAY_MODE_REV = 5, + KHWL_PLAY_MODE_IFRAME_REV = 6, +}; + +//////////////////////////////////// +/// Structures: + +/// OSD setup struct +typedef struct _KHWL_OSDSTRUCT +{ + void *addr; + int width; + int height; + int bpp; + int flags; + +} KHWL_OSDSTRUCT; + +/// Window coords and size +typedef struct _KHWL_WINDOW +{ + int x, y; + int w, h; + +} KHWL_WINDOW; + + +/// YUV frame +typedef struct +{ + void *y_buf; + void *uv_buf; + int y_offs, uv_offs; + int y_num, uv_num; + +} KHWL_YUV_FRAME; + +/// Address-data pair (used for EEPROM and PIO) +typedef struct +{ + DWORD Addr; + DWORD Data; + +} KHWL_ADDR_DATA; + +/// Used for hapeningwait() +typedef struct +{ + DWORD timeout_microsecond; + DWORD mask; + +} KHWL_WAITABLE; + +extern KHWL_OSDSTRUCT osd; + +//////////////////////////////////// +/// Functions: + +/// Loads driver module and opens it. +BOOL khwl_init(BOOL applymodule); + +/// Closes driver handle and unloads module +BOOL khwl_deinit(); + +/// Resets the driver +BOOL khwl_reset(); + +/// OSD switch (enable). Fills given structure with params. +int khwl_osd_switch(KHWL_OSDSTRUCT *osd, BOOL autoupd); + +/// Update OSD. Returns FALSE when user pressed 'exit' for Win32. +BOOL khwl_osd_update(); + +/// Set general alpha for OSD (0-65535) +int khwl_osd_setalpha(int alpha); + +void khwl_get_osd_size(int *width, int *height); + +void khwl_osd_setfullscreen(BOOL is); + +/// Set OSD palette entry +void khwl_osd_setpalette(BYTE *pal, int entry, BYTE r, BYTE g, BYTE b, BYTE a); + +/// Set KHWL property +int khwl_setproperty(KHWL_PROPERTY_SET pset, int id, int size, void *value); +/// Get KHWL property +int khwl_getproperty(KHWL_PROPERTY_SET pset, int id, int size, void *value); + + +/// Initialize color conversion tables +void khwl_inityuv(); +/// RGB -> YUV +void khwl_vgargbtotvyuv(BYTE R, BYTE G, BYTE B, BYTE *y, BYTE *u, BYTE *v); +/// YUV -> RGB +void khwl_tvyuvtovgargb(BYTE y, BYTE u, BYTE v, BYTE *R, BYTE *G, BYTE *B); +/// JPEG YUV -> RGB +void khwl_jpegyuvtorgb(BYTE y, BYTE u, BYTE v, BYTE *R, BYTE *G, BYTE *B); + +/// Display YUV buffer +int khwl_displayYUV(KHWL_YUV_FRAME *f); + +/// Audio switch +int khwl_audioswitch(BOOL ison); + +/// Get supported audio sample rates list (the last is -1). +int *khwl_get_samplerates(); + +/// Decoder play (mode = 0, 2, 6 ?) +int khwl_play(int mode); + +/// Decoder stop +int khwl_stop(); + +/// Decoder pause +int khwl_pause(); + +/// Restore params +BOOL khwl_restoreparams(); + +/// Block/Unblock irq for data change +BOOL khwl_blockirq(BOOL block); + +/// Wait to process media packets +BOOL khwl_happeningwait(DWORD *mask); + +/// Poll khwl device +BOOL khwl_poll(WORD mask, DWORD timeout); + +/// IDE turn on/off +BOOL khwl_ideswitch(BOOL ison); + +/// Get CPU frequency +int khwl_getfrequency(); + +/// Set CPU frequency (100 <= freq <= 202) +/// Caution! Use this in predicaments only! +BOOL khwl_setfrequency(int freq); + +/// Get hardware string +char *khwl_gethw(); + +#ifdef __cplusplus +} +#endif + +#endif // of SP_KHWL_H diff --git a/src/libsp/sp_khwl_colors.cpp b/src/libsp/sp_khwl_colors.cpp new file mode 100644 index 0000000..7484e07 --- /dev/null +++ b/src/libsp/sp_khwl_colors.cpp @@ -0,0 +1,278 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - color space support functions source file + * \file sp_khwl_colors.c + * \author bombur + * \version 0.1 + * \date 4.07.2004 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#include +#include + +#include "sp_misc.h" +#include "sp_khwl.h" + + + +#define USE_NEW_YUV +//#define USE_GAMMA + + +#define START 0 +#define END 65536 +#define PRECISION 20 + +#define SCALEBITS_INT 8 +#define FIX_INT(x) ((WORD) ((x) * (1L<> SCALEBITS_INT) + Y_ADD_INT) +#define RGB2U(r,g,b) (((- U_R_INT * r - U_G_INT * g + U_B_INT * b) >> (SCALEBITS_INT + 2)) + U_ADD_INT) +#define RGB2V(r,g,b) (((V_R_INT * r - V_G_INT * g - V_B_INT * b) >> (SCALEBITS_INT + 2)) + V_ADD_INT) + + +// yuv -> rgb constants +#define RGB_Y_INT 1.164 + +#define B_U_INT 2.018 +#define G_U_INT 0.391 + +#define G_V_INT 0.813 +#define R_V_INT 1.596 + +#define RGB555(R,G,B) ((clip[R] << 7) & 0x7c00) | ((clip[G] << 2) & 0x03e0) | ((clip[B] >> 3) & 0x001f) +#define RGB565(R,G,B) ((clip[R] << 8) & 0xf800) | ((clip[G] << 3) & 0x07e0) | ((clip[B] >> 3) & 0x001f) + +// yuv -> rgb lookup tables +static DWORD RGB_Y_tab[256]; +static DWORD B_U_tab[256]; +static DWORD G_U_tab[256]; +static DWORD G_V_tab[256]; +static DWORD R_V_tab[256]; + +//smart clipping table +static BYTE clip_tab[2048]; +static BYTE *clip = NULL; + +void khwl_inityuv() +{ + DWORD i; + + for (i = 0; i < 512; i++) + clip_tab[i] = 0; + for (i = 512; i < 512+256; i++) + clip_tab[i] = (BYTE)(i - 512); + for (i = 512+256; i < 2048; i++) + clip_tab[i] = 255; + + clip = clip_tab + 512; + + for (i = 0; i < 256; i++) + { + RGB_Y_tab[i] = FIX_INT(RGB_Y_INT) * (i - Y_ADD_INT); + B_U_tab[i] = FIX_INT(B_U_INT) * (i - U_ADD_INT); + G_U_tab[i] = FIX_INT(G_U_INT) * (i - U_ADD_INT); + G_V_tab[i] = FIX_INT(G_V_INT) * (i - V_ADD_INT); + R_V_tab[i] = FIX_INT(R_V_INT) * (i - V_ADD_INT); + } +} + +void new_rgbtoyuv(BYTE R, BYTE G, BYTE B, BYTE *y, BYTE *u, BYTE *v) +{ + *y = (BYTE)RGB2Y(R,G,B); + *u = (BYTE)RGB2U(R,G,B); + *v = (BYTE)RGB2V(R,G,B); +} + +void new_yuvtorgb(BYTE y, BYTE u, BYTE v, BYTE *R, BYTE *G, BYTE *B) +{ + int rgb_y = RGB_Y_tab[y]; + int b_u = B_U_tab[u]; + int g_uv = G_U_tab[u] + G_V_tab[v]; + int r_v = R_V_tab[v]; + + register int r = (rgb_y + r_v) >> SCALEBITS_INT; + register int g = (rgb_y - g_uv) >> SCALEBITS_INT; + register int b = (rgb_y + b_u) >> SCALEBITS_INT; + *R = clip[r]; + *G = clip[g]; + *B = clip[b]; +} + +#ifdef USE_GAMMA + +static DWORD powertwodottwotable[PRECISION + 1] = +{ + 0, 89, 413, 1008, 1899, 3104, 4636, 6507, 8729, 11312, 14263, 17590, + 21301, 25403, 29901, 34802, 40112, 45835, 51977, 58542, 65536 +}; + +static DWORD invertofpowertwodottwotable[PRECISION + 1]= +{ + 0, 16792, 23010, 27667, 31533, 34899, 37914, 40666, 43211, 45587, 47824, + 49941, 51956, 53881, 55727, 57502, 59214, 60869, 62471, 64025, 65536 +}; + +// computes y/65536=f(x/65536) with 0<=x,y<65536 with f=^2.2 or ^(1/2.2) given as a DWORD table (have to code the 65536 case) +static WORD compute_f(DWORD beg, DWORD end, DWORD prec, DWORD *table, WORD x) +{ + long a,b,fa,fb,entry; + + entry = (x - beg) * prec / (end - beg); + + if ((entry < 0) || (entry >= 20)) + { + printf("memory corrupted x=%hd beg=%ld end=%ld prec=%ld\n", x, beg, end, prec); + return 0; + } + + a = entry * (end - beg) / prec + beg; + b = (entry + 1) * (end - beg) / prec + beg; + fa = table[entry]; + fb = table[entry + 1]; + return (WORD)(fa + (fb - fa) * (x - a) / (b - a)); +} +#endif + + +// see video demystified page 43 + +static void internal_rgbtoyuv(WORD R, WORD G, WORD B, WORD *y, WORD *u,WORD *v) +{ + long yraw, uraw, vraw; + + yraw = ( 257*R +504*G + 98*B)/1000 + RANGE8TO16(16); + uraw = (-148*R -291*G +439*B)/1000 + RANGE8TO16(128); + vraw = ( 439*R -368*G - 71*B)/1000 + RANGE8TO16(128); + + *y = (WORD)MAX(MIN(yraw, RANGE8TO16(235)), RANGE8TO16(16)); + *u = (WORD)MAX(MIN(uraw, RANGE8TO16(240)), RANGE8TO16(16)); + *v = (WORD)MAX(MIN(vraw, RANGE8TO16(240)), RANGE8TO16(16)); +} + +/* +static void internal_yuvtorgb(WORD y, WORD u, WORD v, WORD *R, WORD *G, WORD *B) +{ + long Rraw, Graw, Braw; + long ym = y - RANGE8TO16(16), um = u - RANGE8TO16(128), vm = v - RANGE8TO16(128); + + Rraw = (1164 * ym + 1596 * vm)/1000; + Graw = (1164 * ym + 392 * um - 813 * vm)/1000; + Braw = (1164 * ym + 2017 * um )/1000; + + *R = (WORD)MAX(0, MIN(Rraw, RANGE8TO16(255))); + *G = (WORD)MAX(0, MIN(Graw, RANGE8TO16(255))); + *B = (WORD)MAX(0, MIN(Braw, RANGE8TO16(255))); +} +*/ + +void khwl_vgargbtotvyuv(BYTE R, BYTE G, BYTE B, BYTE *y, BYTE *u, BYTE *v) +{ +//#ifdef USE_NEW_YUV +// new_rgbtoyuv(R, G, B, y, u, v); +//#else +#ifdef USE_GAMMA + WORD Rc = compute_f(START, END, PRECISION, powertwodottwotable, (WORD)RANGE8TO16(R)); + WORD Gc = compute_f(START, END, PRECISION, powertwodottwotable, (WORD)RANGE8TO16(G)); + WORD Bc = compute_f(START, END, PRECISION, powertwodottwotable, (WORD)RANGE8TO16(B)); +#else + WORD Rc = (WORD)RANGE8TO16(R); + WORD Gc = (WORD)RANGE8TO16(G); + WORD Bc = (WORD)RANGE8TO16(B); +#endif + WORD wy, wu, wv; + internal_rgbtoyuv(Rc, Gc, Bc, &wy, &wu, &wv); + *y = (BYTE)(wy >> 8); + *u = (BYTE)(wu >> 8); + *v = (BYTE)(wv >> 8); +//#endif +} + +void khwl_tvyuvtovgargb(BYTE y, BYTE u, BYTE v, BYTE *R, BYTE *G, BYTE *B) +{ +#ifdef USE_NEW_YUV + new_yuvtorgb(y, u, v, R, G, B); +#else + WORD Ruc, Guc, Buc; + internal_yuvtorgb((WORD)RANGE8TO16(y), (WORD)RANGE8TO16(u), (WORD)RANGE8TO16(v), &Ruc, &Guc, &Buc); +#ifdef USE_GAMMA + *R = (BYTE)(compute_f(START, END, PRECISION, invertofpowertwodottwotable, Ruc) >> 8); + *G = (BYTE)(compute_f(START, END, PRECISION, invertofpowertwodottwotable, Guc) >> 8); + *B = (BYTE)(compute_f(START, END, PRECISION, invertofpowertwodottwotable, Buc) >> 8); +#else + *R = (BYTE)(Ruc >> 8); + *G = (BYTE)(Guc >> 8); + *B = (BYTE)(Buc >> 8); +#endif +#endif +} + + +void khwl_jpegyuvtorgb(BYTE y, BYTE u, BYTE v, BYTE *R, BYTE *G, BYTE *B) +{ + long Rraw, Graw, Braw; + long ym = RANGE8TO16(y), um = RANGE8TO16(u) - RANGE8TO16(128); + long vm = RANGE8TO16(v) - RANGE8TO16(128); + + Rraw = (1000 * ym + 1000*vm)/1000; + Graw = (1000 * ym - 509 * um - 194 * vm)/1000; + Braw = (1000 * ym + 1000*um )/1000; + + Rraw = (WORD)MAX(0, MIN(Rraw, RANGE8TO16(255))); + Graw = (WORD)MAX(0, MIN(Graw, RANGE8TO16(255))); + Braw = (WORD)MAX(0, MIN(Braw, RANGE8TO16(255))); + *R = (BYTE)(Rraw >> 8); + *G = (BYTE)(Graw >> 8); + *B = (BYTE)(Braw >> 8); +} + + +/* + float y = (float)ybuf[i] / 255.0f; + float u = (float)uvbuf[ui*2+1] / 255.0f -0.5f; + float v = (float)uvbuf[ui*2] / 255.0f -0.5f; + + b = (y + u) * 255.0f; + if (b < 0) b = 0; + if (b > 255)b = 255; + + g = (y - 0.509 * u - 0.194*v) * 255.0f; + if (g < 0)g = 0; + if (g > 255)g = 255; + + r = (y + v) * 255.0f; + if (r < 0) r = 0; + if (r > 255)r = 255; + + backbuf[vi] = (b << 16) | (g << 8) | r; +*/ diff --git a/src/libsp/sp_khwl_prop.h b/src/libsp/sp_khwl_prop.h new file mode 100644 index 0000000..db15fe8 --- /dev/null +++ b/src/libsp/sp_khwl_prop.h @@ -0,0 +1,393 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - KHWL driver's properties. + * \file sp_khwl_prop.h + * \author bombur + * \version 0.1 + * \date 4.07.2004 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#ifndef SP_KHWL_PROP_H +#define SP_KHWL_PROP_H + +#ifdef __cplusplus +extern "C" { +#endif + +// property sets +typedef enum +{ + KHWL_COMMON_SET = 1, + KHWL_EEPROM_SET = 5, // eeprom + KHWL_BOARDINFO_SET = 6, // general properties + KHWL_VIDEO_SET = 7, // video properties + KHWL_AUDIO_SET = 8, // audio properties + KHWL_TIME_SET = 9, // time properties + KHWL_SUBPICTURE_SET = 10, // subpicture properties + KHWL_DVI_TRANSMITTER_SET = 13, // DVI transmitter + KHWL_DECODER_SET = 14, // Mpeg decoder + KHWL_OSD_SET = 17, // OSD properties + +} KHWL_PROPERTY_SET; + +// struct used to set property +typedef struct _KHWL_PROPERTY +{ + KHWL_PROPERTY_SET pset; + int id; + int size; + void *v; + +} KHWL_PROPERTY; + +//////////////////////////////////////////////////// + +// ----------------- KHWL_COMMON_SET (1) common +typedef enum +{ + eDoAudioLater = 15, + +} KHWL_COMMON; + +// ----------------- KHWL_EEPROM_SET (5) eeprom +typedef enum +{ + eEepromAccess = 0, + +} KHWL_EEPROM_SET_PROP; + +// ----------------- KHWL_BOARDINFO_SET (6) general properties +typedef enum +{ + ebiBoardVersion = 2, + ebiAPMState = 3, + ebiPIOAccess = 4, + ebiCommand = 7, + +} KHWL_BOARDINFO_SET_PROP; + +// ----------------- KHWL_VIDEO_SET (7) video properties +typedef enum +{ + evOutputDevice = 1, + evTvOutputFormat = 2, + evTvStandard = 3, + evBrightness = 4, + evContrast = 5, + evSaturation = 6, + evInAspectRatio = 7, + evInStandard = 8, + evOutDisplayOption = 9, + evSourceWindow = 10, + evZoomedWindow = 11, + evMaxDisplayWindow = 12, + evDestinationWindow = 13, + evValidWindow = 14, + evCustomHdtvParams = 15, + evSpeed = 16, + evScartAspectRatio = 23, + evYUVWriteParams = 25, + evDigOvOnlyParams = 28, + evVOBUReverseSpeed = 31, + evForcedProgressiveAlways = 34, + evMacrovisionFlags = 37, + evForcePanScanDefaultSize = 56, + +} KHWL_VIDEO_SET_PROP; + +// ----------------- KHWL_AUDIO_SET (8) audio properties +typedef enum +{ + eaVolumeRight = 5, + eaVolumeLeft = 6, + eAudioFormat = 7, + eAudioSampleRate = 8, + eAudioNumberOfChannels = 9, + eAudioNumberOfBitsPerSample = 10, + eAudioDigitalOutput = 13, + +} KHWL_AUDIO_SET_PROP; + +// ----------------- KHWL_TIME_SET (9) time properties +typedef enum +{ + etimSystemTimeClock = 2, + etimVOPTimeIncrRes = 5, + etimVideoCTSTimeScale = 6, + etimAudioCTSTimeScale = 7, + etimVideoFrameDisplayedTime = 8, + +} KHWL_TIME_SET_PROP; + +// ----------------- KHWL_SUBPICTURE_SET (10) subpicture properties +typedef enum +{ + eSubpictureCmd = 0, + eSubpictureUpdatePalette = 1, + eSubpictureUpdateButton = 2, +} KHWL_SUBPICTURE_SET_PROP; + +// ----------------- KHWL_DVI_TRANSMITTER_SET (13) DVI transmitter +typedef enum +{ + edtAccessRegister = 0, + edtOutputEnable = 1, + +} KHWL_DVI_TRANSMITTER_SET_PROP; + +// ----------------- KHWL_DECODER_SET (14) Mpeg decoder +typedef enum +{ + edecVideoStd = 2, + edecOsdFlicker = 3, + edecForceFixedVOPRate = 5, + edecCSSChlg = 7, // get challenge + edecCSSKey1 = 8, // set key + edecCSSChlg2 = 9, // set challenge + edecCSSKey2 = 10, // get key + edecCSSDiscKey = 11, // set + edecCSSTitleKey = 12, // set + +} KHWL_DECODER_SET_PROP; + +// ----------------- KHWL_OSD_SET (17) OSD properties +typedef enum +{ + eOsdCommand = 0, + eOsdDestinationWindow = 2, + +} KHWL_OSD_SET_PROP; + + +//////////////////////////////////////////////////////// + +/// Aux. structures + +/// Time clock type +typedef struct +{ + DWORD timeres; // 90000 + ULONGLONG pts; + +} KHWL_TIME_TYPE; + +/// Board command +typedef enum +{ + ebiCommand_HardwareReset = 1, + ebiCommand_VideoHwBlackFrame = 2, + +} KHWL_BOARD_COMMAND_TYPE; + +/// Audio format type +typedef enum +{ + eAudioFormat_MPEG1 = 1, // mpeg1 layer 1 + eAudioFormat_MPEG2 = 2, // mpeg1 layer 2 ? + eAudioFormat_AC3 = 3, // ac3 + eAudioFormat_PCM = 4, // lpcm + eAudioFormat_DTS = 5, // dts + eAudioFormat_DVD_AUDIO = 6, // dvd audio + eAudioFormat_REVERSE_PCM = 7, // rpcm + eAudioFormat_AAC = 8, // aac + eAudioFormat_MPEG1_LAYER3 = 9, // mpeg1 layer 3 + eAudioFormat_MPEG2_LAYER1 = 10, // mpeg2 layer 1 + eAudioFormat_MPEG2_LAYER2 = 11, // mpeg2 layer 2 + eAudioFormat_MPEG2_LAYER3 = 12, // mpeg2 layer 3 + + eAudioFormat_UNKNOWN = 0, + +} KHWL_AUDIO_FORMAT_TYPE; + +/// Digital audio output type +typedef enum +{ + eAudioDigitalOutput_Pcm = 0, + eAudioDigitalOutput_Compressed = 1, + +} KHWL_AUDIO_DIGITAL_OUTPUT_TYPE; + +/// Output video device +typedef enum +{ + evOutputDevice_VGA = 0, + evOutputDevice_TV = 1, + evOutputDevice_HDTV = 0x20, + evOutputDevice_DigOvOnly = 0x21, + evOutputDevice_HdtvSubd = 0x400, + +} KHWL_OUTPUT_DEVICE_TYPE; + +/// TV standard used +typedef enum +{ + evTvStandard_NTSC = 0, + evTvStandard_PAL = 2, + evTvStandard_PAL60 = 8, + evTvStandard_PALM = 10, + + // HD/Progressive formats + + evTvStandard_480P = 11, + evTvStandard_576P = 12, + evTvStandard_720P = 13, + evTvStandard_1080I = 14, + + evTvStandard_720P50 = 255, + +} KHWL_TV_STANDARD_TYPE; + +/// TV Output (cable) format used +typedef enum +{ + evTvOutputFormat_NONE = -1, + evTvOutputFormat_COMPOSITE = 0, + evTvOutputFormat_OUTPUT_OFF = 0x40, + evTvOutputFormat_COMPONENT_YUV = 0x80, + evTvOutputFormat_COMPONENT_RGB = 0xc0, + evTvOutputFormat_COMPONENT_RGB_SCART = 0x200, + + evTvOutputFormat_DVI = 0x1000, + +} KHWL_TV_OUTPUT_FORMAT_TYPE; + +/// TV Aspect ratio used +typedef enum +{ + evInAspectRatio_none = 0, + evInAspectRatio_4x3 = 2, + evInAspectRatio_16x9 = 3, + +} KHWL_IN_ASPECT_RATIO_TYPE; + +/// Out display ratio used +typedef enum +{ + evOutDisplayOption_Normal = 0, + evOutDisplayOption_16x9to4x3_PanScan = 1, + evOutDisplayOption_16x9to4x3_LetterBox = 2, + evOutDisplayOption_4x3to16x9_HorzCenter = 3, + evOutDisplayOption_4x3to16x9_VertCenter = 4, + +} KHWL_OUT_DISPLAY_OPTION_TYPE; + +/// OSD switch +typedef enum +{ + eOsdOn = 0, + eOsdOff = 1, + eOsdFlush = 2, + +} KHWL_OSD_COMMAND_TYPE; + + +/// YUV buffer format +typedef enum +{ + KHWL_YUV_420_UNPACKED = 0, + KHWL_YUV_422_UNPACKED, + +} KHWL_YUV_DATA_FORMAT_TYPE; + +/// YUV setup +typedef struct +{ + WORD wWidth; // Picture width in pixels + WORD wHeight; // Picture height in lines + KHWL_YUV_DATA_FORMAT_TYPE YUVFormat; + +} KHWL_YUV_WRITE_PARAMS_TYPE; + +// DigOV params +typedef struct +{ + DWORD HFreq; + DWORD VFreq; + DWORD VideoWidth; + DWORD VideoHeight; + + DWORD HSyncTotal; + DWORD PreHSync; + DWORD HSyncActive; + DWORD PostHSync; + + DWORD VSyncTotal; + DWORD PreVSync; + DWORD VSyncActive; + DWORD PostVSync; + + DWORD PixelFreq; + DWORD Interlaced; + + BYTE HSyncPolarity; + BYTE VSyncPolarity; + + BYTE BitsPerClock; + KHWL_TV_STANDARD_TYPE TvHdtvStandard; + BYTE Ccir; + BYTE InvertField; + BYTE SyncEnable; + BYTE Vip20; + DWORD SyncGen; +} KHWL_DIG_OV_PARAMS; + + +/// SPU commands (bitfields) +typedef enum +{ + KHWL_SPU_ENABLE = 0x2, + KHWL_SPU_BUTTONS_ENABLE = 0x100, + +} KHWL_SPU_COMMAND_TYPE; + +/// SPU palette entry type +typedef struct +{ + BYTE yuvX; + BYTE yuvY; + BYTE yuvCr; + BYTE yuvCb; + +} KHWL_SPU_PALETTE_ENTRY; + +/// SPU button type +typedef struct +{ + int left; + int top; + int right; + int bottom; + int color; + int contrast; + +} KHWL_SPU_BUTTON_TYPE; + +/// Fixed VOP rate type +typedef struct +{ + DWORD force; // command for set, status for get + DWORD time_incr; + DWORD incr_res; + +} KHWL_FIXED_VOP_RATE_TYPE; + +#ifdef __cplusplus +} +#endif + +#endif // of SP_KHWL_PROP_H diff --git a/src/libsp/sp_memory.h b/src/libsp/sp_memory.h new file mode 100644 index 0000000..7da9097 --- /dev/null +++ b/src/libsp/sp_memory.h @@ -0,0 +1,267 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer support lib memory debugging routines + * \file sp_memory.h + * \author Storm + * \version 0.1 + * \date 14.07.2002 (23.08.2001) + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#ifndef SP_MEMORY_H +#define SP_MEMORY_H + +// The memory remains... (c) M. +#ifdef SP_MEMDEBUG + +#ifndef __cplusplus +#error Include with SP_MEMDEBUG for C++ files only! +#endif + +#include +#include + +/// allocation types +enum +{ + SP_ALLOC_UNKNOWN = 0, + SP_ALLOC_NEW, + SP_ALLOC_NEW_ARRAY, + SP_ALLOC_MALLOC, + SP_ALLOC_CALLOC, + SP_ALLOC_REALLOC, + SP_ALLOC_STRDUP, + SP_ALLOC_DELETE, + SP_ALLOC_DELETE_ARRAY, + SP_ALLOC_FREE +}; + +/// internal stats +struct SPMemoryStats +{ + DWORD totalReportedMemory; + DWORD totalActualMemory; + DWORD peakReportedMemory; + DWORD peakActualMemory; + DWORD accumulatedReportedMemory; + DWORD accumulatedActualMemory; + DWORD accumulatedAllocUnitCount; + DWORD totalAllocUnitCount; + DWORD peakAllocUnitCount; +}; + +/// internal memory manager's struct +struct SPAllocUnit +{ + DWORD actualSize; + DWORD reqSize; + void *actualAddress; + void *reportedAddress; + char sourceFile[40]; + DWORD sourceLine; + DWORD allocationType; + DWORD allocationNumber; + SPAllocUnit *next; + SPAllocUnit *prev; +}; + +const DWORD SP_MM_HASHSIZE = 4096; +const DWORD SP_MM_HASHSIZEM = SP_MM_HASHSIZE - 1; +static const char* SP_MM_LOGFILENAME = "sp_mem.log"; +static const char* SP_MMDUMP_LOGFILENAME = "sp_mem_dump.log"; +static char *allocTypes[] = {"unknown", "new", "new[]", "malloc", + "calloc", "realloc", "strdup", "delete", + "delete[]", "free"}; + +////////////////////////////////////////////////////////////////////////// +/// Memory allocator/tracer +class SPMemoryManager +{ +public: + /// Functions + + /// dtor. we use it to let us know when we're in static deinit. + /// and log all leaks info + ~SPMemoryManager() {LeakReport();} + + /// singleton-related function + static SPMemoryManager& GetHandle() {return *mm;} + + /// basic mem functions + void *Alloc(const char *file, const DWORD line, const DWORD reqSize, + const DWORD allocType); + void *Realloc(const char *file, const DWORD line, const DWORD reqSize, + void* reqAddress, const DWORD reallocType); + void Dealloc(const char *file, const DWORD line, + const DWORD deallocType, void* reqAddress); + char *Strdup(const char *file, const DWORD line, const char *source); + + /// final leak report + void LeakReport(); + /// dump all current allocations + void DumpAllocs(FILE *fp); + void DumpLastAlloc(FILE *fp); + + /// print memory usage statistics + void LogStats(); + /// set file and line number + void SetOwner(const char *file, const DWORD line); + +//!!!!!!!!!!!!!!!! + void TEST_DUMP(); +//!!!!!!!!!!!!!!!! + + // friends + friend void *operator new(size_t reqSize); + friend void *operator new[](size_t reqSize); + friend void operator delete(void *reqAddress); + friend void operator delete[](void *reqAddress); + +protected: + friend int _crt_init(); + friend int _crt_deinit(); + + static SPMemoryManager *mm; // singleton + +private: + /// Variables + char *srcFile; /// source file of call + DWORD srcLine; /// line number in scrFile + + SPAllocUnit *units; /// allocation units array + SPAllocUnit **unitsBuffer; /// list linked to hash + SPAllocUnit *hashTable[SP_MM_HASHSIZE]; /// hash for fast unit search + SPMemoryStats memStats; /// memory stats + + DWORD currentAllocated; /// currently allocated bytes + DWORD unitsBufferSize; /// number of allocated units + DWORD borderSize; /// size of safe border + + DWORD prefixPattern; /// beginning pattern + DWORD postfixPattern; /// ending pattern + DWORD unusedPattern; /// clean memory pattern + DWORD releasedPattern; /// deleted memory pattern + + /// Functions + + /// ctor. cleans logfile + SPMemoryManager(); + + /// create logfile + void BeginLog(); + /// preserve globals + void ClearGlobals(); + /// handle out-of-memory case + void OutOfMemory(); + + /// misc. functions + + /// fill allocated memory with specified pattern + void FillWithPattern(SPAllocUnit *u, DWORD pattern, + DWORD originalSize = 0); + /// internal: search unit in hash + SPAllocUnit *FindAllocUnit(void *reqAddress); + /// returnsname of src file + char *GetSourceName(char *sourceFile); + /// scan memory chunk for unused bytes (based on patterns) + DWORD CalculateUnused(SPAllocUnit *u); + /// validate integrity of memory allocation unit + BOOL ValidateUnit(SPAllocUnit *u); + /// helper function that logs into SP_MM_LOGFILENAME + void Log(char *message, ...); + /// dump unit + void DumpUnit(SPAllocUnit *u); +}; + +// Operators' redefinition +void *operator new(size_t reqSize); +void *operator new[](size_t reqSize); +void operator delete(void *reqAddress); +void operator delete[](void *reqAddress); + +#ifndef __PLACEMENT_NEW_INLINE +#define __PLACEMENT_NEW_INLINE +inline void *__cdecl operator new(size_t, void *_P) + {return (_P); } +#if _MSC_VER >= 1200 +inline void __cdecl operator delete(void *, void *) + {return; } +#endif +#endif + +// Macros +#define new (SPMemoryManager::GetHandle().SetOwner (__FILE__, __LINE__), false) ? NULL : new +#define delete (SPMemoryManager::GetHandle().SetOwner (__FILE__, __LINE__), false) ? NULL : delete +#define SPmalloc(sz) SPMemoryManager::GetHandle().Alloc(__FILE__, __LINE__, sz, SP_ALLOC_MALLOC) +#define SPcalloc(sz) SPMemoryManager::GetHandle().Alloc(__FILE__, __LINE__, sz, SP_ALLOC_CALLOC) +#define SPrealloc(ptr,sz) SPMemoryManager::GetHandle().Realloc(__FILE__, __LINE__, sz, ptr, SP_ALLOC_REALLOC) +#define SPfree(ptr) SPMemoryManager::GetHandle().Dealloc(__FILE__, __LINE__, SP_ALLOC_FREE, ptr) +#define SPstrdup(str) SPMemoryManager::GetHandle().Strdup(__FILE__, __LINE__, str) +#define SPLogMemStats() SPMemoryManager::GetHandle().LogStats(); + +#ifdef TEST_UCLIBC +#ifdef __cplusplus +extern "C" +{ +#endif +void *SPmalloc (size_t __size); +void *SPcalloc (size_t __nmemb); +void *SPrealloc (void *__ptr, size_t __size); +void SPfree (void *__ptr); +char *SPstrdup (const char *__s); +#ifdef __cplusplus +} +#endif +#endif + +#else // SP_MEMDEBUG + +#include + +// just use RTL-fuctions +#define SPmalloc(s) malloc(s) +#define SPcalloc(s) calloc(s, 1) +#define SPrealloc(d, s) realloc(d, s) +#define SPfree(d) free(d) +#define SPstrdup(p) strdup(p) +#define SPLogMemStats() + +#ifdef __cplusplus +#ifndef __PLACEMENT_NEW_INLINE +#define __PLACEMENT_NEW_INLINE +inline void * operator new(size_t, void *_P) + {return (_P); } +#ifdef _MSC_VER +#if _MSC_VER >= 1200 +inline void operator delete(void *, void *) + {return; } +#endif +#endif +#endif +#endif + + +#endif // SP_MEMDEBUG + +// Safe deallocation macros +#define SPSafeFree(ptr) {if (ptr) SPfree(ptr); (ptr) = NULL;} +#define SPSafeDelete(ptr) {if (ptr) delete ptr; (ptr) = NULL;} +#define SPSafeDeleteArray(ptr) {if (ptr) delete [] ptr; (ptr) = NULL;} +// Stack allocation macro +#define SPalloca(ptr) alloca(ptr) + +#endif // SP_MEMORY_H diff --git a/src/libsp/sp_misc.cpp b/src/libsp/sp_misc.cpp new file mode 100644 index 0000000..abf81bb --- /dev/null +++ b/src/libsp/sp_misc.cpp @@ -0,0 +1,95 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - misc. functions source file + * \file sp_misc.cpp + * \author bombur + * \version 0.2 + * \date 10.10.2006 4.05.2004 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include +#include +#include + +#include "sp_misc.h" + +BOOL module_apply(const char *modname) +{ + static const char *envps[] = { "HOME=/", "TERM=linux", NULL }; + static const char *args1[] = { "busybox", "insmod", NULL, NULL }; + static const char *args2[] = { "minimod", NULL, NULL }; + + args1[2] = modname; + args2[1] = modname; + + int pid = vfork(); + if (pid == 0) + { + if (errno = 0, !(execve ("/bin/busybox", (char * const *)args1, (char * const *)envps)+1)) + if (errno = 0, !(execve ("/bin/minimod.bin", (char * const *)args2, (char * const *)envps)+1)) + _exit(1); + _exit(0); // for safety + } + + int pstatus; + waitpid(pid, &pstatus, 0); + return WIFSIGNALED(pstatus) == 0; +} + +int exec_file(const char *binname, const char **pargs) +{ + static char *envps[] = { NULL }; + static char *args[] = { NULL, NULL }; + static char **ar = NULL; + if (pargs == NULL) + ar = args; + else + ar = (char **)pargs; + if (binname[0] != '/') + ar[0] = (char *)binname; + else + ar[0] = (char *)binname + 1; + + //msg("EXECVE %s\n", binname); + //for (int i = 0; ar[i]; i++) + // msg(" ARG%d = %s\n", i, ar[i]); + + int pid = vfork(); + if (pid == 0) + { + if (errno = 0, !(execve (binname, ar, envps)+1)) + _exit(1); + } + return pid; +} + +// patch for old UCLIBC +#ifdef __UCLIBC_HAS_THREADS__ +extern "C" +{ +pid_t __libc_fork(void) +{ + errno = ENOSYS; + return -1; +} +} +#endif diff --git a/src/libsp/sp_misc.h b/src/libsp/sp_misc.h new file mode 100644 index 0000000..ba3f658 --- /dev/null +++ b/src/libsp/sp_misc.h @@ -0,0 +1,200 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - misc. functions header file + * \file sp_misc.h + * \author bombur + * \version 0.1 + * \date 4.05.2004 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#ifndef SP_MISC_H +#define SP_MISC_H + +// some typedefs +#ifndef BOOL +typedef int BOOL; +#endif + +typedef unsigned char BYTE; +typedef unsigned short WORD; +typedef unsigned long DWORD; + +#ifdef WIN32 +typedef __int64 LONGLONG; +typedef unsigned __int64 ULONGLONG; +#define INT64(d) d##i64 +#define PRINTF_64d "%I64d" +#define SP_INLINE __inline +#define FORCEINLINE __forceinline +#pragma warning(disable: 4710) +#pragma warning(disable: 4996) +#else + +typedef long long LONGLONG; +typedef unsigned long long ULONGLONG; +#define INT64(d) d##LL +#define PRINTF_64d "%lld" +#ifndef __GNUC__ +#define SP_INLINE __inline +#elif __GNUC__ < 3 +#define SP_INLINE inline +#define FORCEINLINE inline +#else +//#define FORCEINLINE inline __attribute__ ((always_inline)) +#define SP_INLINE inline +#define FORCEINLINE inline +#endif +#endif + +#if defined(__GNUC__) +#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 95) +#define ATTRIBUTE_PACKED __attribute__ ((packed)) +#endif +#endif +#ifdef WIN32 +#define ATTRIBUTE_PACKED +#endif + +#ifndef O_BINARY +#ifdef _O_BINARY +#define O_BINARY _O_BINARY +#else +#define O_BINARY 0 +#endif +#endif + +#ifndef TRUE +#define TRUE 1 +#endif +#ifndef FALSE +#define FALSE 0 +#endif + +#ifndef NULL +#ifdef __cplusplus +#define NULL 0 +#else +#define NULL ((void *)0) +#endif +#endif + + +#ifndef MIN +#define MIN(a, b) (((a) <= (b)) ? (a) : (b)) +#endif +#ifndef MAX +#define MAX(a, b) (((a) >= (b)) ? (a) : (b)) +#endif + +#ifndef SP_MEMDEBUG_OFF +#ifdef __cplusplus +#ifdef WIN32 +#define SP_MEMDEBUG 1 +#endif +#endif +#endif + +#define SPERRMES(err) + +#include + +#include + +#ifndef __USE_LARGEFILE64 +#define __USE_LARGEFILE64 +#define __LARGE64_FILES +#endif +#include + +#define PAD_EVEN(x) (((x)+1) & ~1) + +#ifdef __cplusplus + +#ifdef _MSC_VER +#pragma warning (disable : 4505) +#pragma warning (disable : 4514) +#endif + +template inline T Max(T a, T b) +{ + return (a >= b) ? a : b; +} +template inline T Min(T a, T b) +{ + return (a <= b) ? a : b; +} +template inline T Abs(T a) +{ + return (a >= (T)0) ? a : (T)-a; +} +template inline void Swap(T& a, T& b) +{ + const T Temp = a; + a = b; + b = Temp; +} + +// 'F' -> 15 +inline int hex2char(int h) +{ + if (h >= '0' && h <= '9') + return h - '0'; + if (h >= 'A' && h <= 'F') + return h - 'A' + 10; + if (h >= 'a' && h <= 'f') + return h - 'a' + 10; + return 0; +} + +#define SafeGetWord(ptr) ((WORD)(((WORD)ptr[1] << 8) | ((WORD)ptr[0]))) +#define SafeGetDword(ptr) ((DWORD)(((DWORD)ptr[3] << 24) | ((DWORD)ptr[2] << 16) | ((DWORD)ptr[1] << 8) | ((DWORD)ptr[0]))) + +#include +#include +#include +#include +#include +#include + +#else +#define Abs abs +#endif + + +#ifdef __cplusplus +extern "C" { +#endif + +/// Inserts or deletes module to the linux kenrel. +/// Used for external firmware drivers. +BOOL module_apply(const char *modname); + +/// Calls binary file in a separate child process. +/// Used for external programs and players. +/// Returns child pid. +int exec_file(const char *binname, const char **args); + +/// Call GUI update +void gui_update(void); + +#ifdef __cplusplus +} +#endif + + +#endif // of SP_MISC_H diff --git a/src/libsp/sp_module.h b/src/libsp/sp_module.h new file mode 100644 index 0000000..9bfdd19 --- /dev/null +++ b/src/libsp/sp_module.h @@ -0,0 +1,60 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - modules support functions header file + * \file sp_module.h + * \author bombur + * \version 0.1 + * \date 10.12.2008 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#ifndef SP_SYSMODULE_H +#define SP_SYSMODULE_H + +#ifdef WIN32 +#define MODULE_FUNC(f) unsigned __int64 f +#else +#define MODULE_FUNC(f) unsigned long f __attribute__ ((__section__ (".text"))) +//#define MODULE_FUNC(f) unsigned long f +#endif + + + +#ifdef __cplusplus +extern "C" { +#endif + +/// Load module binary (process ID is returned). +int module_binary_load(char *fname, char *arg); + +/// Unload module binary from memory. +int module_binary_unload(int pid); + +/// Freeze module process +void module_wait(); + +/// Write 'from' address to the 'to' memory location +void module_copy_func(void *from, void *to); + +/// Set function location to NULL +void module_clear_func(void *func); + +#ifdef __cplusplus +} +#endif + +#endif // of SP_SYSMODULE_H diff --git a/src/libsp/sp_mpeg.cpp b/src/libsp/sp_mpeg.cpp new file mode 100644 index 0000000..876de1d --- /dev/null +++ b/src/libsp/sp_mpeg.cpp @@ -0,0 +1,2396 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - MPEG player source file. + * \file sp_mpeg.cpp + * \author bombur + * \version 0.1 + * \date 2.08.2004 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +///////////////////////////////////// +//#define MPEG_DEBUG +//#define MPEG_BUF_DEBUG +///////////////////////////////////// + +#ifndef MPEG_DEBUG +static void empty_msg(const char *,...) +{ +} +#define MSG empty_msg +#else +#define MSG msg +#endif + + +#include "settings.h" + +int MPEG_NUM_BUFS[3] = { 8, 0, 0 }; +int MPEG_BUF_SIZE[3] = { 32768, 0, 0 }; +int MPEG_PACKET_LENGTH = 2048; +int MPEG_NUM_PACKETS = 512; + +static MpegPacket *packets = NULL; +static int cur_num_packets = 0; + +static int cur_bufidx[3] = { 7, -1, -1 }; +static BYTE *bufbase[3][256]; +static BYTE bufidx[3][256/*MPEG_NUM_BUFS*/] = { { 0 }, { 0 }, { 0 } }; +static bool buf_created[3] = { false, false, false }; + +static MPEG_VIDEO_FORMAT mpeg_fmt = MPEG_1; + +static int mpeg_rate = 0, mpeg_rate_sum = 0, mpeg_rate_pos = 0, mpeg_nr = 0; +static int old_mpglayer = -1; +const int num_rates = 40; +static int mpeg_rates[num_rates]; + +static int mpeg_width = 0, mpeg_height = 0, mpeg_fps = 0, mpeg_aspect = 0; +static int mpeg_video_muxrate = 0; +static int mpeg_old_width = 0, mpeg_old_height = 0, mpeg_old_fps = 0, mpeg_old_aspect = 0; + +static int mpeg_zoom_hscale = 0, mpeg_zoom_vscale = 0, mpeg_zoom_offsetx = 0, mpeg_zoom_offsety = 0; + +static KHWL_AUDIO_FORMAT_TYPE mpeg_audio_format = eAudioFormat_UNKNOWN; +static int mpeg_audio_numchans = 0, mpeg_audio_bits = 0, mpeg_sample_rate = 0; +static int mpeg_resample_rate = 0; + +static LONGLONG mpeg_wait_last_pts = 0; +static int mpeg_wait_last_depth = 0; +static int mpeg_wait_count_still = 0, mpeg_wait_cnt = 0; +static bool mpeg_show_empty_stack_depth = true; + +int ac3_total_frame_size = 0, ac3_next_total_frame_size = 0; + +/// Use this to support 8-bit PCM +const int mpeg_min_audio_bits = 16; + +static bool mpeg_allow_mpa_parsing = false; +static bool mpeg_show_mpa_parsing = true; + +static bool mpeg_is_mpeg1 = false; +static bool mpeg_format_changed = false, mpeg_audio_format_set = false; + +static LONGLONG delta_pts = 0; +static LONGLONG last_video_pts = 0; +static LONGLONG mpeg_vop_rate = 24000, mpeg_vop_scale = 1000; + +static bool firstchunk = true; +static bool firstdisplayed = false; +static bool use_scr = true; + +static bool mpeg_audioonly = false; +static int mpeg_curaudstream = 0; +static int mpeg_curspustream = 0; +static int mpeg_numaudstream = 0; +static int mpeg_numspustream = 0; +static MPEG_SPEED_TYPE mpeg_speed = MPEG_SPEED_STOP; +static bool mpeg_needrestoreaudio = false; + +static int mpeg_srate = 0; +static KHWL_AUDIO_FORMAT_TYPE mpeg_aformat = eAudioFormat_UNKNOWN; +static int mpeg_nchannels = 2; +static int mpeg_nbitspersample = 24; + +static WORD ac3_frame_sizes[38][3]; + +static BYTE mpeg_seq_start_packet[256]; +static BYTE mpeg_gop_packet[8] = { 0, 0, 1, 0xb8, 0, 8, 0, 0x40 }; +static int mpeg_seq_start_packet_len = 0; +static bool mpeg_need_seq_start_packet = false; +static bool mpeg_was_seq_header = false, mpeg_was_picture = false; +static bool mpeg_parse_video = false; + +static const BYTE picture_start_code = 0x00; +static const BYTE pack_start_code = 0xBA; +static const BYTE system_header_start_code = 0xBB; +static const BYTE sequence_header_start_code = 0xB3; +static const BYTE packet_start_padding_code = 0xBE; +static const BYTE packet_start_pci_dsi_code = 0xBF; +static const BYTE packet_start_video_code1 = 0xE0; +static const BYTE packet_start_video_code2 = 0xE2; // 0xEF +static const BYTE packet_start_audio1_code1 = 0xC0; +static const BYTE packet_start_audio1_code2 = 0xCF; +static const BYTE packet_start_audio2_code1 = 0xD0; +static const BYTE packet_start_audio2_code2 = 0xDF; +static const BYTE packet_start_private1_code = 0xBD; + +static const int MPEG_WRAP_THRESHOLD = 120000; // value taken from xine + +static const WORD ac3_bitrates[19] = { 32, 40, 48, 56, 64, 80, 96, 112, 128, + 160, 192, 224, 256, 320, 384, 448, 512, 576, 640 }; +static const BYTE ac3_channels[8] = { 2, 1, 2, 3, 3, 4, 4, 5 }; +static const WORD ac3_freqs[3] = { 48000, 44100, 32000 }; + +// in frames per msec. +static const int mpeg_fps_table[16] = { 0, 23976, 24000, 25000, + 29970, 30000, 50000, 59940, 60000, + 15000, 5000, 10000, 12000, 15000, 0, 0, }; +#if 0 +static const char *mpeg_fps_strings[16] = { "none", "3/2 pulldown", "film", "PAL", "NTSC", + "drop-frame NTSC", "double-rate PAL", + "double-rate NTSC", "double-rate d.f. NTSC", + "Xing", "Economy-libmpeg3", "Economy-libmpeg3", + "Economy-libmpeg3", "Economy-libmpeg3", "Unknown", "Unknown", }; +#endif + +bool mpeg_novideo = FALSE; +int mpeg_scr = 0; +bool tell_about_encryption = true; + +int fip_audio_format = -1; + +///////////////////////////////////////////////////// +typedef struct MpegPacketFIFO +{ + MpegPacket *packet; + MpegPacketFIFO *next; +} MpegPacketFIFO; + +static const int feed_stack_num = 16; +static MpegPacketFIFO feed_stack[feed_stack_num]; // FIFO +static MpegPacketFIFO *feed_stack_in = feed_stack; // points to the last being added +static MpegPacketFIFO *feed_stack_out = NULL; // points to the first being removed + +///////////////////////////////////////////////////// + +BOOL mpeg_init(MPEG_VIDEO_FORMAT fmt, BOOL audio_only, BOOL mpa_parsing, BOOL need_seq_start_packet) +{ + mpeg_audioonly = audio_only == TRUE; + mpeg_allow_mpa_parsing = mpa_parsing == TRUE; + mpeg_show_mpa_parsing = true; + mpeg_need_seq_start_packet = need_seq_start_packet == TRUE; + + mpeg_setaudiostream(0); + mpeg_setspustream(0); + mpeg_numaudstream = 0; + mpeg_numspustream = 0; + + delta_pts = 0; + last_video_pts = 0; + + mpeg_vop_rate = 24000; + mpeg_vop_scale = 1000; + + khwl_stop(); + + mpeg_fmt = fmt; + + int var = fmt == MPEG_1 || fmt == MPEG_2 ? 0 : (fmt == MPEG_4 ? 1 : 0); //256; + khwl_setproperty(KHWL_DECODER_SET, edecVideoStd, sizeof(int), &var); // mpeg4 + + var = 0; //256; + khwl_setproperty(KHWL_VIDEO_SET, evVOBUReverseSpeed, sizeof(int), &var); // normal speed + + var = 0; //256; + khwl_setproperty(KHWL_VIDEO_SET, evSpeed, sizeof(int), &var); // normal speed + + firstchunk = true; + firstdisplayed = false; + use_scr = true; + + mpeg_reset(); + + mpeg_setpts(0); + + mpeg_rate = 0; + mpeg_rate_sum = 0; + mpeg_rate_pos = 0; + mpeg_nr = 0; + old_mpglayer = -1; + memset(mpeg_rates, 0, num_rates * sizeof(int)); + + mpeg_is_mpeg1 = false; + mpeg_format_changed = false; + mpeg_audio_format_set = false; + fip_audio_format = -1; + + mpeg_srate = 0; + mpeg_aformat = eAudioFormat_UNKNOWN; + mpeg_nchannels = 2; + mpeg_nbitspersample = 24; + + mpeg_needrestoreaudio = true; + mpeg_audio_format = eAudioFormat_UNKNOWN; + mpeg_sample_rate = 0; + mpeg_audio_numchans = 0; mpeg_audio_bits = 0; + mpeg_resample_rate = 0; + + if (!audio_only) + { + KHWL_WINDOW wnd; + khwl_getproperty(KHWL_VIDEO_SET, evMaxDisplayWindow, sizeof(wnd), &wnd); + //wnd.w = 720; + //wnd.h = 576; + //mpeg_setframesize(wnd.w, wnd.h); + MSG("MPEG: Default frame window: %dx%d\n", wnd.w, wnd.h); + + mpeg_width = 0; mpeg_height = 0; mpeg_fps = 0; mpeg_aspect = 0; + mpeg_video_muxrate = 0; + mpeg_old_width = 0; mpeg_old_height = 0; mpeg_old_fps = 0; mpeg_old_aspect = 0; + + mpeg_zoom_reset(); + } + + for(int i = 0; i < 38; i++) + { + int br = ac3_bitrates[i >> 1]; + ac3_frame_sizes[i][0] = (WORD)(2*br); + ac3_frame_sizes[i][1] = (WORD)((320*br / 147) + (i & 1)); + ac3_frame_sizes[i][2] = (WORD)(3*br); + } + + tell_about_encryption = true; + + for (int k = 0; k < 3; k++) + { + for (int j = 0; j < 256; j++) + bufbase[k][j] = NULL; + } + + mpeg_wait_last_pts = 0; + mpeg_wait_last_depth = 0; + mpeg_wait_count_still = 0; + mpeg_wait_cnt = 0; + mpeg_show_empty_stack_depth = true; + mpeg_seq_start_packet_len = 0; + mpeg_was_seq_header = false; + mpeg_was_picture = false; + + mpeg_parse_video = false; + + for (int j = 0; j < feed_stack_num; j++) + { + feed_stack[j].next = &feed_stack[(j + 1) % feed_stack_num]; + } + feed_stack_in = feed_stack; + feed_stack_out = NULL; + + return TRUE; +} + +BOOL mpeg_deinit() +{ + khwl_stop(); + + if (!mpeg_audioonly) + { + mpeg_zoom_reset(); + khwl_display_clear(); + } + + for (int k = 0; k < 3; k++) + { + if (buf_created[k]) + { + for (int j = 0; j < 256; j++) + SPSafeFree(bufbase[k][j]); + } + } + + if (packets != NULL) + { + SPfree(packets); + packets = NULL; + } + mpeg_speed = MPEG_SPEED_STOP; + return TRUE; +} + +BOOL mpeg_reset() +{ + mpeg_init_packets(); + + cur_bufidx[0] = cur_bufidx[1] = -1; + MPEG_NUM_BUFS[0] = MPEG_NUM_BUFS[1] = 0; + MPEG_BUF_SIZE[0] = MPEG_BUF_SIZE[1] = 0; + + for (int k = 0; k < 3; k++) + { + for (int j = 0; j < 256; j++) + bufbase[k][j] = NULL; + buf_created[k] = false; + } + + mpeg_feed_init(packets, MPEG_NUM_PACKETS); + + return TRUE; +} + +BOOL mpeg_setbuffer_array(MPEG_BUFFER which, BYTE **base, int num_bufs, int buf_size) +{ + MPEG_NUM_BUFS[which] = num_bufs; + MPEG_BUF_SIZE[which] = buf_size; + + for (int i = 0; i < num_bufs; i++) + { + bufbase[which][i] = base[i]; + } + cur_bufidx[which] = num_bufs - 1; + + khwl_blockirq(TRUE); + memset(bufidx[which], 0, MPEG_NUM_BUFS[which]); + khwl_blockirq(FALSE); + + return TRUE; +} + +BOOL mpeg_setbuffer(MPEG_BUFFER which, BYTE *base, int num_bufs, int buf_size) +{ + MPEG_NUM_BUFS[which] = num_bufs; + MPEG_BUF_SIZE[which] = buf_size; + + BYTE *b = base; + for (int i = 0; i < num_bufs; i++) + { + bufbase[which][i] = b; + b += MPEG_BUF_SIZE[which]; + } + cur_bufidx[which] = num_bufs - 1; + + khwl_blockirq(TRUE); + memset(bufidx[which], 0, MPEG_NUM_BUFS[which]); + khwl_blockirq(FALSE); + + return TRUE; +} + + +int mpeg_getbufsize(MPEG_BUFFER which) +{ + return MPEG_BUF_SIZE[which]; +} + +BYTE *mpeg_getbufbase() +{ + return BUF_BASE; +} + +//////////////////////////////////////////////////////// + +BOOL mpeg_setspeed(MPEG_SPEED_TYPE speed) +{ + int val = 256; + + // some currently forbidden combinations: +#if 0 + // 1) no fwd/rev in paused mode + if (((speed & MPEG_SPEED_FWD_MASK) == MPEG_SPEED_FWD_MASK + || (speed & MPEG_SPEED_REV_MASK) == MPEG_SPEED_REV_MASK) + && (mpeg_speed == MPEG_SPEED_PAUSE || mpeg_speed == MPEG_SPEED_STEP)) + return FALSE; +#endif + // 2) slow fwd only at play + if (((speed & MPEG_SPEED_SLOW_FWD_MASK) == MPEG_SPEED_SLOW_FWD_MASK) + && (mpeg_speed != MPEG_SPEED_NORMAL + && (speed & MPEG_SPEED_SLOW_FWD_MASK) != MPEG_SPEED_SLOW_FWD_MASK)) + return FALSE; + // 3) slow rev only at play + if (((speed & MPEG_SPEED_SLOW_REV_MASK) == MPEG_SPEED_SLOW_REV_MASK) + && (mpeg_speed != MPEG_SPEED_NORMAL + && (speed & MPEG_SPEED_SLOW_REV_MASK) != MPEG_SPEED_SLOW_REV_MASK)) + return FALSE; + // 4) pause/step only from play or slow + if ((speed == MPEG_SPEED_PAUSE || speed == MPEG_SPEED_STEP) + && (mpeg_speed != MPEG_SPEED_NORMAL && mpeg_speed != MPEG_SPEED_PAUSE + && ((mpeg_speed & MPEG_SPEED_SLOW_FWD_MASK) != MPEG_SPEED_SLOW_FWD_MASK) + && ((mpeg_speed & MPEG_SPEED_SLOW_REV_MASK) != MPEG_SPEED_SLOW_REV_MASK) + && mpeg_speed != MPEG_SPEED_STEP && !firstchunk)) + return FALSE; + + if (speed == MPEG_SPEED_NORMAL) + { + if (mpeg_speed == MPEG_SPEED_PAUSE || mpeg_speed == MPEG_SPEED_STEP) + { + mpeg_play(); + } + else if ((mpeg_speed & MPEG_SPEED_SLOW_FWD_MASK) == MPEG_SPEED_SLOW_FWD_MASK + || (mpeg_speed & MPEG_SPEED_SLOW_REV_MASK) == MPEG_SPEED_SLOW_REV_MASK) + { + khwl_pause(); + khwl_audioswitch(TRUE); + //mpeg_needrestoreaudio = true; + + val = 256; + khwl_setproperty(KHWL_VIDEO_SET, evSpeed, sizeof(int), &val); + khwl_play(KHWL_PLAY_MODE_NORMAL); + firstchunk = false; + firstdisplayed = true; + } + else // fast fwd/rev + { + ULONGLONG pts; + pts = mpeg_getpts(); + khwl_stop(); + val = 256; + khwl_setproperty(KHWL_VIDEO_SET, evSpeed, sizeof(int), &val); + khwl_setproperty(KHWL_VIDEO_SET, evVOBUReverseSpeed, sizeof(int), &val); + + //khwl_play(KHWL_PLAY_MODE_NORMAL); + //firstchunk = false; + + mpeg_start(); + mpeg_setpts(pts); + } + mpeg_speed = speed; + return TRUE; + } + + if (speed == MPEG_SPEED_PAUSE || speed == MPEG_SPEED_STEP) + { + if (mpeg_speed != MPEG_SPEED_NORMAL + && mpeg_speed != MPEG_SPEED_PAUSE && mpeg_speed != MPEG_SPEED_STEP) + { + ULONGLONG pts; + pts = mpeg_getpts(); + khwl_stop(); + val = 256; + khwl_setproperty(KHWL_VIDEO_SET, evSpeed, sizeof(int), &val); + khwl_setproperty(KHWL_VIDEO_SET, evVOBUReverseSpeed, sizeof(int), &val); + khwl_play(KHWL_PLAY_MODE_NORMAL); + mpeg_setpts(pts); + } + if (speed == MPEG_SPEED_PAUSE) + khwl_pause(); + else + { + khwl_play(KHWL_PLAY_MODE_STEP); + firstchunk = false; + firstdisplayed = true; + } + mpeg_speed = speed; + return TRUE; + } + + // TODO: add FIFO sync... +// if (firstchunk) +// return FALSE; + + + switch (speed) + { + case MPEG_SPEED_REV_4X: + case MPEG_SPEED_FWD_4X: val = 0x400; break; + case MPEG_SPEED_REV_8X: + case MPEG_SPEED_FWD_8X: val = 0x800; break; + case MPEG_SPEED_REV_16X: + case MPEG_SPEED_FWD_16X: val = 0x1000; break; + case MPEG_SPEED_REV_32X: + case MPEG_SPEED_FWD_32X: val = 0x2000; break; + case MPEG_SPEED_REV_48X: + case MPEG_SPEED_FWD_48X: val = 0x3000; break; + + case MPEG_SPEED_SLOW_REV_2X: + case MPEG_SPEED_SLOW_FWD_2X: val = 0x80; break; + case MPEG_SPEED_SLOW_REV_4X: + case MPEG_SPEED_SLOW_FWD_4X: val = 0x40; break; + case MPEG_SPEED_SLOW_REV_8X: + case MPEG_SPEED_SLOW_FWD_8X: val = 0x20; break; + default: + val = 0; + } + + int old_mpeg_speed = mpeg_speed; + mpeg_speed = speed; + + if ((speed & MPEG_SPEED_FWD_MASK) == MPEG_SPEED_FWD_MASK) + { + if ((speed & MPEG_SPEED_SLOW_FWD_MASK) == MPEG_SPEED_SLOW_FWD_MASK) + { + if ((old_mpeg_speed & MPEG_SPEED_SLOW_FWD_MASK) == MPEG_SPEED_SLOW_FWD_MASK + || old_mpeg_speed == MPEG_SPEED_NORMAL) + khwl_pause(); + else + khwl_stop(); + khwl_audioswitch(FALSE); + } + else + khwl_stop(); + khwl_setproperty(KHWL_VIDEO_SET, evSpeed, sizeof(int), &val); + mpeg_play(); + } + else if ((speed & MPEG_SPEED_REV_MASK) == MPEG_SPEED_REV_MASK) + { + if ((speed & MPEG_SPEED_SLOW_REV_MASK) == MPEG_SPEED_SLOW_REV_MASK) + { + if ((old_mpeg_speed & MPEG_SPEED_SLOW_REV_MASK) == MPEG_SPEED_SLOW_REV_MASK + || old_mpeg_speed == MPEG_SPEED_NORMAL) + khwl_pause(); + else + khwl_stop(); + khwl_audioswitch(FALSE); + } + else + khwl_stop(); + khwl_setproperty(KHWL_VIDEO_SET, evVOBUReverseSpeed, sizeof(int), &val); + mpeg_play(); + //mpeg_setpts(pts); + } + + return TRUE; +} + +BOOL mpeg_setframesize(int width, int height, bool noaspect) +{ + if (width <= 0 || height <= 0) + return FALSE; + + mpeg_width = width; + mpeg_height = height; + + khwl_display_clear(); + khwl_set_window_zoom(noaspect ? KHWL_ZOOMMODE_ASPECT : KHWL_ZOOMMODE_DVD); + khwl_set_window(-1, -1, width, height, 100, 100, 0, 0); + + return TRUE; +} + +BOOL mpeg_getframesize(int *width, int *height) +{ + *width = mpeg_width; + *height = mpeg_height; + return TRUE; +} + +int mpeg_get_fps() +{ + return mpeg_fps; +} + +int mpeg_getaspect() +{ + return mpeg_aspect; +} + +void mpeg_start() +{ + firstchunk = true; + MSG("MPEG: MPEG_START\n"); +} + +BOOL mpeg_is_playing() +{ + return !firstchunk; +} + +BOOL mpeg_is_displayed() +{ + return firstdisplayed; +} + +MPEG_VIDEO_FORMAT mpeg_get_video_format() +{ + // MPEG1/2 is detected. + if (mpeg_is_mpeg1) + return MPEG_1; + return mpeg_fmt; +} + +BOOL mpeg_feed(MPEG_FEED_TYPE type) +{ + static MpegPlayStruct *ps[] = { MPEG_VIDEO_STRUCT, MPEG_AUDIO_STRUCT, MPEG_SPU_STRUCT }; + + MpegPlayStruct *feed = ps[type]; + MpegPlayStruct *from = MPEG_PLAY_STRUCT; + MpegPacket *fromin = from->in; + if (fromin == NULL) + return FALSE; + + if (fromin->pts < 0) + fromin->pts = 0; + // save video pts for wrap detection and waiting... + if (fromin->type == 0) + { + if (fromin->flags == 2) + last_video_pts = fromin->pts; + else if (fromin->flags == 0x80) + last_video_pts = INT64(90000) * fromin->pts / mpeg_vop_rate; + + } + +#if 0 +#ifdef WIN32 + if (fromin->type == 0) { + static int siz = 0; + FILE *fp; + fp = fopen("out.m1v", "ab"); + fwrite(fromin->pData, fromin->size, 1, fp); + fclose(fp); + siz += fromin->size; + } + if (fromin->type == 1) { + FILE *fp; + fp = fopen("out.mpa", "ab"); + fwrite(fromin->pData, fromin->size, 1, fp); + fclose(fp); + } +#endif +#endif + + khwl_blockirq(TRUE); + + // advance to the next packet + from->in = from->in->next; + + // one packet will be removed from 'play' FIFO + from->num--; + from->in_cnt++; + // one packet will be added to 'feed' FIFO + feed->num++; + feed->out_cnt++; + + if (fromin->next == NULL) + from->out = NULL; + if (feed->out == NULL) // all data was processed + feed->in = fromin; + else // otherwise - add it to the queue + feed->out->next = fromin; + feed->out = fromin; + + fromin->next = NULL; + + khwl_blockirq(FALSE); + + return TRUE; +} + +BOOL mpeg_feed_push() +{ + MpegPlayStruct *from = MPEG_PLAY_STRUCT; + MpegPacket *fromin = from->in; + + if (fromin == NULL) + return FALSE; + + // check if stack is full + if (feed_stack_in == feed_stack_out) + return FALSE; + + if (feed_stack_out == NULL) + feed_stack_out = feed_stack_in; + + feed_stack_in->packet = fromin; + + feed_stack_in = feed_stack_in->next; + + if (fromin->pts < 0) + fromin->pts = 0; + + khwl_blockirq(TRUE); + + // advance to the next packet + from->in = from->in->next; + + // one packet will be removed from 'play' FIFO + from->num--; + from->in_cnt++; + + if (fromin->next == NULL) + from->out = NULL; + + fromin->next = NULL; + + khwl_blockirq(FALSE); + return TRUE; +} + +BOOL mpeg_feed_pop() +{ + static MpegPlayStruct *ps[] = { MPEG_VIDEO_STRUCT, MPEG_AUDIO_STRUCT, MPEG_SPU_STRUCT }; + + if (feed_stack_out == NULL) + return FALSE; + + MpegPacket *fromin = feed_stack_out->packet; + MpegPlayStruct *feed = ps[fromin->type]; + + feed_stack_out = (feed_stack_out->next != feed_stack_in) ? feed_stack_out->next : NULL; + + // save video pts for wrap detection and waiting... + if (fromin->type == 0) + { + if (fromin->flags == 2) + last_video_pts = fromin->pts; + else if (fromin->flags == 0x80) + last_video_pts = INT64(90000) * fromin->pts / mpeg_vop_rate; + } + + khwl_blockirq(TRUE); + + // one packet will be added to 'feed' FIFO + feed->num++; + feed->out_cnt++; + + if (feed->out == NULL) // all data was processed + feed->in = fromin; + else // otherwise - add it to the queue + feed->out->next = fromin; + feed->out = fromin; + + khwl_blockirq(FALSE); + return TRUE; +} + +BOOL mpeg_feed_isempty() +{ + return (feed_stack_out == NULL) ? TRUE : FALSE; +} + +BOOL mpeg_feed_reset() +{ + while (mpeg_feed_pop()) + ; + feed_stack_in = feed_stack; + feed_stack_out = NULL; + return TRUE; +} + +BOOL mpeg_correct_pts() +{ + MpegPacketFIFO *first = feed_stack_out; + if (first == NULL) + return FALSE; + LONGLONG *p_pts = &first->packet->pts, pts = *p_pts; + + MpegPacketFIFO *cur = first; + while (cur) + { + cur = cur->next; + if (cur == feed_stack_in) + break; + //cur->packet->pts = pts; + BYTE *b = cur->packet->pData; + if (b[0] == 0 && b[1] == 0 && b[2] == 1 && b[3] == 0xb6) + pts += mpeg_vop_scale; + } + *p_pts = pts; + return TRUE; +} + +BOOL mpeg_feed_init(MpegPacket *start, int num) +{ + MPEG_PLAY_STRUCT->in = start; + + khwl_blockirq(TRUE); + + for (int i = 0; i < num-1; i++) + start[i].next = &start[i+1]; + start[num-1].next = NULL; + + MPEG_PLAY_STRUCT->out = &start[num-1]; + + MPEG_PLAY_STRUCT->out_cnt = 0; + MPEG_PLAY_STRUCT->in_cnt = 0; + MPEG_PLAY_STRUCT->num = num; + MPEG_PLAY_STRUCT->reserved = 0; + + MPEG_AUDIO_STRUCT->out_cnt = 0; + MPEG_AUDIO_STRUCT->in_cnt = 0; + MPEG_AUDIO_STRUCT->num = 0; + MPEG_AUDIO_STRUCT->reserved = 0; + MPEG_AUDIO_STRUCT->in = NULL; + MPEG_AUDIO_STRUCT->out = NULL; + + MPEG_VIDEO_STRUCT->out_cnt = 0; + MPEG_VIDEO_STRUCT->in_cnt = 0; + MPEG_VIDEO_STRUCT->num = 0; + MPEG_VIDEO_STRUCT->reserved = 0; + MPEG_VIDEO_STRUCT->in = NULL; + MPEG_VIDEO_STRUCT->out = NULL; + + MPEG_SPU_STRUCT->out_cnt = 0; + MPEG_SPU_STRUCT->in_cnt = 0; + MPEG_SPU_STRUCT->num = 0; + MPEG_SPU_STRUCT->reserved = 0; + MPEG_SPU_STRUCT->in = NULL; + MPEG_SPU_STRUCT->out = NULL; + + khwl_blockirq(FALSE); + + return TRUE; +} + +void mpeg_stop() +{ + mpeg_feed_reset(); + khwl_stop(); + mpeg_speed = MPEG_SPEED_STOP; + +} + +void mpeg_play() +{ + if (mpeg_speed == MPEG_SPEED_NORMAL || + mpeg_speed == MPEG_SPEED_PAUSE || mpeg_speed == MPEG_SPEED_STEP || + mpeg_speed == MPEG_SPEED_STOP) + { + mpeg_speed = MPEG_SPEED_NORMAL; + int val = 256; + khwl_setproperty(KHWL_VIDEO_SET, evSpeed, sizeof(int), &val); + khwl_setproperty(KHWL_VIDEO_SET, evVOBUReverseSpeed, sizeof(int), &val); + khwl_play(KHWL_PLAY_MODE_NORMAL); + MSG("MPEG: KHWL_PLAY NORMAL\n"); + } + if ((mpeg_speed & MPEG_SPEED_FWD_MASK) == MPEG_SPEED_FWD_MASK) + { + if ((mpeg_speed & MPEG_SPEED_SLOW_FWD_MASK) == MPEG_SPEED_SLOW_FWD_MASK) + { + khwl_play(KHWL_PLAY_MODE_NORMAL); + MSG("MPEG: KHWL_PLAY NORMAL\n"); + } else + { + khwl_play(KHWL_PLAY_MODE_IFRAME); + MSG("MPEG: KHWL_PLAY IFRAME\n"); + } + } + else if ((mpeg_speed & MPEG_SPEED_REV_MASK) == MPEG_SPEED_REV_MASK) + { + if ((mpeg_speed & MPEG_SPEED_SLOW_REV_MASK) == MPEG_SPEED_SLOW_REV_MASK) + { + /// \TODO: It seems that mode doesn't work... + khwl_play(KHWL_PLAY_MODE_REV); + MSG("MPEG: KHWL_PLAY REV\n"); + } else + { + khwl_play(KHWL_PLAY_MODE_IFRAME_REV); + MSG("MPEG: KHWL_PLAY IFRAME REV\n"); + } + } + + firstchunk = false; + firstdisplayed = true; + + mpeg_wait_last_pts = 0; + mpeg_wait_last_depth = 0; + mpeg_wait_count_still = 0; + mpeg_wait_cnt = 0; + mpeg_show_empty_stack_depth = true; +} + +void mpeg_play_normal() +{ + int val = 256; + khwl_setproperty(KHWL_VIDEO_SET, evSpeed, sizeof(int), &val); + khwl_play(KHWL_PLAY_MODE_NORMAL); + MSG("MPEG: Play NORMAL\n"); + firstchunk = false; + firstdisplayed = true; +} + +BOOL mpeg_init_packets() +{ + if (packets != NULL && cur_num_packets != MPEG_NUM_PACKETS) + { + SPfree(packets); + packets = NULL; + } + if (packets == NULL) + { + packets = (MpegPacket *)SPmalloc(MPEG_NUM_PACKETS * sizeof(MpegPacket)); + if (packets == NULL) + return FALSE; + } + cur_num_packets = MPEG_NUM_PACKETS; + + khwl_blockirq(TRUE); + for (int i = 0; i < MPEG_NUM_PACKETS; i++) + { + memset(packets + i, 0, sizeof(MpegPacket)); + } + khwl_blockirq(FALSE); + return TRUE; +} + +int mpeg_feed_getstackdepth() +{ + if (mpeg_speed == MPEG_SPEED_STOP) + return 0; + if (firstchunk || mpeg_speed != MPEG_SPEED_NORMAL) + return MPEG_NUM_PACKETS; + return MPEG_NUM_PACKETS - MPEG_PLAY_STRUCT->num; +} + +int mpeg_wait(BOOL onetime) +{ + //khwl_stop(); + if (!onetime) + mpeg_play_normal(); // be sure we play +#if 0 + LONGLONG last_msecs = last_video_pts; + do + { + if (mpeg_feed_getstackdepth() == 0) + return 1; + if (!mpeg_audioonly) + { + LONGLONG msecs = (LONGLONG)mpeg_getpts(); + LONGLONG diff = last_msecs - msecs; + if (diff > 9000) // 100 ms + usleep(MIN((int)(1000 * diff / 90), 10000)); + else + return 1; + } + } while (!onetime); +#endif + + do + { + int depth = mpeg_feed_getstackdepth(); + if (depth == 0) + { + if (mpeg_show_empty_stack_depth) + { + msg("MPEG: * stack depth=0\n"); + mpeg_show_empty_stack_depth = false; + } + //return 1; + } + if (!mpeg_audioonly) + { + KHWL_TIME_TYPE displ; + displ.pts = 0; + displ.timeres = 90000; + khwl_getproperty(KHWL_TIME_SET, etimVideoFrameDisplayedTime, sizeof(displ), &displ); + if (Abs((LONGLONG)displ.pts - mpeg_wait_last_pts) < 9000) + { + //msg("*** dpts = %d\n", (int)displ.pts); + mpeg_wait_count_still++; + } + else + mpeg_wait_count_still = 0; + mpeg_wait_last_pts = displ.pts; + } else + { + if (depth == mpeg_wait_last_depth) + { + //msg("*** depth=%d\n", depth); + mpeg_wait_count_still++; + } + else + mpeg_wait_count_still = 0; + mpeg_wait_last_depth = depth; + } + usleep(100000); + mpeg_wait_cnt++; + if (mpeg_wait_count_still > 15) + { + mpeg_wait_last_pts = 0; + mpeg_wait_last_depth = 0; + mpeg_wait_count_still = 0; + mpeg_wait_cnt = 0; + //mpeg_show_empty_stack_depth = true; + return 1; + } + } while (!onetime); + + return 0; +} + +MpegPacket *mpeg_feed_getlast() +{ + MpegPlayStruct *from = MPEG_PLAY_STRUCT; + + // if playing and buffer is empty... +#ifndef WIN32 +/* + static int empty_cnt = 0; + if (from->num == MPEG_NUM_PACKETS && !mpeg_audioonly && mpeg_fmt == MPEG_2 && !firstchunk && mpeg_speed == MPEG_SPEED_NORMAL) + { + if (++empty_cnt > 0) + { + empty_cnt = 0; + khwl_pause(); + mpeg_start(); + msg("MPEG: Buffer is empty. Resync...\n"); + } + } else + empty_cnt = 0; +*/ +#endif + return from->in; +} + +BYTE *mpeg_getcurbuf(MPEG_BUFFER which) +{ + return bufbase[which][cur_bufidx[which]]; +} + +int mpeg_getpacketlength() +{ + return MPEG_PACKET_LENGTH; +} + +void mpeg_setpts(ULONGLONG estpts) +{ + KHWL_TIME_TYPE stc; + stc.pts = estpts; + stc.timeres = 90000; + khwl_setproperty(KHWL_TIME_SET, etimSystemTimeClock, sizeof(stc), &stc); + last_video_pts = estpts; +} + +ULONGLONG mpeg_getpts() +{ + KHWL_TIME_TYPE stc; + stc.pts = 0; + stc.timeres = 90000; + khwl_getproperty(KHWL_TIME_SET, etimSystemTimeClock, sizeof(stc), &stc); + return stc.pts; +} + +void mpeg_setaudiostream(int id) +{ + mpeg_curaudstream = id; +} + +int mpeg_getaudiostream() +{ + return mpeg_curaudstream; +} + +int mpeg_getaudiostreamsnum() +{ + return mpeg_numaudstream; +} + +void mpeg_setspustream(int id) +{ + mpeg_curspustream = id; +} + +int mpeg_getspustream() +{ + return mpeg_curspustream; +} + +int mpeg_getspustreamsnum() +{ + return mpeg_numspustream; +} + +void mpeg_resetstreams() +{ + mpeg_numaudstream = 0; + mpeg_numspustream = 0; +} + +int mpeg_find_free_blocks(const MPEG_BUFFER which) +{ + const int num_tries = 1; + static int buffull = false; + + int badcnt; + for (badcnt = 0; badcnt < num_tries; badcnt++) + { + cur_bufidx[which] = MPEG_NUM_BUFS[which] - 1; + + khwl_blockirq(TRUE); + while (cur_bufidx[which] >= 0) + { + if (bufidx[which][cur_bufidx[which]] == 0) + break; + cur_bufidx[which]--; + } + khwl_blockirq(FALSE); + + // when paused, just fill the buffer and wait... + // (full buffer is absolutely normal in this mode) + if (mpeg_speed == MPEG_SPEED_PAUSE || + mpeg_speed == MPEG_SPEED_STEP) + break; + + if (firstchunk) + { + if ((MPEG_NUM_BUFS[which] <= 16 && cur_bufidx[which] <= 3) + || (cur_bufidx[which] <= MPEG_NUM_BUFS[which]/2)) + { + mpeg_play(); + usleep(100); + } + } + + // wait for khwl to process packets + // (we need to free the first buffers) + if (cur_bufidx[which] >= 0) + { + buffull = false; + break; + } + + if (!buffull) + { +#ifdef MPEG_BUF_DEBUG + msg("MPEG: .\n"); +#endif + buffull = true; + } + + usleep(50); + //DWORD mask = 0xffffffff; + //khwl_happeningwait(&mask); + } + + if (cur_bufidx[which] < 0) // very bad, but still let's try again later... + { + cur_bufidx[which] = MPEG_NUM_BUFS[which]-1; // no matter which... + return 0; + } + + return 1; +} + +void mpeg_setbufidx(MPEG_BUFFER which, MpegPacket *packet) +{ + khwl_blockirq(TRUE); + bufidx[which][cur_bufidx[which]]++; + khwl_blockirq(FALSE); + + // set bufidx + if (packet != NULL) + packet->bufidx = bufidx[which] + cur_bufidx[which]; +} + +void mpeg_release_packet(MPEG_BUFFER which, MpegPacket *packet) +{ + khwl_blockirq(TRUE); + if (bufidx[which][cur_bufidx[which]] > 0) + bufidx[which][cur_bufidx[which]]--; + khwl_blockirq(FALSE); + + // set bufidx + if (packet != NULL) + packet->bufidx = NULL; +} + +//////////////////////////////////////////////////////////////////////////////////// +// parser + +START_CODE_TYPES mpeg_findpacket(BYTE * &buf, BYTE *base, int &startCounter) +{ + static const BYTE startCode[3] = { 0x00, 0x00, 0x01 }; + + // mini DFA + for (;;) + { + BYTE *bbuf = buf; + if (!mpeg_incParsingBufIndex(buf, base, 1)) + return START_CODE_END; + register BYTE b = *bbuf; + if (startCounter == sizeof(startCode)) + { + switch (b) + { + case pack_start_code: + return START_CODE_PACK; + case system_header_start_code: + return START_CODE_SYSTEM; + case packet_start_pci_dsi_code: + return START_CODE_PCI; + case packet_start_private1_code: + return START_CODE_PRIVATE1; + case packet_start_padding_code: + return START_CODE_PADDING; + case sequence_header_start_code: + buf -= 4; // leave this header to video parser + return START_CODE_MPEG_VIDEO_ELEMENTARY; + } + if (b >= packet_start_video_code1 && b <= packet_start_video_code2) + return START_CODE_MPEG_VIDEO; + if (b >= packet_start_audio1_code1 && b <= packet_start_audio1_code2) + return START_CODE_MPEG_AUDIO1; +#if 0 + if (b >= packet_start_audio2_code1 && b <= packet_start_audio2_code2) + return START_CODE_MPEG_AUDIO2; +#endif + startCounter = 0; + } else + { + if (b == startCode[startCounter]) + startCounter++; + else if (startCounter != 2 || b != 0) // remove this code? + startCounter = 0; + } + } + + // we won't get here + return START_CODE_UNKNOWN; +} + +inline ULONGLONG mpeg_extractTimingStampfromPESheader(BYTE * &buf) +{ + BYTE *tmp = buf; + return ((((tmp[4] >> 1) & 0x7F) + + ((tmp[3] << 7) & 0x7F80) + + ((tmp[2] << 14) & 0x3F8000) + + ((tmp[1] << 22) & 0x3FC00000) + + ((tmp[0] << 29) & INT64(0x1C0000000))) & INT64(0x1FFFFFFFF)); +} + +LONGLONG mpeg_parse_program_stream_pack_header(BYTE * &buf, BYTE * /*base*/) +{ + LONGLONG scr = 0; + mpeg_is_mpeg1 = (buf[0] & 0x40) == 0; + int mux_rate = 0; + if (mpeg_is_mpeg1) + { + scr = (buf[0] & 0x02) << 30; + scr |= (buf[1] & 0xFF) << 22; + scr |= (buf[2] & 0xFE) << 14; + scr |= (buf[3] & 0xFF) << 7; + scr |= (buf[4] & 0xFE) >> 1; + + mux_rate = (buf[5] & 0x7F) << 15; + mux_rate |= (buf[6] & 0x7F) << 7; + mux_rate |= (buf[7] & 0xFE) >> 1; + } else + { + scr = (buf[0] & 0x38) << 27; + scr |= (buf[0] & 0x03) << 28; + scr |= buf[1] << 20; + scr |= (buf[2] & 0xF8) << 12; + scr |= (buf[2] & 0x03) << 13; + scr |= buf[3] << 5; + scr |= (buf[4] & 0xF8) >> 3; +/* + mux_rate = (buf[6] & 0x7f) << 15; + mux_rate |= (buf[7] << 7); + mux_rate |= (buf[8] & 0xfe) >> 1; +*/ + mux_rate = (buf[6]) << 14; + mux_rate |= (buf[7]) << 6; + mux_rate |= (buf[8] & 0xfc) >> 2; + } + + mpeg_rate_sum += mux_rate; + if (mpeg_nr >= num_rates) + { + mpeg_rate_sum -= mpeg_rates[mpeg_rate_pos]; + mpeg_rate = mpeg_rate_sum / num_rates; // here just for optimisation + } + else + { + mpeg_nr++; + mpeg_rate = mpeg_rate_sum / mpeg_nr; + } + mpeg_rates[mpeg_rate_pos] = mux_rate; + + mpeg_rate_pos = (mpeg_rate_pos + 1) % num_rates; + + buf += 8; + return scr; +} + +int mpeg_getrate(BOOL now) +{ + if (now) + { + // at least get video mux.rate + if (mpeg_rate == 0) + return mpeg_video_muxrate * 50; + } + else if (mpeg_nr < num_rates) + return 0; + return mpeg_rate * 50; +} + +////////////////////////////////////////////////////////// + +int mpeg_setaudioparams(MpegAudioPacketInfo *paudio) +{ + if (paudio == NULL) + return -1; + + KHWL_AUDIO_FORMAT_TYPE audioformat = paudio->type; + int numberofchannels, numberofbitspersample, samplerate; + + if (audioformat == eAudioFormat_PCM) + { + samplerate = paudio->samplerate; + numberofchannels = paudio->numberofchannels; + numberofbitspersample = paudio->numberofbitspersample; + } + else if (audioformat == eAudioFormat_MPEG1) + { + samplerate = paudio->samplerate; + numberofchannels = paudio->numberofchannels; + numberofbitspersample = 16; + } + else + { + samplerate = 48000; + numberofchannels = mpeg_nchannels; + numberofbitspersample = mpeg_nbitspersample; + } + + if (samplerate != mpeg_srate || audioformat != mpeg_aformat || + numberofchannels != mpeg_nchannels || + numberofbitspersample != mpeg_nbitspersample || mpeg_needrestoreaudio) + { + if (paudio->fromstream) + mpeg_needrestoreaudio = false; + msg("MPEG: setaudio(format=%d, rate=%d, nc=%d, nbs=%d)\n", audioformat, + samplerate, numberofchannels, numberofbitspersample); + + khwl_audioswitch(FALSE); + mpeg_audio_format = audioformat; + mpeg_sample_rate = samplerate; + mpeg_audio_numchans = numberofchannels; + mpeg_audio_bits = numberofbitspersample; + mpeg_resample_rate = 0; + int newsrate = mpeg_set_sample_rate(samplerate); + if (newsrate != samplerate || mpeg_audio_bits < mpeg_min_audio_bits) + { + if (audioformat == eAudioFormat_PCM && newsrate > 0) + { + mpeg_resample_rate = newsrate; + numberofbitspersample = mpeg_min_audio_bits; + msg("MPEG: Audio PCM resampling from %d/%d to %d/%d.\n", + samplerate, mpeg_audio_bits, + mpeg_resample_rate, mpeg_min_audio_bits); + } else + { + msg_error("MPEG: Samplerate %d not supported by decoder.\n", samplerate); + return -1; + } + } + int var; + if (audioformat == eAudioFormat_PCM) + var = eAudioDigitalOutput_Pcm; + else if (audioformat == eAudioFormat_DTS) + var = eAudioDigitalOutput_Compressed; + else + var = settings_get(SETTING_AUDIOOUT) == 1 ? eAudioDigitalOutput_Compressed : eAudioDigitalOutput_Pcm; + khwl_setproperty(KHWL_AUDIO_SET, eAudioDigitalOutput, sizeof(DWORD), &var); + if (audioformat == eAudioFormat_PCM) + { + khwl_setproperty(KHWL_AUDIO_SET, eAudioNumberOfChannels, sizeof(DWORD), &numberofchannels); + khwl_setproperty(KHWL_AUDIO_SET, eAudioNumberOfBitsPerSample, sizeof(DWORD), &numberofbitspersample); + } + khwl_setproperty(KHWL_AUDIO_SET, eAudioFormat, sizeof(KHWL_AUDIO_FORMAT_TYPE), &audioformat); + khwl_audioswitch(TRUE); + + mpeg_srate = samplerate; + mpeg_aformat = audioformat; + mpeg_nchannels = numberofchannels; + mpeg_nbitspersample = numberofbitspersample; + + mpeg_format_changed = true; + mpeg_audio_format_set = true; + } + + if (paudio->fromstream && audioformat != fip_audio_format) + { + if (audioformat == eAudioFormat_AC3) + { + fip_write_special(FIP_SPECIAL_DOLBY, 1); + fip_write_special(FIP_SPECIAL_DTS, 0); + } + else if (audioformat == eAudioFormat_DTS) + { + fip_write_special(FIP_SPECIAL_DTS, 1); + fip_write_special(FIP_SPECIAL_DOLBY, 0); + } + else + { + fip_write_special(FIP_SPECIAL_DOLBY, 0); + fip_write_special(FIP_SPECIAL_DTS, 0); + } + fip_audio_format = audioformat; + } + + //is_audio_set = TRUE; + return 0; +} + +MpegAudioPacketInfo mpeg_getaudioparams() +{ + MpegAudioPacketInfo apinfo; + apinfo.type = mpeg_audio_format; + apinfo.samplerate = mpeg_sample_rate; + apinfo.numberofchannels = mpeg_audio_numchans; + apinfo.numberofbitspersample = mpeg_audio_bits; + apinfo.fromstream = TRUE; + return apinfo; +} + +int mpeg_set_sample_rate(int samplerate) +{ + int i, *r = khwl_get_samplerates(); + for (i = 0; r[i] > 0; i++) + { + if (r[i] == samplerate) + { + goto found; + } + } + // find 2x upsample rates + for (i = 0; r[i] > 0; i++) + { + if (r[i] == samplerate * 2) + { + samplerate *= 2; + goto found; + } + } + // find 1/2 downsample rates + for (i = 0; r[i] > 0; i++) + { + if (r[i] == samplerate / 2) + { + samplerate /= 2; + goto found; + } + } + // find closest downsample freq. + for (i = 1; r[i] > 0; i++) + { + if (r[i] > samplerate) + { + samplerate = r[i - 1]; + goto found; + } + } +/* + // find closest upsample freq. + for (--i; i > 0; i--) + { + if (r[i - 1] < samplerate) + { + samplerate = r[i]; + goto found; + } + } +*/ + return -1; + +found: + msg("MPEG: * Samplerate = %d.\n", samplerate); + khwl_setproperty(KHWL_AUDIO_SET, eAudioSampleRate, sizeof(DWORD), &samplerate); + return samplerate; +} + +void mpeg_PCM_to_LPCM(BYTE *buf, int len) +{ + if (mpeg_audio_bits == 16) + { + register int i; + if (((DWORD)buf & 3) == 0) // DWORD-aligned version + { + register DWORD *dw = (DWORD *)buf; + int ds = len / 4; + for (i = ds - 1; i >= 0; i--, dw++) + { + *dw = ((*dw & 0x00ff00ff) << 8) | ((*dw & 0xff00ff00) >> 8); + } + + if (len & 3) + { + register WORD *w = (WORD *)dw; + *w = (WORD)(((*w & 0xff) << 8) | (*w >> 8)); + } + } + else if (((DWORD)buf & 1) == 0) // WORD-aligned version + { + register WORD *w = (WORD *)buf; + int ws = len / 2; + for (i = ws - 1; i >= 0; i--, w++) + { + *w = (WORD)(((*w & 0xff) << 8) | (*w >> 8)); + } + } + else + { + register BYTE tmp; + for (i = len - 1; i >= 0; i -= 2, buf += 2) + { + tmp = buf[0]; + buf[0] = buf[1]; + buf[1] = tmp; + } + } + } +} + +BOOL mpeg_is_resample_needed() +{ + return mpeg_resample_rate != 0; +} + +int mpeg_PCM_resample_to_LPCM(BYTE *buf, int len, BYTE *out, int *olen) +{ + if (mpeg_resample_rate == 0 || mpeg_sample_rate == 0) + return -1; + int inlen, outlen; + // 8->16 bits + if (mpeg_audio_bits == 8 && mpeg_audio_bits < mpeg_min_audio_bits) + { + outlen = *olen / 2; + inlen = outlen * mpeg_sample_rate / mpeg_resample_rate; + if (len < inlen) + { + inlen = len; + outlen = len * mpeg_resample_rate / mpeg_sample_rate; + } + + const int FRAC_SHIFT = 10, FRAC_MUL = 1 << FRAC_SHIFT; + int ind = mpeg_sample_rate > mpeg_resample_rate ? + mpeg_sample_rate * FRAC_MUL / mpeg_resample_rate : FRAC_MUL; + int outd = mpeg_resample_rate > mpeg_sample_rate ? + mpeg_resample_rate * FRAC_MUL / mpeg_sample_rate : FRAC_MUL; + register int inpos = 0, outpos = 0, oldoutpos = 0; + for (int i = mpeg_resample_rate > mpeg_sample_rate ? inlen - 1 : outlen - 1; i >= 0; i--) + { + // input contains non-aligned WORDs + register BYTE *b = buf + (inpos >> FRAC_SHIFT); + register WORD *o = (WORD *)out + (outpos >> FRAC_SHIFT); + inpos += ind; + outpos += outd; + WORD w = (WORD)(*b - 0x80); // little-endian -> big-endian + for (int j = (outpos - oldoutpos) >> FRAC_SHIFT; j > 0; j--) + *o++ = w; + oldoutpos = outpos; + } + + *olen = outlen * 2; + } + else if (mpeg_audio_bits == 8) + { + outlen = *olen; + inlen = outlen * mpeg_sample_rate / mpeg_resample_rate; + if (len < inlen) + { + inlen = len; + outlen = len * mpeg_resample_rate / mpeg_sample_rate; + } + + const int FRAC_SHIFT = 10, FRAC_MUL = 1 << FRAC_SHIFT; + int ind = mpeg_sample_rate > mpeg_resample_rate ? + mpeg_sample_rate * FRAC_MUL / mpeg_resample_rate : FRAC_MUL; + int outd = mpeg_resample_rate > mpeg_sample_rate ? + mpeg_resample_rate * FRAC_MUL / mpeg_sample_rate : FRAC_MUL; + register int inpos = 0, outpos = 0, oldoutpos = 0; + for (int i = mpeg_resample_rate > mpeg_sample_rate ? inlen - 1 : outlen - 1; i >= 0; i--) + { + register BYTE *b = buf + (inpos >> FRAC_SHIFT); + register BYTE *o = out + (outpos >> FRAC_SHIFT); + inpos += ind; + outpos += outd; + for (int j = (outpos - oldoutpos) >> FRAC_SHIFT; j > 0; j--) + *o++ = *b; + oldoutpos = outpos; + } + + *olen = outlen; + } + else if (mpeg_audio_bits == 16) + { + outlen = *olen / 2; + inlen = outlen * mpeg_sample_rate / mpeg_resample_rate; + len /= 2; + if (len < inlen) + { + inlen = len; + outlen = len * mpeg_resample_rate / mpeg_sample_rate; + } + + const int FRAC_SHIFT = 10, FRAC_MUL = 1 << FRAC_SHIFT; + int ind = mpeg_sample_rate > mpeg_resample_rate ? + mpeg_sample_rate * FRAC_MUL / mpeg_resample_rate : FRAC_MUL; + int outd = mpeg_resample_rate > mpeg_sample_rate ? + mpeg_resample_rate * FRAC_MUL / mpeg_sample_rate : FRAC_MUL; + register int inpos = 0, outpos = 0, oldoutpos = 0; + for (int i = mpeg_resample_rate > mpeg_sample_rate ? inlen - 1 : outlen - 1; i >= 0; i--) + { + // input contains non-aligned WORDs + register BYTE *b = buf + (inpos >> FRAC_SHIFT) * 2; + register WORD *o = (WORD *)out + (outpos >> FRAC_SHIFT); + inpos += ind; + outpos += outd; + WORD w = (WORD)((*b << 8) | b[1]); // little-endian -> big-endian + for (int j = (outpos - oldoutpos) >> FRAC_SHIFT; j > 0; j--) + *o++ = w; + oldoutpos = outpos; + } + + *olen = outlen * 2; + inlen *= 2; + } + else + return -1; + + return inlen; +} + +WORD mpeg_calc_crc16(BYTE *buf, DWORD bitsize) +{ + DWORD n; + WORD tmpchar, crcmask, tmpi; + crcmask = tmpchar = 0; + WORD crc = 0xffff; // start with inverted value of 0 + + // start with byte 2 of header + for (n = 16; n < bitsize; n++) + { + if (n < 32 || n >= 48) // skip the 2 bytes of the crc itself + { + if (n%8 == 0) + { + crcmask = 1 << 8; + tmpchar = buf[n/8]; + } + crcmask >>= 1; + tmpi = (WORD)(crc & 0x8000); + crc <<= 1; + + if (!tmpi ^ !(tmpchar & crcmask)) + crc ^= 0x8005; + } + } + crc &= 0xffff; // invert the result + return crc; +} + +int mpeg_extractpacket(BYTE * &buf, BYTE *base, MpegPacket *packet, START_CODE_TYPES type, BOOL parse_video) +{ + BYTE *startbuf = buf; + if (packet != NULL) + { + WORD pes_packet_length = 0; + BYTE pes_header_data_length = 0; + BYTE pes_header_data_length0 = 3; + BYTE pts_dts_flags = 0; + BYTE pes_extension_flag = 0; + BOOL encrypted = FALSE; + BOOL need_skip = FALSE; + + if (type == START_CODE_MPEG_VIDEO_ELEMENTARY) + { + pes_packet_length = (WORD)(MPEG_PACKET_LENGTH + 3); + } else + { + pes_packet_length = (WORD)(((buf[0] & 0xFF) << 8) + (buf[1] & 0xFF)); + + if (mpeg_is_mpeg1) + { + if (!mpeg_incParsingBufIndex(buf, base, 2)) + return -1; + //pes_header_data_length = 1; + pes_header_data_length = 1; + pes_header_data_length0 = 0; + if (mpeg_eofBuf(buf, base)) + return -1; + // stuffing + while ((*buf & 0x80) == 0x80) + { + if (!mpeg_incParsingBufIndex(buf, base, 1)) + return -1; + pes_header_data_length0++; + } + if (mpeg_eofBuf(buf, base)) + return -1; + // STD_buffer_scale, STD_buffer_size + if ((*buf & 0xc0) == 0x40) + { + if (!mpeg_incParsingBufIndex(buf, base, 2)) + return -1; + pes_header_data_length0 += 2; + } + if (mpeg_eofBuf(buf, base)) + return -1; + if ((*buf & 0xe0) == 0x20) + { + pes_header_data_length += 4; + if (*buf & 0x10) + pes_header_data_length += 5; + pts_dts_flags = 2; + } + // mpeg-2 pes + else if ((*buf & 0xc0) == 0x80) + { + pts_dts_flags = (BYTE)(((*buf) >> 6) & 0x3); + pes_extension_flag = (BYTE)((*buf++) & 0x1); + pes_header_data_length = (BYTE)(pes_header_data_length + (*buf++)); + } + + // don't use SCR for MPEG-1 (or VCD) stream. + // \TODO: Is it correct? + use_scr = false; + + } else + { + encrypted = (buf[2] & 0x30) != 0; + if (!mpeg_incParsingBufIndex(buf, base, 3)) + return -1; + pes_header_data_length0 = 3; + pts_dts_flags = (BYTE)(((*buf) >> 6) & 0x3); + pes_extension_flag = (BYTE)((*buf++) & 0x1); + pes_header_data_length = *buf++; + if (mpeg_eofBuf(buf, base)) + return -1; + } + switch(pts_dts_flags) + { + case 0x3: + // no dts info for audio packets, but pts is good + case 0x1: + // ??? + case 0x2: + // base timestamp is set by caller + packet->pts += mpeg_extractTimingStampfromPESheader(buf); + packet->flags = 2; + break; + default: + // no timing info + packet->pts = 0; + break; + } + buf += pes_header_data_length; + if (mpeg_eofBuf(buf, base)) + return -1; + } + + if (!use_scr) + packet->scr = 0; + + int pack_length = 0; + MpegAudioPacketInfo plaudioinfo; + bool forbid_setaudio = false; + + switch (type) + { + case START_CODE_MPEG_VIDEO: + case START_CODE_MPEG_VIDEO_ELEMENTARY: + { + mpeg_was_seq_header = false; + mpeg_was_picture = false; + packet->type = 0; + // find frame width & height + BYTE *b = buf; + // header + if (b[0] == 0 && b[1] == 0 && b[2] == 1 && b[3] == 0xb3) + { + int wh = (b[4] << 16) | (b[5] << 8) | b[6]; + mpeg_width = ((wh >> 12) + 15) & ~15; + mpeg_height = ((wh & 0xfff) + 15) & ~15; + mpeg_fps = mpeg_fps_table[b[7] & 0xf]; + + mpeg_aspect = (b[7] >> 4) & 0xf; /* 1 = 1:1, 2 = 4:3, 3 = 16:9 */ + + mpeg_was_seq_header = true; + + if (mpeg_is_mpeg1) + mpeg_video_muxrate = (b[8] << 10) | (b[9] << 2) | (b[10] >> 6); + else + mpeg_video_muxrate = (b[8] << 10) | (b[9] << 2) | (b[10] >> 6); + if (mpeg_video_muxrate == 0x3FFFF) + mpeg_video_muxrate = 0; + + if (mpeg_width != mpeg_old_width || mpeg_height != mpeg_old_height || mpeg_fps != mpeg_old_fps + || mpeg_aspect != mpeg_old_aspect) + { + //mpeg_setframesize(mpeg_width, mpeg_height); + mpeg_old_width = mpeg_width; + mpeg_old_height = mpeg_height; + mpeg_old_fps = mpeg_fps; + mpeg_old_aspect = mpeg_aspect; + } + + b += 4; + b += 7; + if (mpeg_eofBuf(b, base)) + { + buf = b; + return -1; + } + if (b[0] & 2) + b += 64; + if (b[0] & 1) + b += 64; + b += 1; + if (mpeg_eofBuf(b, base)) + { + buf = b; + return -1; + } + if (mpeg_need_seq_start_packet /*&& b - buf >= 12*/) + { + /* + mpeg_seq_start_packet_len = b - buf; + memcpy(mpeg_seq_start_packet, buf, mpeg_seq_start_packet_len); + mpeg_need_seq_start_packet = false; + */ + + if (mpeg_is_mpeg1) + { + mpeg_seq_start_packet_len = 12; + memcpy(mpeg_seq_start_packet, buf, mpeg_seq_start_packet_len); + mpeg_seq_start_packet[8] |= 3; + mpeg_seq_start_packet[9] |= 0xff; + mpeg_seq_start_packet[10] |= 0xff; + mpeg_seq_start_packet[11] &= ~3; + } else + mpeg_seq_start_packet_len = 0; + + memcpy(mpeg_seq_start_packet + mpeg_seq_start_packet_len, mpeg_gop_packet, sizeof(mpeg_gop_packet)); + mpeg_seq_start_packet_len += sizeof(mpeg_gop_packet); + mpeg_need_seq_start_packet = false; + } + + } +#if 1 + else if (parse_video && !mpeg_is_mpeg1) + { + int psize = pes_packet_length - pes_header_data_length - pes_header_data_length0 - pack_length; + for (BYTE *endb = buf + psize; b < endb; b++) + { + if (b[0] == 0 && b[1] == 0 && b[2] == 1 && b[3] == 0) + { + // test picture type + int pic_type = (b[5] >> 3) & 3; + if (pic_type == 1) + { + mpeg_was_picture = true; + pes_packet_length -= (WORD)(b - buf); + buf = b; + break; + } + } + } + } +#endif +#if 0 + while (mpeg_parse_video) + { + // extension + if (b[0] == 0 && b[1] == 0 && b[2] == 1 && b[3] == 0xb5) + { + b += 4; + switch (b[0] & 0xf0) + { + case 0x10: /* sequence extension */ + b += 6; break; + case 0x20: /* sequence display extension */ + b += 1 + 1 + 1 + 1 + 4; break; + case 0x30: /* quant matrix extension */ + { + if (b[0] & 8) + b += 64; + if (b[0] & 4) + b += 64; + break; + } + case 0x70: /* picture display extension for Pan & Scan */ + b += 4; break; + + case 0x80: /* picture coding extension */ + b += 5; break; + } + continue; + } + // GOP + if (b[0] == 0 && b[1] == 0 && b[2] == 1 && b[3] == 0xb8) + { + b += 8; + bool gop_start = true; + } + break; + } +#endif + } + break; + case START_CODE_MPEG_AUDIO1: // MPEG-1 L1, L2, L2.5 + case START_CODE_MPEG_AUDIO2: + { + // this is not good, but it works... + int substreamid = *(startbuf - 1); + packet->type = 1; + mpeg_numaudstream = MAX(mpeg_numaudstream, (substreamid & 0x07)+1); + + if ((substreamid & 0x1f) != mpeg_curaudstream) // skip + { + need_skip = TRUE; + break; + } + + packet->nframeheaders = 0xffff; + packet->firstaccessunitpointer = 0; + forbid_setaudio = true; + +#if 0 + if (type == START_CODE_MPEG_AUDIO2) + { + WORD nfh = ((buf[0] & 0xFF) << 8) + (buf[1] & 0xFF); + packet->nframeheaders = 0xffff; + packet->firstaccessunitpointer = 0; + pack_length = nfh + 6; // ??? + buf += nfh + 6; + } else + pack_length = 1; +#endif + + DWORD header = (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3]; + if (mpeg_allow_mpa_parsing && (header & 0xffe00000) == 0xffe00000) + { + plaudioinfo.samplerate = 48000; + plaudioinfo.numberofchannels = 2; + + DWORD mpglayer = 4 - ((header >> 17) & 0x3); + bool has_CRC = ((header >> 16) & 0x1) == 0; + DWORD sample_rate_index = (header >> 10) & 3; + DWORD bitrate_index = (header >> 12) & 0xf; + DWORD emphasis = header & 3; + DWORD mode = (header >> 6) & 3; + DWORD modeext = (header >> 4) & 3; + //msg("MPEG: *FFE* layer=%d, old=%d\n", mpglayer, old_mpglayer); + if (has_CRC && mpglayer == 1) + { + int bound = mode == 1 ? 4 + modeext * 4 : 32; + int pbits = 4* ((mode == 3 ? 1 : 2) * bound + (32 - bound)); + pbits += 4*8 + 16; + //int crcsize = (pbits + 7) / 8; + WORD wCRC16 = mpeg_calc_crc16(buf, pbits); + // read out crc from frame (it follows frame header) + WORD fileCRC = (WORD)((buf[4] << 8) | buf[5]); + has_CRC = wCRC16 != fileCRC; + } + else if (!mpeg_audio_format_set) // give it a chance + has_CRC = false; + + // layer change currently not supported in MPEG-1/2 container... + if ((int)mpglayer == old_mpglayer || old_mpglayer == -1) + { + // mpeg1l3 currently not supported in MPEG-1/2 container... + if (!has_CRC && sample_rate_index < 3 && mpglayer < 3 && bitrate_index < 15 && emphasis != 2) + { + DWORD version_idx = 0; + if (mpglayer == 2) // mpeg1 L2 extensions... + { + if (((header >> 19) & 0x1) == 0) // lsf + version_idx++; + if (((header >> 20) & 0x1) == 0) // mpeg25 + version_idx++; + } + + + const DWORD mpa_freq_tab[] = { 44100, 48000, 32000 }; + plaudioinfo.samplerate = mpa_freq_tab[sample_rate_index] >> version_idx/*(lsf + mpeg25)*/; + plaudioinfo.numberofchannels = (mode == 3/*mono*/) ? 1 : 2; + old_mpglayer = mpglayer; + forbid_setaudio = false; + + if (mpeg_sample_rate != plaudioinfo.samplerate || + mpeg_audio_numchans != plaudioinfo.numberofchannels || + mpeg_show_mpa_parsing) + { + msg("MPEG: * Audio stream format detected: %d kHz %s\n", plaudioinfo.samplerate, + plaudioinfo.numberofchannels == 1 ? "mono" : "stereo"); + mpeg_show_mpa_parsing = false; + } + } + } + } else + if (mpeg_needrestoreaudio) + { + plaudioinfo.samplerate = 48000; + plaudioinfo.numberofchannels = 2; + forbid_setaudio = false; + } + + plaudioinfo.type = eAudioFormat_MPEG1; + break; + } + case START_CODE_PRIVATE1: // DVD private + { + int substreamid = *buf++; + pack_length = 1; + if ((substreamid & 0xE0) == 0x20) // DVD SPU substream + { + int spu_id = (substreamid & 0x1f); + mpeg_numspustream = MAX(mpeg_numspustream, spu_id+1); + packet->type = 2; + + if (spu_id != mpeg_curspustream && mpeg_curspustream != -2) + { + need_skip = TRUE; + break; + } + } + else if (substreamid == 0x70 && (packet->nframeheaders & 0xFC) == 0x00) // SVCD OGT spu... + { + int spu_id = *buf++; + mpeg_numspustream = MAX(mpeg_numspustream, spu_id+1); + packet->type = 2; + + if (spu_id != mpeg_curspustream) + { + need_skip = TRUE; + break; + } + } + else if ((substreamid & 0xFC) == 0x00) // CVD spu... + { + int spu_id = (substreamid & 0x03); + mpeg_numspustream = MAX(mpeg_numspustream, spu_id+1); + packet->type = 2; + + if (spu_id != mpeg_curspustream) + { + need_skip = TRUE; + break; + } + } + else if ((substreamid & 0xF0) == 0x80) // AC3/DTS + { + BYTE nframeheaders = *buf; + packet->nframeheaders = 0; + packet->firstaccessunitpointer = (WORD)(((buf[1] & 0xFF) << 8) + (buf[2] & 0xFF)); + buf += 3; + pack_length += 3; + + mpeg_numaudstream = MAX(mpeg_numaudstream, (substreamid & 0x07)+1); + packet->type = 1; + + if ((substreamid & 0x07) != mpeg_curaudstream) // skip + { + need_skip = TRUE; + break; + } + + if ((substreamid & 0xF8) == 0x88) + { + plaudioinfo.type = eAudioFormat_DTS; + packet->nframeheaders = nframeheaders; + } + else + plaudioinfo.type = eAudioFormat_AC3; + } + else if ((substreamid & 0xf0) == 0xa0) // MPEG-2 lpcm + { + int track = substreamid & 0x07; + + mpeg_numaudstream = MAX(mpeg_numaudstream, track+1); + packet->type = 1; + + if (track != mpeg_curaudstream) // skip + { + buf += 6; + pack_length += 6; + need_skip = TRUE; + break; + } + + packet->nframeheaders = 0xffff;//buf[0]; + packet->firstaccessunitpointer = (WORD)(((buf[1] & 0xFF) << 8) + (buf[2] & 0xFF)); + const DWORD lpcm_freq_tab[] = { 48000, 96000, 44100, 32000 }; + plaudioinfo.samplerate = lpcm_freq_tab[(buf[4] >> 4) & 0x3]; + plaudioinfo.numberofchannels = (buf[4] & 0x7) + 1; + switch ((buf[4] >> 6) & 3) + { + case 1: + plaudioinfo.numberofbitspersample = 20; + break; + case 2: + plaudioinfo.numberofbitspersample = 24; + break; + case 3: + plaudioinfo.numberofbitspersample = 32; + break; + default: // = 0 + plaudioinfo.numberofbitspersample = 16; + } + plaudioinfo.dynrange = buf[5]; + buf += 6; + + pack_length += 6; + plaudioinfo.type = eAudioFormat_PCM; + } + break; + } + default: + ; + } + + packet->pData = buf; + packet->size = pes_packet_length - pes_header_data_length - pes_header_data_length0 - pack_length; + + if (need_skip) + return 0; + + if (encrypted) + { + packet->encryptedinfo = 0x8000 | (105 - pes_header_data_length - pack_length); + if (tell_about_encryption) + { + MSG("MPEG: !!CSS-encrypted!!\n"); + tell_about_encryption = false; + } + } + + mpeg_format_changed = false; + if (packet->type == 1 && !forbid_setaudio) // audio + { + plaudioinfo.fromstream = 1; + mpeg_setaudioparams(&plaudioinfo); + } + + return 1; + } + return -1; +} + +int mpeg_parse_ac3_header(BYTE *buf, int len, int *bitrate, int *sample_rate, int *channels, int *lfe, int *framesize) +{ + if (len < 7) + return -1; + // check sync.word + if (buf[0] != 0xb || buf[1] != 0x77) + return -1; + + int bsid = (buf[5] >> 3) & 0x1f; + + // if E-AC-3, not AC-3 + if (bsid > 10) + return -2; + + int fscod = (buf[4] >> 6) & 3; + if (fscod == 3) + return -3; + + int frmsizecod = buf[4] & 0x3f; + if (frmsizecod > 37) + return -4; + + int frame_size = ac3_frame_sizes[frmsizecod][fscod] * 2; + ac3_total_frame_size = ac3_next_total_frame_size; + ac3_next_total_frame_size += frame_size; + + if (framesize != NULL) + *framesize = frame_size; +/* + msg("frame = %d, bps = %d\n", frame_size, + (ac3_bitrates[frmsizecod >> 1] * 1000) >> halfratecod); +*/ + + // fill in extended info + if (bitrate != NULL) + { + int halfratecod = MAX(bsid, 8) - 8; + + *bitrate = (ac3_bitrates[frmsizecod >> 1] * 1000) >> halfratecod; + if (sample_rate != NULL) + *sample_rate = ac3_freqs[fscod] >> halfratecod; + int acmod = (buf[6] >> 5) & 7; + if (channels != NULL) + *channels = ac3_channels[acmod]; + if (lfe != NULL) + { + int lfe_offs = 4; + if ((acmod & 1) && acmod != 1) + lfe_offs -= 2; + if (acmod & 4) + lfe_offs -= 2; + if (acmod == 2) + lfe_offs -= 2; + + *lfe = (buf[6] >> lfe_offs) & 1; + } + } + + return 0; +} + +BOOL mpeg_audio_format_changed() +{ + return mpeg_format_changed; +} + +LONGLONG mpeg_detect_and_fix_pts_wrap(MpegPacket *packet) +{ + LONGLONG diff = last_video_pts - packet->pts; + LONGLONG scr = packet->scr & (~SPTM_SCR_FLAG); + if (use_scr && Abs(packet->pts - scr) > MPEG_WRAP_THRESHOLD) + { + packet->scr = 0; + use_scr = false; + } + if (last_video_pts != 0 && (int)diff > MPEG_WRAP_THRESHOLD) + { + // fix packet data + packet->pts += diff; + packet->scr = (scr + diff) | SPTM_SCR_FLAG; + + return diff; + } + + return 0; +} + +BOOL mpeg_needs_seq_start_header(MpegPacket **cur_packet) +{ + if (mpeg_was_seq_header) + return TRUE; +#if 1 + // for MPEG2 we can only wait for native seq.headers + if (!mpeg_is_mpeg1 && !mpeg_was_picture) + return FALSE; +#endif + + if (mpeg_seq_start_packet_len < 1) + return FALSE; + + // first, save current packet + MpegPacket tmp_packet; + memcpy((BYTE *)&tmp_packet + 4, (BYTE *)*cur_packet + 4, sizeof(MpegPacket) - 4); + + MpegPacket *packet = NULL; + packet = mpeg_feed_getlast(); + if (packet == NULL) // well, it won't really help + return FALSE; + + memset((BYTE *)packet + 4, 0, sizeof(MpegPacket) - 4); + //packet->type = 0; + //packet->flags = 0; + //packet->bufidx = 0; + packet->pData = mpeg_seq_start_packet; + packet->size = mpeg_seq_start_packet_len; + mpeg_feed(MPEG_FEED_VIDEO); + + *cur_packet = mpeg_feed_getlast(); + if (*cur_packet == NULL) // well, it won't really help + return FALSE; + memcpy((BYTE *)*cur_packet + 4, (BYTE *)&tmp_packet + 4, sizeof(MpegPacket) - 4); + + return TRUE; +} + +void mpeg_set_scale_rate(DWORD *scale, DWORD *rate) +{ + if (*scale < 1) + *scale = 1; + if (*rate < 1) + *rate = 1; + + if (*scale < 1000) + { + int d = 1000 / *scale; + *scale *= d; + *rate *= d; + } + if (*rate >= 64000) + { + *rate = (int)(((LONGLONG)*rate * 1000) / *scale); + *scale = 1000; + } + if (*scale == 1000 && *rate == 23975) + { + *rate = 24000; + *scale = 1001; + } + if (*scale == 1000 && *rate == 23976) + { + *rate = 24000; + *scale = 1001; + } + if (*scale == 1000) + { + if (*rate >= 29992 && *rate <= 30007) + { + if (*rate != 30000) + { + *rate = 30000; + *scale = 1001; + } + } + } + + khwl_setproperty(KHWL_TIME_SET, etimVOPTimeIncrRes, sizeof(int), rate); + khwl_setproperty(KHWL_TIME_SET, etimVideoCTSTimeScale, sizeof(int), rate); + khwl_setproperty(KHWL_TIME_SET, etimAudioCTSTimeScale, sizeof(int), rate); + + KHWL_FIXED_VOP_RATE_TYPE voprate; + voprate.force = 1; + voprate.time_incr = *scale; + voprate.incr_res = *rate; + mpeg_vop_rate = *rate; + mpeg_vop_scale = *scale; + khwl_setproperty(KHWL_DECODER_SET, edecForceFixedVOPRate, sizeof(voprate), &voprate); + + mpeg_fps = (int)(INT64(1000) * mpeg_vop_rate / mpeg_vop_scale); +} + +void mpeg_zoom_scroll() +{ + const int zoom_delta = 25; + const int scroll_delta = 15; + + MSG("MPEG: zoom_scroll(%d,%d %d,%d)\n", mpeg_zoom_hscale, mpeg_zoom_vscale, mpeg_zoom_offsetx, mpeg_zoom_offsety); + + khwl_set_window(mpeg_width, mpeg_height, mpeg_width, mpeg_height, + 100 + zoom_delta*mpeg_zoom_hscale, 100 + zoom_delta*mpeg_zoom_vscale, + mpeg_zoom_offsetx*scroll_delta, mpeg_zoom_offsety*scroll_delta); +} + +BOOL mpeg_zoom_hor(int scale) +{ + mpeg_zoom_hscale = scale; + mpeg_zoom_scroll(); + return TRUE; +} + +BOOL mpeg_zoom_ver(int scale) +{ + mpeg_zoom_vscale = scale; + mpeg_zoom_scroll(); + return TRUE; +} + +BOOL mpeg_scroll(int offsetx, int offsety) +{ + mpeg_zoom_offsetx = offsetx; + mpeg_zoom_offsety = offsety; + mpeg_zoom_scroll(); + return TRUE; +} + +BOOL mpeg_zoom_reset() +{ + if (mpeg_zoom_hscale != 0 || mpeg_zoom_vscale != 0 || mpeg_zoom_offsetx != 0 || mpeg_zoom_offsety != 0) + { + mpeg_zoom_hscale = 0; + mpeg_zoom_vscale = 0; + mpeg_zoom_offsetx = 0; + mpeg_zoom_offsety = 0; + mpeg_zoom_scroll(); + return TRUE; + } + return FALSE; +} diff --git a/src/libsp/sp_mpeg.h b/src/libsp/sp_mpeg.h new file mode 100644 index 0000000..d5eb435 --- /dev/null +++ b/src/libsp/sp_mpeg.h @@ -0,0 +1,456 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - MPEG support header file + * \file sp_mpeg.h + * \author bombur + * \version 0.1 + * \date 2.08.2004 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#ifndef SP_MPEG_H +#define SP_MPEG_H + +#ifdef __cplusplus +extern "C" { +#endif + +#define SPTM_SCR_FLAG INT64(0x8000000000000000) + +#define FOURCC(ch0, ch1, ch2, ch3) \ + ((DWORD)(BYTE)(ch0) | \ + ((DWORD)(BYTE)(ch1) << 8) | \ + ((DWORD)(BYTE)(ch2) << 16) | \ + ((DWORD)(BYTE)(ch3) << 24)) + +static inline ULONGLONG GET_ULONGLONG(BYTE *b) +{ + ULONGLONG r = (b[0] | (b[1]<<8) | (b[2]<<16) | (b[3]<<24)); + ULONGLONG s = (b[4] | (b[5]<<8) | (b[6]<<16) | (b[7]<<24)); + return ((s << 32) & INT64(0xffffffff00000000U)) | (r & INT64(0xffffffff)); +} + + +static inline DWORD GET_DWORD(BYTE *b) +{ + return (b[0] | (b[1]<<8) | (b[2]<<16) | (b[3]<<24)); +} + +static inline DWORD GET_SYNCSAFE_DWORD(BYTE *b) +{ + return ((b[0] & 0x7f) << 21) | ((b[1] & 0x7f) << 14) + | ((b[2] & 0x7f) << 7) | (b[3] & 0x7f); +} + +static inline WORD GET_WORD(BYTE *b) +{ + return (WORD)(b[0] | (b[1] << 8)); +} + +#ifdef WIN32 +#pragma pack(1) +#endif + +/// MPEG Write packet (not 100% accurate info) +typedef struct MpegPacket_s MpegPacket; +struct MpegPacket_s +{ + MpegPacket *next; + int type; + DWORD flags; + + DWORD reserved[3]; // indexes, counters - not used (i guess) + + BYTE *pData; + DWORD size; + LONGLONG pts; + LONGLONG dts; // not used ? + DWORD encryptedinfo; + WORD nframeheaders; + WORD firstaccessunitpointer; + BYTE *bufidx; + DWORD AudioType; + + LONGLONG scr; + LONGLONG vobu_sptm; +} ATTRIBUTE_PACKED; + +#ifdef WIN32 +#pragma pack() +#endif + +typedef struct +{ + MpegPacket *in; + MpegPacket *out; + int num; + int out_cnt; + int in_cnt; + DWORD reserved; +} MpegPlayStruct; + +typedef struct +{ + KHWL_AUDIO_FORMAT_TYPE type; + int dynrange; + int samplerate; + int numberofchannels; + int numberofbitspersample; + + BOOL fromstream; +} MpegAudioPacketInfo; + +typedef struct +{ + void *payload; + DWORD payload_size; + ULONGLONG pts; +} MpegSpuPacketTnfo; + +extern int MPEG_PACKET_LENGTH; // = 2048 for standard MPEG1/2 +extern int MPEG_NUM_PACKETS; // = 512 (DVD, VCD) or 2048 (others) + +/// Data pointers used by player code +extern MpegPlayStruct *MPEG_PLAY_STRUCT; +extern MpegPlayStruct *MPEG_VIDEO_STRUCT; +extern MpegPlayStruct *MPEG_AUDIO_STRUCT; +extern MpegPlayStruct *MPEG_SPU_STRUCT; + +/// Packet feed types +typedef enum +{ + MPEG_FEED_VIDEO = 0, + MPEG_FEED_AUDIO, + MPEG_FEED_SPU, +} MPEG_FEED_TYPE; + +/// Default buffers storage (use it in mpeg_setbuffer()) +extern BYTE *BUF_BASE; + +/// Source media types +typedef enum +{ + MEDIA_TYPE_UNKNOWN, + MEDIA_TYPE_DVD = 1, + MEDIA_TYPE_VCD = 2, + MEDIA_TYPE_MPEG = 3, + MEDIA_TYPE_AUDIO = 4, + MEDIA_TYPE_VIDEO = 5, + MEDIA_TYPE_CDDA = 6, + //... +} MEDIA_TYPES; + +/// Packet types +typedef enum +{ + START_CODE_UNKNOWN = 0xff, + START_CODE_END = 0xfe, + + START_CODE_PACK = 0, + START_CODE_SYSTEM, + START_CODE_PCI, + START_CODE_PADDING, + START_CODE_MPEG_VIDEO, + START_CODE_MPEG_VIDEO_ELEMENTARY, + START_CODE_MPEG_AUDIO1, + START_CODE_MPEG_AUDIO2, + START_CODE_PRIVATE1, +} START_CODE_TYPES; + +/// Playing speed +typedef enum +{ + MPEG_SPEED_STOP = 0, + MPEG_SPEED_NORMAL = 1, // play/resume + MPEG_SPEED_PAUSE = 2, + MPEG_SPEED_STEP = 3, + + /////////////////////////////////////////////////////// + /// masks: + MPEG_SPEED_FWD_MASK = 0x100, + MPEG_SPEED_REV_MASK = 0x200, + MPEG_SPEED_FAST_FWD_MASK = MPEG_SPEED_FWD_MASK | 0x10, + MPEG_SPEED_FAST_REV_MASK = MPEG_SPEED_REV_MASK | 0x20, + MPEG_SPEED_SLOW_FWD_MASK = MPEG_SPEED_FWD_MASK | 0x40, + MPEG_SPEED_SLOW_REV_MASK = MPEG_SPEED_REV_MASK | 0x80, + /////////////////////////////////////////////////////// + + MPEG_SPEED_FWD_4X = MPEG_SPEED_FAST_FWD_MASK | 1, + MPEG_SPEED_FWD_8X = MPEG_SPEED_FAST_FWD_MASK | 2, + MPEG_SPEED_FWD_16X = MPEG_SPEED_FAST_FWD_MASK | 3, + MPEG_SPEED_FWD_32X = MPEG_SPEED_FAST_FWD_MASK | 4, + MPEG_SPEED_FWD_48X = MPEG_SPEED_FAST_FWD_MASK | 5, + MPEG_SPEED_FWD_MAX = MPEG_SPEED_FAST_FWD_MASK | 6, + + MPEG_SPEED_REV_4X = MPEG_SPEED_FAST_REV_MASK | 1, + MPEG_SPEED_REV_8X = MPEG_SPEED_FAST_REV_MASK | 2, + MPEG_SPEED_REV_16X = MPEG_SPEED_FAST_REV_MASK | 3, + MPEG_SPEED_REV_32X = MPEG_SPEED_FAST_REV_MASK | 4, + MPEG_SPEED_REV_48X = MPEG_SPEED_FAST_REV_MASK | 5, + MPEG_SPEED_REV_MAX = MPEG_SPEED_FAST_REV_MASK | 6, + + MPEG_SPEED_SLOW_FWD_2X = MPEG_SPEED_SLOW_FWD_MASK | 1, // 1/2 + MPEG_SPEED_SLOW_FWD_4X = MPEG_SPEED_SLOW_FWD_MASK | 2, // 1/4 + MPEG_SPEED_SLOW_FWD_8X = MPEG_SPEED_SLOW_FWD_MASK | 3, // 1/8 + + MPEG_SPEED_SLOW_REV_2X = MPEG_SPEED_SLOW_REV_MASK | 1, // 1/2 + MPEG_SPEED_SLOW_REV_4X = MPEG_SPEED_SLOW_REV_MASK | 2, // 1/4 + MPEG_SPEED_SLOW_REV_8X = MPEG_SPEED_SLOW_REV_MASK | 3, // 1/8 + + MPEG_SPEED_UNKNOWN = 0xffffffff, + +} MPEG_SPEED_TYPE; + + +typedef enum +{ + MPEG_BUFFER_1 = 0, + MPEG_BUFFER_2 = 1, + MPEG_BUFFER_3 = 2, +} MPEG_BUFFER; + +typedef enum +{ + MPEG_UNKNOWN = 0, + MPEG_1 = 1, + MPEG_2 = 2, + MPEG_4 = 4, + +} MPEG_VIDEO_FORMAT; + +////////////////////////////////////////////////////// +/// Functions + +/// Initialize MPEG player. +/// If 'videobuf' is set, a separate videobuffer is created. +BOOL mpeg_init(MPEG_VIDEO_FORMAT fmt, BOOL audio_only, BOOL mpa_parsing, BOOL need_seq_start_packet); + +/// Set buffers from 1 big memory chunk +BOOL mpeg_setbuffer(MPEG_BUFFER which, BYTE *base, int num_bufs, int buf_size); +/// Set buffers from array +BOOL mpeg_setbuffer_array(MPEG_BUFFER which, BYTE **base, int num_bufs, int buf_size); + +/// Get main buffer base address +BYTE *mpeg_getbufbase(); + +/// Get buffer size +int mpeg_getbufsize(MPEG_BUFFER which); + +/// Deinitialize MPEG player +BOOL mpeg_deinit(); + +/// Change playing speed +BOOL mpeg_setspeed(MPEG_SPEED_TYPE speed); + +/// Get last frame dimensions +BOOL mpeg_getframesize(int *width, int *height); + +/// Get current frames-per-second, in x1000 format (25000 = 25 fps) +int mpeg_get_fps(); + +/// Get current aspect ratio (1 = 1:1, 2 = 4:3, 3 = 16:9) +int mpeg_getaspect(); + +/// Zoom window horizontally (scale = 0, 1, 2,... for zoom-out, and negative for zoom-in) +BOOL mpeg_zoom_hor(int scale); +/// Zoom window vertically (scale = 0, 1, 2,... for zoom-out, and negative for zoom-in) +BOOL mpeg_zoom_ver(int scale); + +/// Scroll window (offset = ..., -2, -1, 0, 1, 2,...) +/// <0 = window is shifted to the left +/// >0 = window is shifted to the right +BOOL mpeg_scroll(int offsetx, int offsety); + +/// Reset zoom&scroll to defaults +BOOL mpeg_zoom_reset(); + +/// Reset MPEG queues and data before next play +BOOL mpeg_reset(); + +/// Start playing, but wait for +void mpeg_start(); + +/// Feed given struct directly to khwl queue +BOOL mpeg_feed(MPEG_FEED_TYPE type); + +/// Cached feed version - push packet into the stack (up to 16 packets in stack) +BOOL mpeg_feed_push(); + +/// Pop stored packet from the FIFO stack into khwl queue +BOOL mpeg_feed_pop(); + +/// Return TRUE if feed stack is empty +BOOL mpeg_feed_isempty(); + +BOOL mpeg_feed_reset(); + +BOOL mpeg_correct_pts(); + +/// Init queue +BOOL mpeg_feed_init(MpegPacket *start, int num); + +/// Make sure we start playing +void mpeg_play(); + +/// Make sure we stopped playing +void mpeg_stop(); + +/// Start normal play (but don't change speed) - used for 'wait' func. +void mpeg_play_normal(); + +/// Return true if we are actually playing (not just buffering) +BOOL mpeg_is_playing(); + +/// Return true if any frame was displayed +BOOL mpeg_is_displayed(); + +/// Get video format. +MPEG_VIDEO_FORMAT mpeg_get_video_format(); + +/// Reset (clear) packets data +BOOL mpeg_init_packets(); + +/// Return how many packets are in processing +int mpeg_feed_getstackdepth(); + +/// Wait for all packets are processed by decoder. +BOOL mpeg_wait(BOOL onetime); + +/// Get Last packet ready to fill in +MpegPacket *mpeg_feed_getlast(); + +/// Return current buffer's pointer +BYTE *mpeg_getcurbuf(MPEG_BUFFER which); + +int mpeg_getpacketlength(); + +/// Set system PTS value +void mpeg_setpts(ULONGLONG estpts); + +/// Get system PTS value +ULONGLONG mpeg_getpts(); + +/// Get stream rate, in bytes per sec. (or 0 if not ready) +int mpeg_getrate(BOOL now); + +void mpeg_resetstreams(); + +/// Change audio stream +void mpeg_setaudiostream(int id); + +/// Get current audio stream +int mpeg_getaudiostream(); + +/// Get audio streams number +int mpeg_getaudiostreamsnum(); + +/// Change SPU stream +void mpeg_setspustream(int id); + +/// Get current SPU stream +int mpeg_getspustream(); + +/// Get SPU streams number +int mpeg_getspustreamsnum(); + +/// Set current buffer index to free chunk (or wait for it) +int mpeg_find_free_blocks(const MPEG_BUFFER which); + +/// Increase and set buffer index for packet +void mpeg_setbufidx(MPEG_BUFFER which, MpegPacket *packet); + +/// Decrease buffer index and release packet +void mpeg_release_packet(MPEG_BUFFER which, MpegPacket *packet); + +/// Check for the special 'sequence start' header (and send one for MPEG1) +BOOL mpeg_needs_seq_start_header(MpegPacket **cur_packet); + +// sorry, a packet parsing part is for CPP only +#ifdef __cplusplus + +/// Retrieve the next packet and determine its type +START_CODE_TYPES mpeg_findpacket(BYTE * &buf, BYTE *base, int &startCounter); + +/// Get PTS from packet header +ULONGLONG mpeg_extractTimingStampfromPESheader(BYTE * &buf); + +/// Get MPEG type and bitrate, and return SCR +LONGLONG mpeg_parse_program_stream_pack_header(BYTE * &buf, BYTE *base); + +/// Get Dolby AC-3 bitrate and audio params. +int mpeg_parse_ac3_header(BYTE *buf, int len, int *bitrate, int *sample_rate, int *channels, int *lfe, int *framesize); + +/// Set audio params to KHWL +int mpeg_setaudioparams(MpegAudioPacketInfo *paudio); + +/// Get current audio params copy +MpegAudioPacketInfo mpeg_getaudioparams(); + +/// Set audio sample rate and check if it's supported by decoder. +/// Return the closest available rate. +int mpeg_set_sample_rate(int samplerate); + +/// Returns if resampling needed for non-standard audio sample rates. +BOOL mpeg_is_resample_needed(); + +/// Returns TRUE if audio format changed from the parsed MPEG stream +BOOL mpeg_audio_format_changed(); + +/// Fill packet data from raw packet +int mpeg_extractpacket(BYTE * &buf, BYTE *base, MpegPacket *packet, START_CODE_TYPES type, BOOL parse_video); + +/// Detect & fix PTS wrap. Return fix PTS offset for new packets. +LONGLONG mpeg_detect_and_fix_pts_wrap(MpegPacket *packet); + +/// Set MPEG4 VOP rate and scale +void mpeg_set_scale_rate(DWORD *scale, DWORD *rate); + +/// Set video frame size +BOOL mpeg_setframesize(int width, int height, bool noaspect); + +/// Converts little-endian PCM to big-endian LPCM +void mpeg_PCM_to_LPCM(BYTE *buf, int len); + +/// Resample PCM audio and return bytes read. +/// Also converts little-endian PCM to big-endian. +int mpeg_PCM_resample_to_LPCM(BYTE *buf, int len, BYTE *out, int *outlen); + +/// Advance data pointer with bounds check +inline int mpeg_incParsingBufIndex(BYTE * &buf, BYTE *base, DWORD inc) +{ + if (buf + inc <= base + MPEG_PACKET_LENGTH) + { + buf += inc; + return TRUE; + } + return FALSE; +} + +inline bool mpeg_eofBuf(BYTE * buf, BYTE *base) +{ + return (buf >= base + MPEG_PACKET_LENGTH); +} + +WORD mpeg_calc_crc16(BYTE *buf, DWORD bitsize); + +#endif // of #ifdef __cplusplus + +#ifdef __cplusplus +} +#endif + +#endif // of SP_MPEG_H diff --git a/src/libsp/sp_msg.cpp b/src/libsp/sp_msg.cpp new file mode 100644 index 0000000..837511c --- /dev/null +++ b/src/libsp/sp_msg.cpp @@ -0,0 +1,177 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - debug messaging functions impl. + * \file sp_msg.cpp + * \author bombur + * \version 0.1 + * \date 4.05.2004 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include + +#include +#include + +#ifndef SP_MSG_SIMPLE +#include +#include +#endif + +// 0 = off, 1 = on, 2 = ready_to_clear +MSG_OUTPUT msg_output = MSG_OUTPUT_HIDE; +int msg_filter = MSG_FILTER_ALL; + +static char msgbuf[4096]; + +void msg_set_filter(int f) +{ + msg_filter = f; +} + +int msg_get_filter() +{ + return msg_filter; +} + +void msg_init() +{ +#ifndef SP_MSG_SIMPLE +#ifdef WIN32 + FILE *fp = fopen("sp_log.txt", "wt"); + fclose(fp); +#endif +#endif +} + +void msg_set_output(MSG_OUTPUT o) +{ + if (o == MSG_OUTPUT_SHOW) + { + msg_output = o; +#ifndef SP_MSG_SIMPLE + gui.UpdateEnable(true); + gui.ShowWindow(console, true); +#endif + msg("DEBUG show...\n"); + } + else if (o == MSG_OUTPUT_HIDE) + { + msg_output = o; +#ifndef SP_MSG_SIMPLE + gui.ShowWindow(console, false); +#endif + } + else if (o == MSG_OUTPUT_FREEZE) + { + msg("DEBUG stop...\n"); + msg_output = o; + } +} + +MSG_OUTPUT msg_get_output() +{ + return msg_output; +} + +/////////////////////////////////////////////// + +static void msg_internal(const char *msgbuf) +{ + if (msg_output != MSG_OUTPUT_FREEZE) + { + printf(msgbuf);fflush(stdout); + } + +#ifndef SP_MSG_SIMPLE + if (console != NULL) + console->AddString(msgbuf); + +#ifdef WIN32 + FILE *fp = fopen("sp_log.txt", "at"); + if (fp) { fprintf(fp, msgbuf);fclose(fp); } +#endif +#endif +} + +void msg(const char *text, ...) +{ + if (msg_output != MSG_OUTPUT_FREEZE && (msg_filter & MSG_FILTER_DEBUG) == MSG_FILTER_DEBUG) + { + va_list args; + va_start(args, text); + vsnprintf(msgbuf, 4096, text, args); + va_end(args); + msg_internal(msgbuf); + } +} + +void msg_error(const char *text, ...) +{ + if (msg_output != MSG_OUTPUT_FREEZE && (msg_filter & MSG_FILTER_ERROR) == MSG_FILTER_ERROR) + { + va_list args; + va_start(args, text); + vsnprintf(msgbuf, 4096, text, args); + va_end(args); + msg_internal(msgbuf); + } +} + +void msg_critical(const char *text, ...) +{ + if (/*msg_output != MSG_OUTPUT_FREEZE && */ (msg_filter & MSG_FILTER_CRITICAL) == MSG_FILTER_CRITICAL) + { + va_list args; + va_start(args, text); + vsnprintf(msgbuf, 4096, text, args); + va_end(args); + msg_internal(msgbuf); + } +} + +void msg_sysinfo() +{ +#ifndef SP_MSG_SIMPLE + struct sysinfo si; + sysinfo(&si); + + msg("uptime : %d\n", si.uptime); + msg("totalram : %d\n", si.totalram); + msg("freeram : %d\n", si.freeram); + //msg("sharedram : %d\n", si.sharedram); + //msg("bufferram : %d\n", si.bufferram); + //msg("totalswap : %d\n", si.totalswap); + //msg("freeswap : %d\n", si.freeswap); + msg("procs : %d\n", si.procs); + //msg("totalhigh : %d\n", si.totalhigh); + //msg("freehigh : %d\n", si.freehigh); + //msg("mem_unit : %d\n", si.mem_unit); +#endif +} + +void msg_shell() +{ +#ifndef SP_MSG_SIMPLE + static const char *args[] = { "busybox", "sh", NULL }; + printf("Starting SHELL...\n"); + exec_file("/bin/busybox", args); +#endif +} diff --git a/src/libsp/sp_msg.h b/src/libsp/sp_msg.h new file mode 100644 index 0000000..5acb102 --- /dev/null +++ b/src/libsp/sp_msg.h @@ -0,0 +1,80 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - debug messaging functions header file + * \file sp_msg.h + * \author bombur + * \version 0.1 + * \date 4.05.2004 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#ifndef SP_MSG_H +#define SP_MSG_H + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum MSG_OUTPUT +{ + MSG_OUTPUT_HIDE = 0, + MSG_OUTPUT_SHOW = 1, + MSG_OUTPUT_FREEZE = 2, +} MSG_OUTPUT; + +typedef enum MSG_FILTER +{ + MSG_FILTER_NONE = 0, + + MSG_FILTER_CRITICAL = 1, + MSG_FILTER_ERROR = 2, + MSG_FILTER_DEBUG = 4, + + MSG_FILTER_ALL = MSG_FILTER_CRITICAL | MSG_FILTER_ERROR | MSG_FILTER_DEBUG, +} MSG_FILTER; + +/// Initialize debug/messaging system +void msg_init(void); + +/// Set messages output type (show/hide/disable) +void msg_set_output(MSG_OUTPUT o); +/// Get messages output type +MSG_OUTPUT msg_get_output(void); + +/// Set messages filter (see MSG_FILTER bitfields) +void msg_set_filter(int); +/// Get messages filter +int msg_get_filter(void); + +/// Output debug message to the console. +void msg(const char *text, ...); +/// Output error message to the console. +void msg_error(const char *text, ...); +/// Output critical error message to the console. +void msg_critical(const char *text, ...); + +/// Output system information +void msg_sysinfo(void); + +/// Open TTY shell +void msg_shell(void); + +#ifdef __cplusplus +} +#endif + +#endif // of SP_MSG_H diff --git a/src/libsp/sp_video.cpp b/src/libsp/sp_video.cpp new file mode 100644 index 0000000..a2995e7 --- /dev/null +++ b/src/libsp/sp_video.cpp @@ -0,0 +1,753 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - KHWL misc. video functions source file. + * \file sp_video.cpp + * \author bombur + * \version 0.1 + * \date 4.07.2004 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include + +#include +#include +#include + +static KHWL_WINDOW dest_wnd; +static KHWL_VIDEOMODE cur_mode = KHWL_VIDEOMODE_NORMAL; +static int cur_aspect = evInAspectRatio_4x3; +static int cur_output = evOutDisplayOption_Normal; +static int frame_left = 0, frame_top = 0, frame_right = 719, frame_bottom = 479; + +static int khwl_wide = 0; + +int khwl_display_clear() +{ + //return ioctl(khwl_handle, KHWL_DISPLAY_CLEAR, 0); + int var = ebiCommand_VideoHwBlackFrame; + return khwl_setproperty(KHWL_BOARDINFO_SET, ebiCommand, sizeof(DWORD), &var); +} + +void khwl_set_max_osd_wnd(int width, int osd_width, int height, int osd_height) +{ + KHWL_WINDOW wnd; + wnd.x = (width * 35) / 720; + + int h = height; + if (h < 0) + h += 31; + wnd.y = h / 32; + + wnd.w = width * osd_width / 720; + + wnd.h = height * osd_height / 480; + + khwl_getproperty(KHWL_OSD_SET, eOsdDestinationWindow, sizeof(wnd), &wnd); + +} + +void khwl_switch_dvi(bool is_on) +{ + KHWL_ADDR_DATA data; + if (is_on) + { + data.Addr = 8; + data.Data = 55; + khwl_setproperty(KHWL_DVI_TRANSMITTER_SET, edtAccessRegister, sizeof(data), &data); + data.Addr = 9; + data.Data = 13; + khwl_setproperty(KHWL_DVI_TRANSMITTER_SET, edtAccessRegister, sizeof(data), &data); + data.Addr = 10; + data.Data = 240; + khwl_setproperty(KHWL_DVI_TRANSMITTER_SET, edtAccessRegister, sizeof(data), &data); + data.Addr = 12; + data.Data = 137; + khwl_setproperty(KHWL_DVI_TRANSMITTER_SET, edtAccessRegister, sizeof(data), &data); + data.Addr = 13; + data.Data = 0; + khwl_setproperty(KHWL_DVI_TRANSMITTER_SET, edtAccessRegister, sizeof(data), &data); + data.Addr = 14; + data.Data = 0; + khwl_setproperty(KHWL_DVI_TRANSMITTER_SET, edtAccessRegister, sizeof(data), &data); + data.Addr = 15; + data.Data = 0; + khwl_setproperty(KHWL_DVI_TRANSMITTER_SET, edtAccessRegister, sizeof(data), &data); + } else + { + data.Addr = 8; + data.Data = 6; + khwl_setproperty(KHWL_DVI_TRANSMITTER_SET, edtAccessRegister, sizeof(data), &data); + data.Addr = 12; + data.Data = 10; + khwl_setproperty(KHWL_DVI_TRANSMITTER_SET, edtAccessRegister, sizeof(data), &data); + data.Addr = 51; + data.Data = 1; + khwl_setproperty(KHWL_DVI_TRANSMITTER_SET, edtAccessRegister, sizeof(data), &data); + } +} + +void khwl_setwide(BOOL wide) +{ + khwl_wide = wide ? 1 : 0; +} + +int khwl_setdisplay(KHWL_TV_OUTPUT_FORMAT_TYPE mode, KHWL_TV_STANDARD_TYPE standard) +{ + static KHWL_TV_OUTPUT_FORMAT_TYPE old_mode = evTvOutputFormat_NONE; + static int old_device = evOutputDevice_TV; + static int old_standard = -1; + static BOOL old_wide = TRUE; + + static const KHWL_DIG_OV_PARAMS dig_params[10] = + { + // YUV-480P + { 31469, 5994, 720, 480, 858, 16, 62, 60, 525, 9, 6, 30, 0, 0, 0, 0, + 8, evTvStandard_480P, 0, 0, 1, 1, 0 }, + // YUV-576P + { 31250, 5004, 720, 576, 864, 20, 63, 60, 625, 5, 5, 39, 0, 0, 0, 0, + 8, evTvStandard_576P, 0, 0, 1, 1, 0 }, + // YUV-720P + { 45000, 6000, 1280, 720, 1650, 110, 40, 220, 750, 5, 5, 20, 0, 0, 1, 1, + 24, evTvStandard_720P, 0, 0, 1, 1, 0 }, + // YUV-720P-50 + { 37500, 5000, 1280, 720, 1650, 110, 40, 220, 750, 5, 5, 20, 0, 0, 1, 1, + 24, evTvStandard_720P, 0, 0, 1, 1, 0 }, + // YUV-1080I + { 33716, 5994, 1920, 1080, 2199, 88, 44, 148, 1125, 5, 10, 30, 0, 1, 0, 0, + 24, evTvStandard_1080I, 0, 0, 1, 1, 0 }, + + ///////////////////// + + // DVI-480P + { 31469, 5994, 720, 480, 858, 16, 62, 60, 525, 9, 6, 30, 0, 0, 0, 0, + 24, evTvStandard_480P, 0, 0, 1, 1, 0 }, + // DVI-576P + { 31250, 5004, 720, 576, 864, 20, 63, 60, 625, 5, 5, 39, 0, 0, 0, 0, + 24, evTvStandard_576P, 0, 0, 1, 1, 0 }, + // DVI-1080I + { 33716, 5994, 1920, 1080, 2199, 88, 44, 148, 1125, 5, 10, 30, 0, 1, 1, 1, + 24, evTvStandard_1080I, 0, 0, 1, 1, 0 }, + }; + + int use_dvi = false, use_hd = false; + bool is_on = mode != evTvOutputFormat_OUTPUT_OFF; + + int val; + if (is_on) + { + if (standard != old_standard) + { + KHWL_WINDOW valid_wnd; + const KHWL_DIG_OV_PARAMS *dig = NULL; + valid_wnd.x = valid_wnd.y = 0; + + // set params + switch (standard) + { + case evTvStandard_480P: + dig = &dig_params[0]; + break; + case evTvStandard_576P: + dig = &dig_params[1]; + break; + case evTvStandard_720P: + dig = &dig_params[2]; + break; + case evTvStandard_720P50: + dig = &dig_params[3]; + break; + case evTvStandard_1080I: + dig = &dig_params[4]; + break; + case evTvStandard_NTSC: + valid_wnd.w = 720; + valid_wnd.h = 480; + break; + default: + case evTvStandard_PAL: + valid_wnd.w = 720; + valid_wnd.h = 576; + break; + + // TODO: add HDTV modes... + } + + if (dig != NULL) + { + valid_wnd.w = dig->VideoWidth; + valid_wnd.h = dig->VideoHeight; + + khwl_setproperty(KHWL_VIDEO_SET, evDigOvOnlyParams, sizeof(KHWL_DIG_OV_PARAMS), (void *)dig); + + old_mode = evTvOutputFormat_NONE; + old_standard = -1; + standard = dig->TvHdtvStandard; + + if (mode == evTvOutputFormat_COMPONENT_YUV) + use_hd = true; + else if (mode == evTvOutputFormat_DVI) + use_dvi = true; + } else + { + val = standard; + khwl_setproperty(KHWL_VIDEO_SET, evTvStandard, sizeof(DWORD), &val); + if (old_standard != evTvStandard_NTSC && old_standard != evTvStandard_PAL) + old_mode = evTvOutputFormat_NONE; + old_standard = standard; + } + + khwl_setproperty(KHWL_VIDEO_SET, evValidWindow, sizeof(valid_wnd), &valid_wnd); + } + + if (mode != old_mode) + { + val = (mode == evTvOutputFormat_DVI) ? evTvOutputFormat_COMPONENT_RGB_SCART : mode; + khwl_setproperty(KHWL_VIDEO_SET, evTvOutputFormat, sizeof(DWORD), &val); + + val = use_dvi ? evOutputDevice_HDTV : (use_hd ? evOutputDevice_DigOvOnly : evOutputDevice_TV); + if (old_device != val) + { + khwl_setproperty(KHWL_VIDEO_SET, evOutputDevice, sizeof(DWORD), &val); + old_device = val; + } + + KHWL_ADDR_DATA data; + + // composite/component? + data.Addr = 3; + data.Data = (mode == evTvOutputFormat_COMPOSITE) ? 1 : 0; + khwl_setproperty(KHWL_BOARDINFO_SET, ebiPIOAccess, sizeof(data), &data); + + // DVI + data.Addr = 5; + data.Data = use_dvi ? 0 : 1; + khwl_setproperty(KHWL_BOARDINFO_SET, ebiPIOAccess, sizeof(data), &data); + + // RCA-output? + data.Addr = 6; + data.Data = (mode != evTvOutputFormat_COMPONENT_RGB_SCART) ? 1 : 0; + khwl_setproperty(KHWL_BOARDINFO_SET, ebiPIOAccess, sizeof(data), &data); + + khwl_switch_dvi(use_hd || use_dvi); + } + + // Wide + if (khwl_wide != old_wide) + { + KHWL_ADDR_DATA data; + data.Addr = 7; + data.Data = khwl_wide ? 1 : 0; + khwl_setproperty(KHWL_BOARDINFO_SET, ebiPIOAccess, sizeof(data), &data); + old_wide = khwl_wide; + } + + // init as normal/wide + khwl_getproperty(KHWL_VIDEO_SET, evMaxDisplayWindow, sizeof(dest_wnd), &dest_wnd); + + int osd_width, osd_height; + khwl_get_osd_size(&osd_width, &osd_height); + khwl_set_max_osd_wnd(dest_wnd.w, osd_width, dest_wnd.h, osd_height); + + khwl_restoreparams(); + + khwl_set_window(-1, -1, -1, -1, 100, 100, 0, 0); + + } else + { + if (mode != old_mode) + { + val = mode; + khwl_setproperty(KHWL_VIDEO_SET, evTvOutputFormat, sizeof(DWORD), &val); + } + + if (old_mode == evTvOutputFormat_DVI) + khwl_switch_dvi(false); + + KHWL_ADDR_DATA data; + /* + data.Addr = 5; + data.Data = 1; + khwl_setproperty(KHWL_BOARDINFO_SET, ebiPIOAccess, sizeof(data), &data); + */ + + data.Addr = 6; + data.Data = 1; + khwl_setproperty(KHWL_BOARDINFO_SET, ebiPIOAccess, sizeof(data), &data); + } + + old_mode = mode; + + return TRUE; +} + +void khwl_transform(KHWL_VIDEOMODE mode, int *x, int *y) +{ + switch (mode) + { + case KHWL_VIDEOMODE_PANSCAN: + *x = 4 * (*x) / 3; + break; + case KHWL_VIDEOMODE_LETTERBOX: + *y = 3 * (*y) / 4; + break; + case KHWL_VIDEOMODE_HCENTER: + *x = 3 * (*x) / 4; + break; + case KHWL_VIDEOMODE_VCENTER: + *y = 4 * (*y) / 3; + break; + default: + ; + } +} + +void khwl_inversetransform(KHWL_VIDEOMODE mode, int *x, int *y) +{ + switch (mode) + { + case KHWL_VIDEOMODE_PANSCAN: + *x = 3 * (*x) / 4; + break; + case KHWL_VIDEOMODE_LETTERBOX: + *y = 4 * (*y) / 3; + break; + case KHWL_VIDEOMODE_HCENTER: + *x = 4 * (*x) / 3; + break; + case KHWL_VIDEOMODE_VCENTER: + *y = 3 * (*y) / 4; + break; + default: + ; + } +} + +void khwl_getscreensize(int *width, int *height) +{ + KHWL_WINDOW wnd; + khwl_getproperty(KHWL_VIDEO_SET, evMaxDisplayWindow, sizeof(wnd), &wnd); + *width = wnd.w; + *height = wnd.h; +} + +void khwl_transformcoord(KHWL_VIDEOMODE mode, int *x, int *y, int /*width*/, int /*height*/) +{ + // correction for NTSC/PAL +/* + int sw, sh; + khwl_getscreensize(&sw, &sh); + if (width != 0) + *x = *x * sw / width; + if (height != 0) + *y = *y * sh / height; +*/ + // correction for aspect ratio + khwl_transform(mode, x, y); + // correction for black bands (letterbox & hcenter) + *x += dest_wnd.x; + *y += dest_wnd.y; +} + +void khwl_getdestwindow(KHWL_VIDEOMODE mode, KHWL_WINDOW *wnd) +{ + int screenWidth, screenHeight; + khwl_getscreensize(&screenWidth, &screenHeight); + + int displayWidth = screenWidth; + int displayHeight = screenHeight; + + khwl_transform(mode, &displayWidth, &displayHeight); + + wnd->x = (screenWidth - displayWidth) / 2; + wnd->y = (screenHeight - displayHeight) / 2; + wnd->w = displayWidth; + wnd->h = displayHeight; + + //khwl_setproperty(KHWL_VIDEO_SET, evDestinationWindow, sizeof(*wnd), wnd); +} + + +void khwl_setvideomode(KHWL_VIDEOMODE mode, BOOL set_hw) +{ + KHWL_IN_ASPECT_RATIO_TYPE aspect = evInAspectRatio_4x3; + KHWL_OUT_DISPLAY_OPTION_TYPE output = evOutDisplayOption_Normal; + int panscan = 0; + KHWL_ADDR_DATA pio7 = { 7, 0 }; + pio7.Data = 0; + + switch (mode) + { + case KHWL_VIDEOMODE_NONE: + aspect = evInAspectRatio_none; + output = evOutDisplayOption_Normal; + break; + case KHWL_VIDEOMODE_NORMAL: + aspect = evInAspectRatio_4x3; + output = evOutDisplayOption_Normal; + break; + case KHWL_VIDEOMODE_VCENTER: + aspect = evInAspectRatio_4x3; + output = evOutDisplayOption_4x3to16x9_VertCenter; + khwl_wide = 1; + break; + case KHWL_VIDEOMODE_HCENTER: + aspect = evInAspectRatio_4x3; + output = evOutDisplayOption_4x3to16x9_HorzCenter; + khwl_wide = 1; + break; + case KHWL_VIDEOMODE_WIDE: + aspect = evInAspectRatio_16x9; + output = evOutDisplayOption_Normal; + khwl_wide = 1; + break; + case KHWL_VIDEOMODE_PANSCAN: + aspect = evInAspectRatio_16x9; + output = evOutDisplayOption_16x9to4x3_PanScan; + panscan = 1; + break; + case KHWL_VIDEOMODE_LETTERBOX: + aspect = evInAspectRatio_16x9; + output = evOutDisplayOption_16x9to4x3_LetterBox; + break; + } + + if (set_hw && (mode != cur_mode || aspect != cur_aspect || output != cur_output)) + { + cur_mode = mode; + cur_aspect = aspect; + cur_output = output; + + khwl_set_window(-1, -1, -1, -1, 100, 100, 0, 0); +/* + //khwl_getdestwindow(mode, &dest_wnd); + khwl_getproperty(KHWL_VIDEO_SET, evMaxDisplayWindow, sizeof(dest_wnd), &dest_wnd); + + KHWL_IN_ASPECT_RATIO_TYPE a = (aspect == evInAspectRatio_none) ? evInAspectRatio_4x3 : aspect; + khwl_setproperty(KHWL_VIDEO_SET, evInAspectRatio, sizeof(a), &a); + + khwl_setproperty(KHWL_VIDEO_SET, evOutDisplayOption, sizeof(output), &output); + + khwl_setproperty(KHWL_VIDEO_SET, evDestinationWindow, sizeof(dest_wnd), &dest_wnd); + + khwl_setproperty(KHWL_VIDEO_SET, evForcePanScanDefaultSize, sizeof(panscan), &panscan); +*/ + pio7.Data = khwl_wide ? 1 : 0; + khwl_setproperty(KHWL_BOARDINFO_SET, ebiPIOAccess, sizeof(pio7), &pio7); + } + cur_mode = mode; + cur_aspect = aspect; + cur_output = output; +} + +static int old_sw = 720, old_sh = 480; +static KHWL_ZOOMMODE zoom_mode = KHWL_ZOOMMODE_YUV; + +void khwl_set_window_source(int src_width, int src_height) +{ + old_sw = src_width; + old_sh = src_height; +} + +void khwl_set_window_zoom(KHWL_ZOOMMODE zm) +{ + zoom_mode = zm; +} + +void khwl_set_window_frame(int fr_left, int fr_top, int fr_right, int fr_bottom) +{ + frame_left = MIN(MAX(fr_left, 0), 719) & 0x3ffe; + frame_top = MIN(MAX(fr_top, 0), 479) & 0x3ffe; + frame_right = MIN(MAX(fr_right, 0), 719); + frame_bottom = MIN(MAX(fr_bottom, 0), 479); + + if ((frame_right - frame_left + 1) & 1) + frame_right--; + if ((frame_bottom - frame_top + 1) & 1) + frame_bottom--; + + frame_right = MAX(frame_left, frame_right); + frame_bottom = MAX(frame_top, frame_bottom); +} + +void khwl_get_window_frame(int *fr_left, int *fr_top, int *fr_right, int *fr_bottom) +{ + *fr_left = frame_left; + *fr_top = frame_top; + *fr_right = frame_right; + *fr_bottom = frame_bottom; +} + +void khwl_apply_aspect(KHWL_WINDOW *dstwnd, int maxw, int maxh, int src_width, int src_height, int offsx, int offsy) +{ + int dstw = dstwnd->w; + int dsth = dstwnd->h; + + int which = -1; + + // process special DVD modes first + if (cur_output == evOutDisplayOption_4x3to16x9_VertCenter) + { + src_width = 4; + src_height = 3; + which = 3; + } + else if (cur_output == evOutDisplayOption_4x3to16x9_HorzCenter) + { + src_width = 4; + src_height = 3; + which = 2; + } + else if (cur_output == evOutDisplayOption_16x9to4x3_PanScan) + { + src_width = 16; + src_height = 9; + which = 0; + } + else if (cur_output == evOutDisplayOption_16x9to4x3_LetterBox) + { + src_width = 16; + src_height = 9; + which = 1; + } + else if (cur_aspect == evInAspectRatio_4x3) + { + if (3 * src_width < 4 * src_height) + which = 0; + else + which = 1; + } + else if (cur_aspect == evInAspectRatio_16x9) // Wide + { + if (9 * src_width < 16 * src_height) + which = 2; + else + which = 3; + } + + switch (which) + { + case 0: + dstw = 3 * dstw * src_width / (4 * src_height); + dsth = dsth; + break; + case 1: + dstw = dstw; + dsth = 4 * dsth * src_height / (3 * src_width); + break; + case 2: + dstw = 9 * dstw * src_width / (16 * src_height); + dsth = dsth; + break; + case 3: + dstw = dstw; + dsth = 16 * dsth * src_height / (9 * src_width); + break; + default: + ; + } + + dstwnd->x = (maxw - dstw)/2; + dstwnd->y = (maxh - dsth)/2; + + int x = Abs(dstwnd->x), y = Abs(dstwnd->y); + dstwnd->x += MAX(MIN(offsx, x), -x); + dstwnd->y += MAX(MIN(offsy, y), -y); + + dstwnd->w = dstw; + dstwnd->h = dsth; + +} + +void khwl_set_window(int src_width, int src_height, int frame_width, int frame_height, int hscale, int vscale, int offsx, int offsy) +{ + KHWL_WINDOW wnd, dstwnd; + khwl_getproperty(KHWL_VIDEO_SET, evMaxDisplayWindow, sizeof(dstwnd), &dstwnd); + + KHWL_WINDOW curdst, maxdst; + khwl_getproperty(KHWL_VIDEO_SET, evDestinationWindow, sizeof(curdst), &curdst); + khwl_getproperty(KHWL_VIDEO_SET, evMaxDisplayWindow, sizeof(maxdst), &maxdst); + + if (src_width < 0) + src_width = old_sw; + if (src_height < 0) + src_height = old_sh; + + int frame_x = 0, frame_y = 0; + if (frame_width > 0 && frame_height > 0) + { + wnd.x = 0; + wnd.y = 0; + wnd.w = frame_width; + wnd.h = frame_height; + khwl_setproperty(KHWL_VIDEO_SET, evZoomedWindow, sizeof(wnd), &wnd); + wnd.x = 0; + wnd.y = 0; + wnd.w = (frame_width + 15) & ~15; + wnd.h = (frame_height + 15) & ~15; + khwl_setproperty(KHWL_VIDEO_SET, evSourceWindow, sizeof(wnd), &wnd); + + KHWL_YUV_WRITE_PARAMS_TYPE yuvparams; + yuvparams.wWidth = (WORD)wnd.w; + yuvparams.wHeight = (WORD)wnd.h; + yuvparams.YUVFormat = KHWL_YUV_420_UNPACKED; + khwl_setproperty(KHWL_VIDEO_SET, evYUVWriteParams, sizeof(yuvparams), &yuvparams); + } else + { + khwl_getproperty(KHWL_VIDEO_SET, evSourceWindow, sizeof(wnd), &wnd); + frame_x = wnd.x; + frame_y = wnd.y; + frame_width = wnd.w; + frame_height = wnd.h; + } + + if (src_width < 0) + src_width = frame_width; + if (src_height < 0) + src_height = frame_height; + + int sw, sh, sx, sy; + + sx = src_width / 2; + sy = src_height / 2; + sw = src_width; + sh = src_height; + + if (hscale < 10) + hscale = 10; + if (hscale > 400) + hscale = 400; + if (vscale < 10) + vscale = 10; + if (vscale > 400) + vscale = 400; + +#if 0 + msg("* zmode=%d, hscale=%d, vscale=%d, src=(%dx%d).\n", zoom_mode, hscale, vscale, src_width, src_height); +#endif + + if (zoom_mode == KHWL_ZOOMMODE_YUV) + { + if (hscale < 100) + dstwnd.w = dstwnd.w * hscale / 100; + else + sw = src_width * 100 / hscale; + if (vscale < 100) + dstwnd.h = dstwnd.h * vscale / 100; + else + sh = src_height * 100 / vscale; + + khwl_apply_aspect(&dstwnd, maxdst.w, maxdst.h, src_width, src_height, 0, 0); + + wnd.x = sx - sw/2; + wnd.y = sy - sh/2; + wnd.w = sw; + wnd.h = sh; + wnd.x += MAX(MIN(offsx, wnd.x), -wnd.x); + wnd.y += MAX(MIN(offsy, wnd.y), -wnd.y); + wnd.x += (frame_width - src_width)/2; + wnd.y += (frame_height - src_height)/2; + khwl_setproperty(KHWL_VIDEO_SET, evZoomedWindow, sizeof(wnd), &wnd); + + khwl_setproperty(KHWL_VIDEO_SET, evDestinationWindow, sizeof(dstwnd), &dstwnd); + } + else if (zoom_mode == KHWL_ZOOMMODE_ASPECT || zoom_mode == KHWL_ZOOMMODE_DVD) + { +#if 0 + if (zoom_mode == KHWL_ZOOMMODE_DVD && vscale == 100 && hscale == 100) + khwl_setvideomode(cur_mode, TRUE); + else +#endif + { + if (hscale == vscale) // constrain proportions... + { + dstwnd.w = MAX(dstwnd.w * hscale / 100, 64); + dstwnd.h = dstwnd.h * dstwnd.w / maxdst.w; + } else + { + dstwnd.w = MAX(dstwnd.w * hscale / 100, 64); + dstwnd.h = MAX(dstwnd.h * vscale / 100, 64); + } + + // fix aspect ratio for anamorphic modes only + int old_asp = cur_aspect; + int old_out = cur_output; + if (zoom_mode == KHWL_ZOOMMODE_DVD && (cur_mode == KHWL_VIDEOMODE_NORMAL || cur_mode == KHWL_VIDEOMODE_WIDE)) + cur_aspect = evInAspectRatio_none; + + khwl_apply_aspect(&dstwnd, maxdst.w, maxdst.h, src_width, src_height, offsx, offsy); + + cur_aspect = evInAspectRatio_4x3; + khwl_setproperty(KHWL_VIDEO_SET, evInAspectRatio, sizeof(cur_aspect), &cur_aspect); + + cur_output = evOutDisplayOption_Normal; + khwl_setproperty(KHWL_VIDEO_SET, evOutDisplayOption, sizeof(cur_output), &cur_output); + + khwl_setproperty(KHWL_VIDEO_SET, evDestinationWindow, sizeof(dstwnd), &dstwnd); + + int panscan = 0; + khwl_setproperty(KHWL_VIDEO_SET, evForcePanScanDefaultSize, sizeof(panscan), &panscan); + + cur_aspect = old_asp; + cur_output = old_out; + + //cur_mode = KHWL_VIDEOMODE_NONE; + } + } +#if 0 + msg("zoom(%d,%d)=%d,%d %dx%d\n", hscale, vscale, wnd.x, wnd.y, wnd.w, wnd.h); + msg(" frm=%d,%d %dx%d, src=%dx%d,\n", frame_x, frame_y, frame_width, frame_height, src_width, src_height); + + msg("* olddst=%d,%d %dx%d\n", curdst.x, curdst.y, curdst.w, curdst.h); + msg("* maxdst=%d,%d %dx%d\n", maxdst.x, maxdst.y, maxdst.w, maxdst.h); + msg("* dst=%d,%d %dx%d\n", dstwnd.x, dstwnd.y, dstwnd.w, dstwnd.h); +#endif + old_sw = src_width; + old_sh = src_height; +} + +void khwl_set_display_settings(int brightness, int contrast, int saturation) +{ + if (brightness != -1) + { + brightness = MIN(MAX(brightness, 0), 1000); + khwl_setproperty(KHWL_VIDEO_SET, evBrightness, sizeof(brightness), &brightness); + } + if (contrast != -1) + { + contrast = MIN(MAX(contrast, 0), 1000); + khwl_setproperty(KHWL_VIDEO_SET, evContrast, sizeof(contrast), &contrast); + } + if (saturation != -1) + { + saturation = MIN(MAX(saturation, 0), 1000); + khwl_setproperty(KHWL_VIDEO_SET, evSaturation, sizeof(saturation), &saturation); + } +} + +void khwl_get_display_settings(int *brightness, int *contrast, int *saturation) +{ + if (brightness != NULL) + khwl_getproperty(KHWL_VIDEO_SET, evBrightness, sizeof(brightness), brightness); + if (contrast != NULL) + khwl_getproperty(KHWL_VIDEO_SET, evContrast, sizeof(contrast), contrast); + if (saturation != NULL) + khwl_getproperty(KHWL_VIDEO_SET, evSaturation, sizeof(saturation), saturation); +} diff --git a/src/libsp/sp_video.h b/src/libsp/sp_video.h new file mode 100644 index 0000000..86ed3f5 --- /dev/null +++ b/src/libsp/sp_video.h @@ -0,0 +1,96 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - KHWL video functions/enums properties. + * \file sp_video.h + * \author bombur + * \version 0.1 + * \date 14.09.2004 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#ifndef SP_SPVIDEO_H +#define SP_SPVIDEO_H + +#ifdef __cplusplus +extern "C" { +#endif + + +// Video mode +typedef enum +{ + KHWL_VIDEOMODE_NONE = 0, + + KHWL_VIDEOMODE_NORMAL = 1, // source = 4:3, display = 4:3, normal + KHWL_VIDEOMODE_VCENTER, // source = 4:3, display = 16:9, crop top/bottom + KHWL_VIDEOMODE_HCENTER, // source = 4:3, display = 16:9, black band on left/right + KHWL_VIDEOMODE_WIDE, // source = 16:9, display = 16:9, wide + KHWL_VIDEOMODE_PANSCAN, // source = 16:9, display = 4:3, panscan (crop left/right) + KHWL_VIDEOMODE_LETTERBOX, // source = 16:9, display = 4:3, letterbox (black band on top/bottom) + +} KHWL_VIDEOMODE; + +typedef enum +{ + KHWL_ZOOMMODE_YUV = 0, + KHWL_ZOOMMODE_DVD, + KHWL_ZOOMMODE_ASPECT, +} KHWL_ZOOMMODE; + +/// Set display output -1=turn off else = set tvoutput mode +/// and standard. +int khwl_setdisplay(KHWL_TV_OUTPUT_FORMAT_TYPE mode, KHWL_TV_STANDARD_TYPE standard); + +/// Clear YUV screen +int khwl_display_clear(); + +/// Set video mode (change aspect ratio) +/// Use frame width/height if set. +void khwl_setvideomode(KHWL_VIDEOMODE mode, BOOL set_hw); + +/// Transform coord (used for anamorphic modes and overlays) +void khwl_transformcoord(KHWL_VIDEOMODE mode, int *x, int *y, int width, int height); + +/// Get screen width +void khwl_getscreensize(int *width, int *height); + +void khwl_set_window(int src_width, int src_height, int frame_width, int frame_height, + int hscale, int vscale, int offsx, int offsy); + +void khwl_set_window_zoom(KHWL_ZOOMMODE); + +void khwl_set_window_source(int src_width, int src_height); + +/// Set Window Frame in OSD coordinates! +void khwl_set_window_frame(int fr_left, int fr_top, int fr_right, int fr_bottom); +/// Get Window Frame in OSD coordinates! +void khwl_get_window_frame(int *fr_left, int *fr_top, int *fr_right, int *fr_bottom); + +/// Set display settings (values 0..1000) +void khwl_set_display_settings(int brightness, int contrast, int saturation); + +/// Get display settings (values 0..1000) +void khwl_get_display_settings(int *brightness, int *contrast, int *saturation); + +/// Used for start-up procedure only... +void khwl_setwide(BOOL wide); + +#ifdef __cplusplus +} +#endif + +#endif // of SP_SPVIDEO_H diff --git a/src/libsp/win32/dirent.c b/src/libsp/win32/dirent.c new file mode 100644 index 0000000..f3b5c71 --- /dev/null +++ b/src/libsp/win32/dirent.c @@ -0,0 +1,57 @@ +#include +#include +#include "dirent.h" + +struct _finddata_t first; +int wasfirst = 0; + +DIR * opendir(const char *name) +{ + char tmp[4097], dir[4097]; + //int len; + DIR *ret; + wasfirst = 1; + /*strcpy(tmp, name); + len = strlen(tmp); + if (tmp[len-1] != '/' && tmp[len-1] != '\\') + strcat(tmp, "/");*/ + _getcwd(tmp, 1024); + if (name[0] == '/') + { + strcpy(dir, tmp); + strcat(dir, name); + name = dir; + } + _chdir(name); + ret = (DIR *)_findfirst("*.*", &first); + _chdir(tmp); + return ret; +} + +int closedir (DIR *d) +{ + _findclose((long)d); + return 0; +} + +struct _finddata_t *readdir(DIR *d) +{ + if (wasfirst == 1) + { + wasfirst = 0; + return &first; + } + if (_findnext((long)d, &first) != 0) + return NULL; + return &first; +} + +int readlink(char *path, char *buf, size_t size) +{ + path = path; + buf = buf; + size = size; + + return -1; +} + diff --git a/src/libsp/win32/fip.bmp b/src/libsp/win32/fip.bmp new file mode 100644 index 0000000000000000000000000000000000000000..ab8a828f67145cf2fc69d2d00746bbe03c214d5c GIT binary patch literal 46344 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 T+dbyHNtC6^;E!7h3Elqz;6}z( literal 0 HcmV?d00001 diff --git a/src/libsp/win32/include/alloca.h b/src/libsp/win32/include/alloca.h new file mode 100644 index 0000000..60e0279 --- /dev/null +++ b/src/libsp/win32/include/alloca.h @@ -0,0 +1 @@ +#include \ No newline at end of file diff --git a/src/libsp/win32/include/byteswap.h b/src/libsp/win32/include/byteswap.h new file mode 100644 index 0000000..c0ac97c --- /dev/null +++ b/src/libsp/win32/include/byteswap.h @@ -0,0 +1,105 @@ +/* Macros to swap the order of bytes in integer values. + Copyright (C) 1997, 1998 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C 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. + + The GNU C 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 the GNU C Library; see the file COPYING.LIB. If not, + write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + Boston, MA 02111-1307, USA. */ + +//#if !defined _BYTESWAP_H && !defined _NETINET_IN_H +//# error "Never use directly; include instead." +//#endif + +/* Swap bytes in 16 bit value. */ +#define __bswap_constant_16(x) \ + ((((x) >> 8) & 0xff) | (((x) & 0xff) << 8)) + +#if defined __GNUC__ && __GNUC__ >= 2 +# define __bswap_16(x) \ + (__extension__ \ + ({ register unsigned short int __v; \ + if (__builtin_constant_p (x)) \ + __v = __bswap_constant_16 (x); \ + else \ + __asm__ __volatile__ ("rorw $8, %w0" \ + : "=r" (__v) \ + : "0" ((unsigned short int) (x)) \ + : "cc"); \ + __v; })) +#else +/* This is better than nothing. */ +# define __bswap_16(x) __bswap_constant_16 (x) +#endif + + +/* Swap bytes in 32 bit value. */ +#define __bswap_constant_32(x) \ + ((((x) & 0xff000000) >> 24) | (((x) & 0x00ff0000) >> 8) | \ + (((x) & 0x0000ff00) << 8) | (((x) & 0x000000ff) << 24)) + +#if defined __GNUC__ && __GNUC__ >= 2 +/* To swap the bytes in a word the i486 processors and up provide the + `bswap' opcode. On i386 we have to use three instructions. */ +# if !defined __i486__ && !defined __pentium__ && !defined __pentiumpro__ +# define __bswap_32(x) \ + (__extension__ \ + ({ register unsigned int __v; \ + if (__builtin_constant_p (x)) \ + __v = __bswap_constant_32 (x); \ + else \ + __asm__ __volatile__ ("rorw $8, %w0;" \ + "rorl $16, %0;" \ + "rorw $8, %w0" \ + : "=r" (__v) \ + : "0" ((unsigned int) (x)) \ + : "cc"); \ + __v; })) +# else +# define __bswap_32(x) \ + (__extension__ \ + ({ register unsigned int __v; \ + if (__builtin_constant_p (x)) \ + __v = __bswap_constant_32 (x); \ + else \ + __asm__ __volatile__ ("bswap %0" \ + : "=r" (__v) \ + : "0" ((unsigned int) (x))); \ + __v; })) +# endif +#else +# define __bswap_32(x) __bswap_constant_32 (x) +#endif + + +#if defined __GNUC__ && __GNUC__ >= 2 +/* Swap bytes in 64 bit value. */ +# define __bswap_64(x) \ + (__extension__ \ + ({ union { __extension__ unsigned long long int __ll; \ + unsigned long int __l[2]; } __w, __r; \ + __w.__ll = (x); \ + __r.__l[0] = __bswap_32 (__w.__l[1]); \ + __r.__l[1] = __bswap_32 (__w.__l[0]); \ + __r.__ll; })) +#endif + +// [bombur]: hey, win32 can do this also! +#ifdef WIN32 + /* Swap bytes in 64 bit value. */ +# define __bswap_64(x) \ + ((__int64)__bswap_constant_32(x >> 32)) | (((__int64)__bswap_constant_32(x & 0xffffffff) << 32)) +#endif + + +#include "win32-stuff.h" \ No newline at end of file diff --git a/src/libsp/win32/include/dirent.h b/src/libsp/win32/include/dirent.h new file mode 100644 index 0000000..4ac3506 --- /dev/null +++ b/src/libsp/win32/include/dirent.h @@ -0,0 +1,59 @@ +#include +#include + +#ifndef __DIRENT_H__ +#define __DIRENT_H__ + +#define DIR int +#define PATH_MAX 4096 + +#define d_name name +#define dirent _finddata_t + +#define S_IROTH _S_IREAD +#define S_IRGRP 0 +#define S_IRWXU 0 +#define S_IRUSR 0 // 0000400 +#define S_IWUSR 0 // 0000200 +#define S_IXUSR 0 // 0000100 +#define S_IRWXG 0 // 0000070 +#define S_IWGRP 0 // 0000020 +#define S_IXGRP 0 // 0000010 +#define S_IRWXO 0 // 0000007 +#define S_IWOTH 0 // 0000002 +#define S_IXOTH 0 // 0000001 + +#define S_IFBLK 0060000 + +#define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR) +#define S_ISLNK(m) (((m) & S_IFMT) == S_IFLNK) +#define S_ISREG(m) (((m) & S_IFMT) == S_IFREG) +#define S_ISCHR(m) (((m) & S_IFMT) == S_IFCHR) +#define S_ISBLK(m) (((m) & S_IFMT) == S_IFBLK) +#define S_ISFIFO(m) (((m) & S_IFMT) == S_IFIFO) +#define S_ISSOCK(m) (((m) & S_IFMT) == S_IFSOCK) + +#define lstat stat +#define stat64 _stati64 + +#define STAT_INODE(X) (X.st_size * X.st_mtime * X.st_ctime) + +typedef int mode_t; + +#ifdef __cplusplus +extern "C" +{ +#endif + +DIR * opendir(const char *name); +int closedir (DIR *); + +struct _finddata_t *readdir(DIR *); + +int readlink(char *path, char *buf, size_t size); + +#ifdef __cplusplus +} +#endif + +#endif // of __DIRENT_H__ \ No newline at end of file diff --git a/src/libsp/win32/include/endian.h b/src/libsp/win32/include/endian.h new file mode 100644 index 0000000..6e3f608 --- /dev/null +++ b/src/libsp/win32/include/endian.h @@ -0,0 +1,4 @@ +#define __LITTLE_ENDIAN 1234 +#define __BIG_ENDIAN 4321 +#define __BYTE_ORDER __LITTLE_ENDIAN + diff --git a/src/libsp/win32/include/inttypes.h b/src/libsp/win32/include/inttypes.h new file mode 100644 index 0000000..78e5503 --- /dev/null +++ b/src/libsp/win32/include/inttypes.h @@ -0,0 +1,43 @@ +#ifndef _INTTYPES_H +#define _INTTYPES_H 1 + +#ifndef __int8_t_defined +# define __int8_t_defined +typedef signed char int8_t; +typedef short int int16_t; +typedef int int32_t; +typedef __int64 int64_t; +#endif + +/* Unsigned. */ +typedef unsigned char uint8_t; +typedef unsigned short int uint16_t; +#ifndef __uint32_t_defined +typedef unsigned int uint32_t; +# define __uint32_t_defined +#endif +typedef unsigned __int64 uint64_t; + +#ifndef _UINTPTR_T_DEFINED +typedef unsigned long int uintptr_t; +#endif + +# define INT8_MIN (-128) +# define INT16_MIN (-32767-1) +# define INT32_MIN (-2147483647-1) +# define INT64_MIN (-__INT64_C(9223372036854775807)-1) +/* Maximum of signed integral types. */ +# define INT8_MAX (127) +# define INT16_MAX (32767) +# define INT32_MAX (2147483647) +# define INT64_MAX (__INT64_C(9223372036854775807)) + +/* Maximum of unsigned integral types. */ +# define UINT8_MAX (255) +# define UINT16_MAX (65535) +# define UINT32_MAX (4294967295U) +# define UINT64_MAX (__UINT64_C(18446744073709551615)) + +#endif /* inttypes.h */ + +#include "win32-stuff.h" \ No newline at end of file diff --git a/src/libsp/win32/include/linux/cdrom.h b/src/libsp/win32/include/linux/cdrom.h new file mode 100644 index 0000000..4d17615 --- /dev/null +++ b/src/libsp/win32/include/linux/cdrom.h @@ -0,0 +1,178 @@ + +#define _WINSOCKAPI_ +#include +#include +#define NOTIMEVAL + +#define CDROMPAUSE 0x5301 /* Pause Audio Operation */ +#define CDROMRESUME 0x5302 /* Resume paused Audio Operation */ +#define CDROMPLAYMSF 0x5303 /* Play Audio MSF (struct cdrom_msf) */ +#define CDROMPLAYTRKIND 0x5304 /* Play Audio Track/index + (struct cdrom_ti) */ +#define CDROMREADTOCHDR 0x5305 /* Read TOC header + (struct cdrom_tochdr) */ +#define CDROMREADTOCENTRY 0x5306 /* Read TOC entry + (struct cdrom_tocentry) */ +#define CDROMSTOP 0x5307 /* Stop the cdrom drive */ +#define CDROMSTART 0x5308 /* Start the cdrom drive */ +#define CDROMEJECT 0x5309 /* Ejects the cdrom media */ +#define CDROMVOLCTRL 0x530a /* Control output volume + (struct cdrom_volctrl) */ +#define CDROMSUBCHNL 0x530b /* Read subchannel data + (struct cdrom_subchnl) */ +#define CDROMREADMODE2 0x530c /* Read CDROM mode 2 data (2336 Bytes) + (struct cdrom_read) */ +#define CDROMREADMODE1 0x530d /* Read CDROM mode 1 data (2048 Bytes) + (struct cdrom_read) */ +#define CDROMREADAUDIO 0x530e /* (struct cdrom_read_audio) */ +#define CDROMEJECT_SW 0x530f /* enable(1)/disable(0) auto-ejecting */ +#define CDROMMULTISESSION 0x5310 /* Obtain the start-of-last-session + address of multi session disks + (struct cdrom_multisession) */ +#define CDROM_GET_MCN 0x5311 /* Obtain the "Universal Product Code" + if available (struct cdrom_mcn) */ +#define CDROM_GET_UPC CDROM_GET_MCN /* This one is depricated, + but here anyway for compatability */ +#define CDROMRESET 0x5312 /* hard-reset the drive */ +#define CDROMVOLREAD 0x5313 /* Get the drive's volume setting + (struct cdrom_volctrl) */ +#define CDROMREADRAW 0x5314 /* read data in raw mode (2352 Bytes) + + + + +/* Address in MSF format */ +struct cdrom_msf0 +{ + BYTE minute; + BYTE second; + BYTE frame; +}; + +/* Address in either MSF or logical format */ +union cdrom_addr +{ + struct cdrom_msf0 msf; + int lba; +}; + +/* This struct is used by the CDROMPLAYMSF ioctl */ +struct cdrom_msf +{ + BYTE cdmsf_min0; /* start minute */ + BYTE cdmsf_sec0; /* start second */ + BYTE cdmsf_frame0; /* start frame */ + BYTE cdmsf_min1; /* end minute */ + BYTE cdmsf_sec1; /* end second */ + BYTE cdmsf_frame1; /* end frame */ +}; + +/* This struct is used by the CDROMPLAYTRKIND ioctl */ +struct cdrom_ti +{ + BYTE cdti_trk0; /* start track */ + BYTE cdti_ind0; /* start index */ + BYTE cdti_trk1; /* end track */ + BYTE cdti_ind1; /* end index */ +}; + +/* This struct is used by the CDROMREADTOCHDR ioctl */ +struct cdrom_tochdr +{ + BYTE cdth_trk0; /* start track */ + BYTE cdth_trk1; /* end track */ +}; + +/* This struct is used by the CDROMVOLCTRL and CDROMVOLREAD ioctls */ +struct cdrom_volctrl +{ + BYTE channel0; + BYTE channel1; + BYTE channel2; + BYTE channel3; +}; + +/* This struct is used by the CDROMSUBCHNL ioctl */ +struct cdrom_subchnl +{ + BYTE cdsc_format; + BYTE cdsc_audiostatus; + BYTE cdsc_adr: 4; + BYTE cdsc_ctrl: 4; + BYTE cdsc_trk; + BYTE cdsc_ind; + union cdrom_addr cdsc_absaddr; + union cdrom_addr cdsc_reladdr; +}; + + +/* This struct is used by the CDROMREADTOCENTRY ioctl */ +struct cdrom_tocentry +{ + BYTE cdte_track; + BYTE cdte_adr :4; + BYTE cdte_ctrl :4; + BYTE cdte_format; + union cdrom_addr cdte_addr; + BYTE cdte_datamode; +}; + +/* This struct is used by the CDROMREADMODE1, and CDROMREADMODE2 ioctls */ +struct cdrom_read +{ + int cdread_lba; + char *cdread_bufaddr; + int cdread_buflen; +}; + +/* This struct is used by the CDROMREADAUDIO ioctl */ +struct cdrom_read_audio +{ + union cdrom_addr addr; /* frame address */ + BYTE addr_format; /* CDROM_LBA or CDROM_MSF */ + int nframes; /* number of 2352-byte-frames to read at once */ + BYTE *buf; /* frame buffer (size: nframes*2352 bytes) */ +}; + + +#define CD_MINS 74 /* max. minutes per CD, not really a limit */ +#define CD_SECS 60 /* seconds per minute */ +#define CD_FRAMES 75 /* frames per second */ +#define CD_SYNC_SIZE 12 /* 12 sync bytes per raw data frame */ +#define CD_MSF_OFFSET 150 /* MSF numbering offset of first frame */ +#define CD_CHUNK_SIZE 24 /* lowest-level "data bytes piece" */ +#define CD_NUM_OF_CHUNKS 98 /* chunks per frame */ +#define CD_FRAMESIZE_SUB 96 /* subchannel data "frame" size */ +#define CD_HEAD_SIZE 4 /* header (address) bytes per raw data frame */ +#define CD_SUBHEAD_SIZE 8 /* subheader bytes per raw XA data frame */ +#define CD_EDC_SIZE 4 /* bytes EDC per most raw data frame types */ +#define CD_ZERO_SIZE 8 /* bytes zero per yellow book mode 1 frame */ +#define CD_ECC_SIZE 276 /* bytes ECC per most raw data frame types */ +#define CD_FRAMESIZE 2048 /* bytes per frame, "cooked" mode */ +#define CD_FRAMESIZE_RAW 2352 /* bytes per frame, "raw" mode */ +#define CD_FRAMESIZE_RAWER 2646 /* The maximum possible returned bytes */ +/* most drives don't deliver everything: */ +#define CD_FRAMESIZE_RAW1 (CD_FRAMESIZE_RAW-CD_SYNC_SIZE) /*2340*/ +#define CD_FRAMESIZE_RAW0 (CD_FRAMESIZE_RAW-CD_SYNC_SIZE-CD_HEAD_SIZE) /*2336*/ + +#define CD_XA_HEAD (CD_HEAD_SIZE+CD_SUBHEAD_SIZE) /* "before data" part of raw XA frame */ +#define CD_XA_TAIL (CD_EDC_SIZE+CD_ECC_SIZE) /* "after data" part of raw XA frame */ +#define CD_XA_SYNC_HEAD (CD_SYNC_SIZE+CD_XA_HEAD) /* sync bytes + header of XA frame */ + +/* CD-ROM address types (cdrom_tocentry.cdte_format) */ +#define CDROM_LBA 0x01 /* "logical block": first frame is #0 */ +#define CDROM_MSF 0x02 /* "minute-second-frame": binary, not bcd here! */ + +/* bit to tell whether track is data or audio (cdrom_tocentry.cdte_ctrl) */ +#define CDROM_DATA_TRACK 0x04 + +/* The leadout track is always 0xAA, regardless of # of tracks on disc */ +#define CDROM_LEADOUT 0xAA + +/* audio states (from SCSI-2, but seen with other drives, too) */ +#define CDROM_AUDIO_INVALID 0x00 /* audio status not supported */ +#define CDROM_AUDIO_PLAY 0x11 /* audio play operation in progress */ +#define CDROM_AUDIO_PAUSED 0x12 /* audio play operation paused */ +#define CDROM_AUDIO_COMPLETED 0x13 /* audio play successfully completed */ +#define CDROM_AUDIO_ERROR 0x14 /* audio play stopped due to error */ +#define CDROM_AUDIO_NO_STATUS 0x15 /* no current audio status to return */ diff --git a/src/libsp/win32/include/linux/mtd/mtd.h b/src/libsp/win32/include/linux/mtd/mtd.h new file mode 100644 index 0000000..f730238 --- /dev/null +++ b/src/libsp/win32/include/linux/mtd/mtd.h @@ -0,0 +1,41 @@ + +/* [bombur]: this is a fake! */ + +#ifndef __MTD_MTD_H__ +#define __MTD_MTD_H__ + +struct erase_info_user { + unsigned long start; + unsigned long length; +}; + +struct mtd_info_user { + unsigned char type; + unsigned long flags; + unsigned long size; // Total size of the MTD + unsigned long erasesize; + unsigned long oobblock; // Size of OOB blocks (e.g. 512) + unsigned long oobsize; // Amount of OOB data per block (e.g. 16) + unsigned long ecctype; + unsigned long eccsize; +}; + +struct region_info_user { + unsigned long offset; /* At which this region starts, from the beginning of the MTD */ + unsigned long erasesize; /* For this region */ + unsigned long numblocks; /* Number of blocks in this region */ + unsigned long regionindex; +}; + + +#define MEMGETINFO 1 +#define MEMERASE 2 +#define MEMWRITEOOB 3 +#define MEMREADOOB 4 +#define MEMLOCK 5 +#define MEMUNLOCK 6 +#define MEMGETREGIONCOUNT 7 +#define MEMGETREGIONINFO 8 + + +#endif /* __MTD_MTD_H__ */ diff --git a/src/libsp/win32/include/mntent.h b/src/libsp/win32/include/mntent.h new file mode 100644 index 0000000..e69de29 diff --git a/src/libsp/win32/include/pty.h b/src/libsp/win32/include/pty.h new file mode 100644 index 0000000..e69de29 diff --git a/src/libsp/win32/include/pwd.h b/src/libsp/win32/include/pwd.h new file mode 100644 index 0000000..e69de29 diff --git a/src/libsp/win32/include/sched.h b/src/libsp/win32/include/sched.h new file mode 100644 index 0000000..e69de29 diff --git a/src/libsp/win32/include/stdint.h b/src/libsp/win32/include/stdint.h new file mode 100644 index 0000000..917d353 --- /dev/null +++ b/src/libsp/win32/include/stdint.h @@ -0,0 +1,2 @@ + +#include diff --git a/src/libsp/win32/include/strings.h b/src/libsp/win32/include/strings.h new file mode 100644 index 0000000..78caf5b --- /dev/null +++ b/src/libsp/win32/include/strings.h @@ -0,0 +1,3 @@ +#ifndef strcasecmp +#define strcasecmp _stricmp +#endif \ No newline at end of file diff --git a/src/libsp/win32/include/sys/io.h b/src/libsp/win32/include/sys/io.h new file mode 100644 index 0000000..e69de29 diff --git a/src/libsp/win32/include/sys/ioctl.h b/src/libsp/win32/include/sys/ioctl.h new file mode 100644 index 0000000..e69de29 diff --git a/src/libsp/win32/include/sys/mman.h b/src/libsp/win32/include/sys/mman.h new file mode 100644 index 0000000..e69de29 diff --git a/src/libsp/win32/include/sys/mount.h b/src/libsp/win32/include/sys/mount.h new file mode 100644 index 0000000..e69de29 diff --git a/src/libsp/win32/include/sys/param.h b/src/libsp/win32/include/sys/param.h new file mode 100644 index 0000000..e69de29 diff --git a/src/libsp/win32/include/sys/poll.h b/src/libsp/win32/include/sys/poll.h new file mode 100644 index 0000000..e69de29 diff --git a/src/libsp/win32/include/sys/socket.h b/src/libsp/win32/include/sys/socket.h new file mode 100644 index 0000000..e69de29 diff --git a/src/libsp/win32/include/sys/sysinfo.h b/src/libsp/win32/include/sys/sysinfo.h new file mode 100644 index 0000000..e69de29 diff --git a/src/libsp/win32/include/sys/time.h b/src/libsp/win32/include/sys/time.h new file mode 100644 index 0000000..4c15111 --- /dev/null +++ b/src/libsp/win32/include/sys/time.h @@ -0,0 +1,2 @@ +#include "../win32-stuff.h" + diff --git a/src/libsp/win32/include/sys/uio.h b/src/libsp/win32/include/sys/uio.h new file mode 100644 index 0000000..e69de29 diff --git a/src/libsp/win32/include/sys/un.h b/src/libsp/win32/include/sys/un.h new file mode 100644 index 0000000..e69de29 diff --git a/src/libsp/win32/include/sys/wait.h b/src/libsp/win32/include/sys/wait.h new file mode 100644 index 0000000..e69de29 diff --git a/src/libsp/win32/include/termios.h b/src/libsp/win32/include/termios.h new file mode 100644 index 0000000..e69de29 diff --git a/src/libsp/win32/include/unistd.h b/src/libsp/win32/include/unistd.h new file mode 100644 index 0000000..b1e97a0 --- /dev/null +++ b/src/libsp/win32/include/unistd.h @@ -0,0 +1,5 @@ +#include +#include + + +#include "win32-stuff.h" \ No newline at end of file diff --git a/src/libsp/win32/include/win32-stuff.h b/src/libsp/win32/include/win32-stuff.h new file mode 100644 index 0000000..073d60b --- /dev/null +++ b/src/libsp/win32/include/win32-stuff.h @@ -0,0 +1,424 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - win32-unix compatibility header. + * \file win32-stuff.h + * \author bombur + * \version 0.1 + * \date 4.05.2004 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + + +#ifndef __WIN32_STUFF__ +#define __WIN32_STUFF__ + +#include +#include +#include + +/* Standard file descriptors. */ +#define STDIN_FILENO 0 /* Standard input. */ +#define STDOUT_FILENO 1 /* Standard output. */ +#define STDERR_FILENO 2 /* Standard error output. */ + +#define SIGKILL 9 +#define SIGALRM 14 +#define SIGCHLD 17 /* Child status has changed (POSIX). */ + +#define SA_NOCLDSTOP 1 /* Don't send SIGCHLD when children stop. */ +#define SA_NOCLDWAIT 2 /* Don't create zombie on child death. */ +#define SA_SIGINFO 4 /* Invoke signal-catching function with three arguments instead of one. */ + +#define WIFSIGNALED(status) 0 + +#define WNOHANG 1 /* Don't block waiting. */ +#define WUNTRACED 2 /* Report status of stopped children. */ + +#define ONLCR 0000004 +#define ECHO 0000010 + +#define TCSANOW 0 +#define TCSADRAIN 1 +#define TCSAFLUSH 2 + +#define F_DUPFD 0 /* Duplicate file descriptor. */ +#define F_GETFD 1 /* Get file descriptor flags. */ +#define F_SETFD 2 /* Set file descriptor flags. */ +#define F_GETFL 3 /* Get file status flags. */ +#define F_SETFL 4 /* Set file status flags. */ + +#define O_NONBLOCK 04000 + +#define PROT_READ 0x1 /* Page can be read. */ +#define PROT_WRITE 0x2 /* Page can be written. */ +#define PROT_EXEC 0x4 /* Page can be executed. */ +#define PROT_NONE 0x0 /* Page can not be accessed. */ + +#define MAP_SHARED 0x01 /* Share changes. */ +#define MAP_PRIVATE 0x02 /* Changes are private. */ +#define MAP_ANONYMOUS 0x20 /* Don't use a file. */ +#define MAP_ANON MAP_ANONYMOUS + +typedef int __pid_t; +typedef int __uid_t; + +# ifndef __pid_t_defined +typedef __pid_t pid_t; +# define __pid_t_defined +# endif +# ifndef __uid_t_defined +typedef __uid_t uid_t; +# define __uid_t_defined +# endif + + +#ifdef __cplusplus +extern "C" +{ +#endif + +typedef long int __clock_t; + +// from inttypes.h +typedef __int64 ssize_t; + +extern int fcntl (int __fd, int __cmd, ...); +extern void usleep(unsigned int); +extern unsigned int sleep (unsigned int __seconds); +extern unsigned int alarm (unsigned int __seconds); +extern int kill (__pid_t __pid, int __sig); +extern __pid_t waitpid (__pid_t __pid, int *__stat_loc, int __options); +extern __pid_t vfork (void); + +extern void *mmap (void *__addr, int __len, int __prot, int __flags, int __fd, int __offset); +extern int munmap (void *__addr, int __len); + +#ifndef lseek64 +#define lseek64 _lseeki64 +#endif + +void *alloca (unsigned __size); + +extern int fsync(int); +extern int fchdir (int __fd); + +enum __itimer_which + { + /* Timers run in real time. */ + ITIMER_REAL = 0, +#define ITIMER_REAL ITIMER_REAL + /* Timers run only when the process is executing. */ + ITIMER_VIRTUAL = 1, +#define ITIMER_VIRTUAL ITIMER_VIRTUAL + /* Timers run when the process is executing and when + the system is executing on behalf of the process. */ + ITIMER_PROF = 2 +#define ITIMER_PROF ITIMER_PROF + }; + +typedef long suseconds_t; + +///////////////////////////////////////////////////// + +#define POLLIN 0x001 /* There is data to read. */ +#define POLLPRI 0x002 /* There is urgent data to read. */ +#define POLLOUT 0x004 /* Writing now will not block. */ + +struct pollfd +{ + int fd; /* File descriptor to poll. */ + short int events; /* Types of events poller cares about. */ + short int revents; /* Types of events that actually occurred. */ +}; + +typedef unsigned long int nfds_t; + +extern int poll (struct pollfd *__fds, nfds_t __nfds, int __timeout); + +#ifndef NOTIMEVAL + +struct timeval { + time_t tv_sec; /* seconds */ + suseconds_t tv_usec; /* microseconds */ +}; + +struct itimerval + { + /* Value to put into `it_value' when the timer expires. */ + struct timeval it_interval; + /* Time to the next timer expiration. */ + struct timeval it_value; + }; + +struct timezone + { + int tz_minuteswest; /* Minutes west of GMT. */ + int tz_dsttime; /* Nonzero if DST is ever in effect. */ + }; + +#endif + +/* Set the timer WHICH to *NEW. If OLD is not NULL, + set *OLD to the old value of timer WHICH. + Returns 0 on success, -1 on errors. */ +extern int setitimer (enum __itimer_which __which, + const struct itimerval *__new, + struct itimerval *__old); + + +extern int gettimeofday (struct timeval *__tv, + struct timezone *__tz); + + +/////////////////////////////////////////////////////////////// + +typedef unsigned char cc_t; +typedef unsigned int speed_t; +typedef unsigned int tcflag_t; + +#define NCCS 32 +struct termios + { + tcflag_t c_iflag; /* input mode flags */ + tcflag_t c_oflag; /* output mode flags */ + tcflag_t c_cflag; /* control mode flags */ + tcflag_t c_lflag; /* local mode flags */ + cc_t c_line; /* line discipline */ + cc_t c_cc[NCCS]; /* control characters */ + speed_t c_ispeed; /* input speed */ + speed_t c_ospeed; /* output speed */ + }; + +struct winsize + { + unsigned short int ws_row; + unsigned short int ws_col; + unsigned short int ws_xpixel; + unsigned short int ws_ypixel; + }; + +extern int openpty (int *__amaster, int *__aslave, char *__name, + struct termios *__termp, struct winsize *__winp); + +extern int tcgetattr (int __fd, struct termios *__termios_p); + +extern int tcsetattr (int __fd, int __optional_actions, + struct termios *__termios_p); + +/////////////////////////////////////////////////////////////// +typedef void (*__sighandler_t) (int); +# define _SIGSET_NWORDS (1024 / (8 * sizeof (unsigned long int))) +typedef struct + { + unsigned long int __val[_SIGSET_NWORDS]; + } __sigset_t; + +typedef union sigval + { + int sival_int; + void *sival_ptr; + } sigval_t; + +#define __WORDSIZE 32 +# define __SI_MAX_SIZE 128 +# if __WORDSIZE == 64 +# define __SI_PAD_SIZE ((__SI_MAX_SIZE / sizeof (int)) - 4) +# else +# define __SI_PAD_SIZE ((__SI_MAX_SIZE / sizeof (int)) - 3) +# endif + +typedef struct siginfo + { + int si_signo; /* Signal number. */ + int si_errno; /* If non-zero, an errno value associated with + this signal, as defined in . */ + int si_code; /* Signal code. */ + + union + { + int _pad[__SI_PAD_SIZE]; + + /* kill(). */ + struct + { + __pid_t si_pid; /* Sending process ID. */ + __uid_t si_uid; /* Real user ID of sending process. */ + } _kill; + + /* POSIX.1b timers. */ + struct + { + unsigned int _timer1; + unsigned int _timer2; + } _timer; + + /* POSIX.1b signals. */ + struct + { + __pid_t si_pid; /* Sending process ID. */ + __uid_t si_uid; /* Real user ID of sending process. */ + sigval_t si_sigval; /* Signal value. */ + } _rt; + + /* SIGCHLD. */ + struct + { + __pid_t si_pid; /* Which child. */ + __uid_t si_uid; /* Real user ID of sending process. */ + int si_status; /* Exit value or signal. */ + __clock_t si_utime; + __clock_t si_stime; + } _sigchld; + + /* SIGILL, SIGFPE, SIGSEGV, SIGBUS. */ + struct + { + void *si_addr; /* Faulting insn/memory ref. */ + } _sigfault; + + /* SIGPOLL. */ + struct + { + long int si_band; /* Band event for SIGPOLL. */ + int si_fd; + } _sigpoll; + } _sifields; + } siginfo_t; + +struct sigaction + { + /* Signal handler. */ + union + { + __sighandler_t sa_handler; + void (*sa_sigaction) (int, siginfo_t *, void *); + } __sigaction_handler; + # define sa_handler __sigaction_handler.sa_handler + # define sa_sigaction __sigaction_handler.sa_sigaction + + /* Additional set of signals to be blocked. */ + __sigset_t sa_mask; + + /* Special flags. */ + int sa_flags; + + /* Restore handler. */ + void (*sa_restorer) (void); + }; + +# define si_pid _sifields._kill.si_pid + +typedef jmp_buf sigjmp_buf; + +extern int sigaction (int __sig, struct sigaction *__act, struct sigaction *__oact); + +/////////////////////////////////////////////////////////////// + +struct sysinfo { + long uptime; /* Seconds since boot */ + unsigned long loads[3]; /* 1, 5, and 15 minute load averages */ + unsigned long totalram; /* Total usable main memory size */ + unsigned long freeram; /* Available memory size */ + unsigned long sharedram; /* Amount of shared memory */ + unsigned long bufferram; /* Memory used by buffers */ + unsigned long totalswap; /* Total swap space size */ + unsigned long freeswap; /* swap space still available */ + unsigned short procs; /* Number of current processes */ + unsigned short pad; /* Padding needed for m68k */ + unsigned long totalhigh; /* Total high memory size */ + unsigned long freehigh; /* Available high memory size */ + unsigned int mem_unit; /* Memory unit size in bytes */ + char _f[20-2*sizeof(long)-sizeof(int)]; /* Padding: libc5 uses this.. */ +}; + +extern int sysinfo (struct sysinfo *__info); + +/////////////////////////////////////////////////////////////// + +#ifndef _WINSOCKAPI_ + +#undef __NFDBITS +#define __NFDBITS (8 * sizeof(unsigned long)) + +#undef __FD_SETSIZE +#define __FD_SETSIZE 1024 + +#undef __FDSET_LONGS +#define __FDSET_LONGS (__FD_SETSIZE/__NFDBITS) + +typedef struct { + unsigned long fds_bits [__FDSET_LONGS]; +} fd_set; + +#undef __FD_SET +#define __FD_SET(fd, fdsetp) \ + (((fd_set *)fdsetp)->fds_bits[fd >> 5] |= (1<<(fd & 31))) + +#undef __FD_CLR +#define __FD_CLR(fd, fdsetp) \ + (((fd_set *)fdsetp)->fds_bits[fd >> 5] &= ~(1<<(fd & 31))) + +#undef __FD_ISSET +#define __FD_ISSET(fd, fdsetp) \ + ((((fd_set *)fdsetp)->fds_bits[fd >> 5] & (1<<(fd & 31))) != 0) + +#undef __FD_ZERO +#define __FD_ZERO(fdsetp) \ + (memset (fdsetp, 0, sizeof (*(fd_set *)fdsetp))) + + +#define FD_SET(fd,fdsetp) __FD_SET(fd,fdsetp) +#define FD_CLR(fd,fdsetp) __FD_CLR(fd,fdsetp) +#define FD_ISSET(fd,fdsetp) __FD_ISSET(fd,fdsetp) +#define FD_ZERO(fdsetp) __FD_ZERO(fdsetp) + + +int select (int __nfds, fd_set *__readfds, + fd_set *__writefds, fd_set *__exceptfds, + struct timeval *__timeout); + +extern int ioctl (int __fd, unsigned long int __request, ...); +extern int ftruncate(int fd, off_t length); + +#endif // of _WINSOCKAPI_ + +#ifndef strcasecmp +#define strcasecmp _stricmp +#endif +#ifndef strncasecmp +#define strncasecmp _strnicmp +#endif + +#define snprintf _snprintf +#define vsnprintf _vsnprintf + +#define bswap_16(x) __bswap_16 (x) + +/* Return a value with all bytes in the 32 bit argument swapped. */ +#define bswap_32(x) __bswap_32 (x) + +/* Return a value with all bytes in the 64 bit argument swapped. */ +# define bswap_64(x) __bswap_64 (x) + +extern __int64 strtoll (const char *__nptr, char ** __endptr, int __base); +extern unsigned __int64 strtoull (const char * __nptr, char **__endptr, int __base); + +#ifdef __cplusplus +} +#endif + +#endif // of __WIN32_STUFF__ \ No newline at end of file diff --git a/src/libsp/win32/libsp.aps b/src/libsp/win32/libsp.aps new file mode 100644 index 0000000000000000000000000000000000000000..939631ed2318f4b28df9679668ffdcaad0fc6946 GIT binary patch literal 81832 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ℜu)^eRYm55iHUYmRLvB1x}86@p2x0)%kV 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 literal 0 HcmV?d00001 diff --git a/src/libsp/win32/libsp.rc b/src/libsp/win32/libsp.rc new file mode 100644 index 0000000..27a6e55 --- /dev/null +++ b/src/libsp/win32/libsp.rc @@ -0,0 +1,161 @@ +// Microsoft Visual C++ generated resource script. +// +#include "resrc1.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "resource.h" +#include "afxres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// Russian resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_RUS) +#ifdef _WIN32 +LANGUAGE LANG_RUSSIAN, SUBLANG_DEFAULT +#pragma code_page(1251) +#endif //_WIN32 + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resrc1.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""resource.h""\r\n" + "#include ""afxres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + +#endif // Russian resources +///////////////////////////////////////////////////////////////////////////// + + +///////////////////////////////////////////////////////////////////////////// +// 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 + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_EJECT DIALOG 0, 0, 154, 63 +STYLE DS_SETFONT | DS_MODALFRAME | DS_CENTER | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU +CAPTION "Drive ejected..." +FONT 8, "MS Sans Serif" +BEGIN + CONTROL "&Use real device",IDC_RADIO1,"Button",BS_AUTORADIOBUTTON | WS_GROUP | WS_TABSTOP,10,12,65,10 + CONTROL "Emulate &ISO",IDC_RADIO2,"Button",BS_AUTORADIOBUTTON | WS_TABSTOP,10,22,60,10 + CONTROL "Emulate &HDD",IDC_RADIO3,"Button",BS_AUTORADIOBUTTON | WS_TABSTOP,10,32,60,10 + CONTROL "Emulate &DVD",IDC_RADIO4,"Button",BS_AUTORADIOBUTTON | WS_TABSTOP,10,42,70,10 + DEFPUSHBUTTON "Close",IDOK,95,7,50,14 + PUSHBUTTON "Cancel",IDCANCEL,95,24,50,14 + GROUPBOX "Choose medium type:",IDC_STATIC,5,2,80,58 +END + + +///////////////////////////////////////////////////////////////////////////// +// +// Bitmap +// + +IDB_FIP BITMAP "fip.bmp" + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 1,4,1,0 + PRODUCTVERSION 1,4,1,0 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x40004L + FILETYPE 0x1L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "Comments", "www.sigmaplayer.com" + VALUE "CompanyName", "SigmaPlayer Team" + VALUE "FileDescription", "SigmaPlayer firmware emulator" + VALUE "FileVersion", "1, 4, 1, a" + VALUE "InternalName", "init" + VALUE "LegalCopyright", "Copyright © 2004-2010" + VALUE "OriginalFilename", "init.exe" + VALUE "ProductName", "SigmaPlayer emulator" + VALUE "ProductVersion", "1, 4, 1, a" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO +BEGIN + IDD_EJECT, DIALOG + BEGIN + RIGHTMARGIN, 153 + BOTTOMMARGIN, 51 + END +END +#endif // APSTUDIO_INVOKED + +#endif // English (U.S.) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/src/libsp/win32/manifest.xml b/src/libsp/win32/manifest.xml new file mode 100644 index 0000000..d7c922f --- /dev/null +++ b/src/libsp/win32/manifest.xml @@ -0,0 +1,23 @@ + + + +SigmaPlayer emulator + + + + + + diff --git a/src/libsp/win32/resource.h b/src/libsp/win32/resource.h new file mode 100644 index 0000000..eaec160 --- /dev/null +++ b/src/libsp/win32/resource.h @@ -0,0 +1,21 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Developer Studio generated include file. +// Used by libsp.rc +// +#define IDD_EJECT 1101 +#define IDB_FIP 102 +#define IDC_RADIO1 1000 +#define IDC_RADIO2 1001 +#define IDC_RADIO3 1002 +#define IDC_RADIO4 1003 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 107 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1004 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/src/libsp/win32/resrc1.h b/src/libsp/win32/resrc1.h new file mode 100644 index 0000000..d1e8a4d --- /dev/null +++ b/src/libsp/win32/resrc1.h @@ -0,0 +1,15 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Developer Studio generated include file. +// Used by libsp.rc +// + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 105 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1000 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/src/libsp/win32/sp_cdrom.cpp b/src/libsp/win32/sp_cdrom.cpp new file mode 100644 index 0000000..6771840 --- /dev/null +++ b/src/libsp/win32/sp_cdrom.cpp @@ -0,0 +1,536 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - CDROM driver's functions source file (Win32) + * \file sp_cdrom.cpp + * \author bombur + * \version 0.1 + * \date 4.05.2004 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#include + +#pragma warning (disable : 4201) +#include +#pragma warning (default : 4201) + +#include +#include +#include +#include +#include +#include +#include + +#include "sp_misc.h" +#include "sp_msg.h" +#include "sp_cdrom.h" + +#include "resource.h" + +int cdrom_handle = -1; + +int cdrom_last_letter = 'z'; + +static int dvd_letter = 0; +static BOOL cdrom_mounted = FALSE; + +/// CD-ROM fs mount language +static char *def_cdrom_language = "iso8859-1"; +static char *cdrom_language = NULL; + +static CDROM_STATUS cdrom_type = /*CDROM_STATUS_HAS_ISO; */CDROM_STATUS_NODISC; + +static int cdrom_getmediumtype(); + +static char dvdpath[3], dvdpath2[1024]; +static const char *curdvdpath = (const char *)dvdpath; + +static BOOL cdrom_cd_hdd = FALSE, cdrom_hdd = FALSE, cdrom_hdd_root = FALSE; +//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +char *CDROMDIR = "cdrom"; +//char *CDROMDIR = dvdpath; +extern int khwl_handle; + +// taken from linux/cdrom.h (kernel mode) +struct mode_page_header +{ + WORD mode_data_length; + BYTE medium_type; + BYTE reserved1; + BYTE reserved2; + BYTE reserved3; + WORD desc_length; +}; + +struct atapi_capabilities_page +{ + struct mode_page_header header; + BYTE caps[20]; +}; + + +BOOL cdrom_init() +{ + cdrom_deinit(); + cdrom_language = SPstrdup(def_cdrom_language); + + char tmp[256]; + DWORD mask = GetLogicalDrives(); + for (char letter = 'A'; letter <= 'Z'; letter++) + { + if ((mask & (1 << (letter - 'A'))) != 0) + { + sprintf( tmp, "%c:\\", letter ); + if (GetDriveType(tmp) == DRIVE_CDROM) + { + dvd_letter = letter; + break; + } + } + } + + if (dvd_letter != 0) + { + dvdpath[0] = (char)dvd_letter; + dvdpath[1] = ':'; + dvdpath[2] = '\0'; + } else + curdvdpath = NULL; + + // fake for sure... + cdrom_handle = 1; + + return TRUE; +} + +BOOL cdrom_deinit() +{ + if (cdrom_handle == -1) + return FALSE; +// close (cdrom_handle); + cdrom_handle = -1; + SPSafeFree(cdrom_language); + return TRUE; +} + +BOOL cdrom_switch(BOOL ) +{ + return TRUE; +} + +const char *cdrom_getdevicepath(const char *src_path) +{ + if (src_path != NULL) + { + static char tmpp[2014]; + if (_strnicmp(src_path, "/cdrom", 6) == 0) // real device! + sprintf(tmpp, "%s%s", CDROMDIR, src_path + 6); + else if (_strnicmp(src_path, "/hdd", 4) == 0) // real device! + sprintf(tmpp, "%s%s", CDROMDIR, src_path + 4); + else + return curdvdpath; + return tmpp; + } + return curdvdpath; +} + +BOOL CALLBACK eject_dlg(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + static int timer_id = 0; + ULONG wID, code; + switch (uMsg) + { + case WM_INITDIALOG: + CheckDlgButton(hwndDlg, IDC_RADIO1, BST_CHECKED); + timer_id = SetTimer(hwndDlg, NULL, 10, NULL); + return 0; + + case WM_CLOSE: + KillTimer(hwndDlg, timer_id); + EndDialog(hwndDlg, 0); + break; + case WM_COMMAND: + wID = LOWORD(wParam); + code = HIWORD(wParam); + switch(wID) + { + case IDOK: + if (IsDlgButtonChecked(hwndDlg, IDC_RADIO1) == BST_CHECKED) + EndDialog(hwndDlg, 0); + else if (IsDlgButtonChecked(hwndDlg, IDC_RADIO2) == BST_CHECKED) + EndDialog(hwndDlg, 1); + else if (IsDlgButtonChecked(hwndDlg, IDC_RADIO3) == BST_CHECKED) + EndDialog(hwndDlg, 2); + else if (IsDlgButtonChecked(hwndDlg, IDC_RADIO4) == BST_CHECKED) + EndDialog(hwndDlg, 3); + break; + case IDCANCEL: + EndDialog(hwndDlg, -1); + break; + } + break; + // bigfix: strange XP-style behaviour... + case WM_NCCALCSIZE: + break; + case WM_TIMER: + gui_update(); + break; + + default: + return DefWindowProc(hwndDlg, uMsg, wParam, lParam); + } + return 0; +} + +int cdrom_eject(BOOL open) +{ + if (open) + { + cdrom_handle = 1; + gui_update(); + cdrom_hdd = FALSE; + + int ret = DialogBox(GetModuleHandle(NULL), MAKEINTRESOURCE(IDD_EJECT), + (HWND)khwl_handle, eject_dlg); + SetFocus((HWND)khwl_handle); + if (ret == -1) + cdrom_type = CDROM_STATUS_NODISC; + else if (ret == 0) // autodetect from real device + { + cdrom_type = CDROM_STATUS_NODISC; + cdrom_handle = 2; + + ::SetCursor(::LoadCursor(NULL, IDC_WAIT)); + + if (dvd_letter != 0) + { + char tmp[256]; + sprintf(tmp, "\\\\.\\%c:", dvd_letter); + + HANDLE fd = CreateFile(tmp, GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, OPEN_EXISTING, + FILE_FLAG_RANDOM_ACCESS, NULL); + + if (fd == INVALID_HANDLE_VALUE) + fd = CreateFile(tmp, GENERIC_READ, FILE_SHARE_READ, + NULL, OPEN_EXISTING, + FILE_FLAG_RANDOM_ACCESS, NULL); + if (fd != INVALID_HANDLE_VALUE) + { + struct MY_GET_MEDIA_TYPES + { + DWORD DeviceType; // FILE_DEVICE_XXX values + DWORD MediaInfoCount; + DEVICE_MEDIA_INFO MediaInfo[256]; + } mt; + int BytesReturned; + if (DeviceIoControl(fd, IOCTL_STORAGE_GET_MEDIA_TYPES_EX, NULL, 0, + (LPVOID)&mt, (DWORD)sizeof(mt), (LPDWORD) &BytesReturned, NULL) != 0) + { + BOOL mounted = FALSE; + for (DWORD i = 0; i < mt.MediaInfoCount; i++) + { + if ((mt.DeviceType == FILE_DEVICE_DVD || mt.DeviceType == FILE_DEVICE_CD_ROM) && + (mt.MediaInfo[i].DeviceSpecific.DiskInfo.MediaCharacteristics & MEDIA_CURRENTLY_MOUNTED) + == MEDIA_CURRENTLY_MOUNTED) + { + mounted = TRUE; + break; + } + } + if (mounted) + { + MCI_OPEN_PARMS mciOpen; + MCI_STATUS_PARMS mciParams; + mciOpen.lpstrDeviceType = (LPCSTR)MCI_DEVTYPE_CD_AUDIO; + mciSendCommand(0, MCI_OPEN, MCI_OPEN_TYPE | MCI_OPEN_TYPE_ID, (DWORD)&mciOpen); + + mciParams.dwItem = MCI_STATUS_NUMBER_OF_TRACKS; + mciSendCommand(mciOpen.wDeviceID, MCI_STATUS, MCI_STATUS_ITEM, (DWORD)&mciParams); + int numtr = mciParams.dwReturn; + bool has_audio = false, has_data = false; + for (int i = 0; i < numtr; i++) + { + mciParams.dwItem = MCI_CDA_STATUS_TYPE_TRACK; + mciParams.dwTrack = i + 1; + mciSendCommand(mciOpen.wDeviceID, MCI_STATUS, MCI_STATUS_ITEM | MCI_TRACK, (DWORD)&mciParams); + if (mciParams.dwReturn == MCI_CDA_TRACK_AUDIO) + has_audio = true; + else + has_data = true; + } + mciSendCommand(mciOpen.wDeviceID, MCI_CLOSE, 0, 0); + + curdvdpath = dvdpath; + CDROMDIR = dvdpath; + + DIR *dir = opendir(dvdpath); + struct dirent *entry; + while ((entry = readdir(dir)) != NULL) + { + if (_stricmp(entry->name, "VIDEO_TS") == 0) + { + cdrom_type = CDROM_STATUS_HAS_DVD; + break; + } + } + closedir(dir); + // one more chance... + if (cdrom_type != CDROM_STATUS_HAS_DVD) + { + dir = opendir(dvdpath); + struct dirent *entry; + while ((entry = readdir(dir)) != NULL) + { + sprintf(dvdpath2, "%s/%s", dvdpath, entry->name); + DIR *dir2 = opendir(dvdpath2); + struct dirent *entry2; + while ((entry2 = readdir(dir2)) != NULL) + { + if (_stricmp(entry2->name, "VIDEO_TS") == 0) + { + cdrom_type = CDROM_STATUS_HAS_DVD; + break; + } + } + closedir(dir2); + if (cdrom_type == CDROM_STATUS_HAS_DVD) + { + curdvdpath = dvdpath2; + break; + } + } + closedir(dir); + } + + if (cdrom_type != CDROM_STATUS_HAS_DVD) + { + if (has_audio && !has_data) + cdrom_type = CDROM_STATUS_HAS_AUDIO; + else if (has_audio && has_data) + cdrom_type = CDROM_STATUS_HAS_MIXED; + else + cdrom_type = CDROM_STATUS_HAS_ISO; + } + } + } else + { + //DWORD err = GetLastError(); + } + + + CloseHandle(fd); + } + } + ::SetCursor(::LoadCursor(NULL, IDC_ARROW)); + } + else if (ret == 1) + { + cdrom_hdd = FALSE; + CDROMDIR = "cdrom"; + cdrom_type = CDROM_STATUS_HAS_ISO; + } + else if (ret == 2) + { + cdrom_hdd = TRUE; + CDROMDIR = "cdrom/hdd"; + cdrom_type = CDROM_STATUS_HAS_ISO; + } + else if (ret == 3) + { + cdrom_hdd = FALSE; + curdvdpath = "cdrom/dvd"; + cdrom_type = CDROM_STATUS_HAS_DVD; + } + } else + { + gui_update(); + } + + return -1; +} + + +int cdrom_mount(char *language, BOOL /*cd_only*/) +{ + int ret = 0; + char iocharset[1024]; + if (language == NULL) + language = cdrom_language; + + BOOL lang_changed = strcasecmp(cdrom_language, language) != 0; + if (!cdrom_mounted || lang_changed) + { + sprintf(iocharset, "iocharset=%s", language); + //int rmnt = ((cdrom_mounted && lang_changed) ? MS_REMOUNT : 0); + //ret = mount("/dev/cdroms/cdrom0", "/cdrom", "iso9660", MS_RDONLY | MS_NOSUID | MS_NODEV | rmnt, iocharset); + + msg("CD-ROM: mount with %s.\n", iocharset); + } + cdrom_mounted = TRUE; + + if (lang_changed) + { + SPSafeFree(cdrom_language); + cdrom_language = SPstrdup(language); + } + + return ret; +} + +int cdrom_umount() +{ + msg("CD-ROM: umount.\n"); + cdrom_mounted = FALSE; + //return umount("/cdrom"); + return 0; +} + +BOOL cdrom_ismounted() +{ + return cdrom_mounted; +} + + +CDROM_STATUS cdrom_getstatus(BOOL *) +{ + return cdrom_type; +} + +BOOL cdrom_isready() +{ + return TRUE; +} + +// [bombur]: this was die hard! +int cdrom_getmediumtype() +{ +/* + cdrom_generic_command cmd; + struct request_sense sense; + struct atapi_capabilities_page caps; + + if (cdrom_handle != -1) + { + memset(&cmd, 0, sizeof(cmd)); + cmd.buffer = (BYTE *)∩︀ + cmd.buflen = sizeof(caps); + cmd.cmd[0] = GPCMD_MODE_SENSE_10; + cmd.cmd[2] = GPMODE_CAPABILITIES_PAGE; + cmd.cmd[8] = cmd.buflen & 0xff; + cmd.sense = &sense; + cmd.data_direction = CGC_DATA_READ; + ioctl(cdrom_handle, CDROM_SEND_PACKET, &cmd); + + return caps.header.medium_type; + } +*/ + return 0; +} + +int cdrom_stat(const char *path, struct stat64 *s) +{ + if (_strnicmp(path, "/cdrom", 6) == 0) // real device! + { + char tmpp[2014]; + sprintf(tmpp, "%s%s", CDROMDIR, path + 6); + return _stati64(tmpp, s); + } + if (_strnicmp(path, "/hdd", 4) == 0) // real device! + { + char tmpp[2014]; + sprintf(tmpp, "%s%s", CDROMDIR, path + 4); + return _stati64(tmpp, s); + } + if (path[0] == '/') + { + path++; + } + return _stati64(path, s); +} + +DIR *cdrom_opendir(const char *path) +{ + cdrom_hdd_root = FALSE; + if (_strnicmp(path, "/cdrom", 6) == 0) // real device! + { + char tmpp[2014]; + sprintf(tmpp, "%s%s", CDROMDIR, path + 6); + return opendir(tmpp); + } + if (_strnicmp(path, "/hdd", 4) == 0) // real device! + { + char tmpp[2014]; + sprintf(tmpp, "%s%s", CDROMDIR, path + 4); + if (path[4] == '/' && path[5] == '\0') + cdrom_hdd_root = TRUE; + return opendir(tmpp); + } + return opendir(path); +} + +int cdrom_open(const char *fname, int flags) +{ + if (_strnicmp(fname, "/cdrom", 6) == 0) // real device! + { + char tmpp[2014]; + sprintf(tmpp, "%s%s", CDROMDIR, fname + 6); + return open(tmpp, flags | O_BINARY); + } + if (_strnicmp(fname, "/hdd", 4) == 0) // real device! + { + char tmpp[2014]; + sprintf(tmpp, "%s%s", CDROMDIR, fname + 4); + return open(tmpp, flags | O_BINARY); + } + return -1; +} + +char *cdrom_getrealpath(const char *path) +{ + static char tmpp[4096]; + if (memcmp(path, "/", 2) == 0) + { + if (cdrom_cd_hdd) + return (char *)path; + if (cdrom_hdd) + strcpy(tmpp, "/hdd"); + else + strcpy(tmpp, "/cdrom"); + strcat(tmpp, path); + return tmpp; + } + return (char *)path; +} + +struct dirent *cdrom_readdir(DIR *dir) +{ + struct dirent *d = readdir(dir); + if (d == NULL) + return NULL; + // fast mount-check (exclude unmounted folders) + if (cdrom_hdd_root && d->name[0] > cdrom_last_letter && d->name[1] == '\0') + return NULL; + return d; +} + +int cdrom_closedir(DIR *dir) +{ + return closedir(dir); +} \ No newline at end of file diff --git a/src/libsp/win32/sp_css.cpp b/src/libsp/win32/sp_css.cpp new file mode 100644 index 0000000..ec6a176 --- /dev/null +++ b/src/libsp/win32/sp_css.cpp @@ -0,0 +1,229 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - CSS auth. functions source file (Win32) + * \file sp_css.cpp + * \author bombur + * \version 0.1 + * \date 4.07.2004 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include + +#include "sp_misc.h" +#include "sp_khwl.h" +#include "sp_css.h" + +typedef unsigned char uint8_t; + +#include + +void CryptKey( int i_key_type, int i_variant, + unsigned char const *p_challenge, unsigned char *p_key ) +{ + /* Permutation table for challenge */ + unsigned char pp_perm_challenge[3][10] = + { { 1, 3, 0, 7, 5, 2, 9, 6, 4, 8 }, + { 6, 1, 9, 3, 8, 5, 7, 4, 0, 2 }, + { 4, 0, 3, 5, 7, 2, 8, 6, 1, 9 } }; + + /* Permutation table for variant table for key2 and buskey */ + unsigned char pp_perm_variant[2][32] = + { { 0x0a, 0x08, 0x0e, 0x0c, 0x0b, 0x09, 0x0f, 0x0d, + 0x1a, 0x18, 0x1e, 0x1c, 0x1b, 0x19, 0x1f, 0x1d, + 0x02, 0x00, 0x06, 0x04, 0x03, 0x01, 0x07, 0x05, + 0x12, 0x10, 0x16, 0x14, 0x13, 0x11, 0x17, 0x15 }, + { 0x12, 0x1a, 0x16, 0x1e, 0x02, 0x0a, 0x06, 0x0e, + 0x10, 0x18, 0x14, 0x1c, 0x00, 0x08, 0x04, 0x0c, + 0x13, 0x1b, 0x17, 0x1f, 0x03, 0x0b, 0x07, 0x0f, + 0x11, 0x19, 0x15, 0x1d, 0x01, 0x09, 0x05, 0x0d } }; + + unsigned char p_variants[32] = + { 0xB7, 0x74, 0x85, 0xD0, 0xCC, 0xDB, 0xCA, 0x73, + 0x03, 0xFE, 0x31, 0x03, 0x52, 0xE0, 0xB7, 0x42, + 0x63, 0x16, 0xF2, 0x2A, 0x79, 0x52, 0xFF, 0x1B, + 0x7A, 0x11, 0xCA, 0x1A, 0x9B, 0x40, 0xAD, 0x01 }; + + /* The "secret" key */ + unsigned char p_secret[5] = { 0x55, 0xD6, 0xC4, 0xC5, 0x28 }; + + unsigned char p_bits[30], p_scratch[10], p_tmp1[5], p_tmp2[5]; + unsigned char i_lfsr0_o; /* 1 bit used */ + unsigned char i_lfsr1_o; /* 1 bit used */ + unsigned char i_css_variant, i_cse, i_index, i_combined, i_carry; + unsigned char i_val = 0; + unsigned long i_lfsr0, i_lfsr1; + int i_term = 0; + int i_bit; + int i; + + for (i = 9; i >= 0; --i) + p_scratch[i] = p_challenge[pp_perm_challenge[i_key_type][i]]; + + i_css_variant = (unsigned char)(( i_key_type == 0 ) ? i_variant : + pp_perm_variant[i_key_type-1][i_variant]); + + /* + * This encryption engine implements one of 32 variations + * one the same theme depending upon the choice in the + * variant parameter (0 - 31). + * + * The algorithm itself manipulates a 40 bit input into + * a 40 bit output. + * The parameter 'input' is 80 bits. It consists of + * the 40 bit input value that is to be encrypted followed + * by a 40 bit seed value for the pseudo random number + * generators. + */ + + /* Feed the secret into the input values such that + * we alter the seed to the LFSR's used above, then + * generate the bits to play with. + */ + for( i = 5 ; --i >= 0 ; ) + { + p_tmp1[i] = (unsigned char)(p_scratch[5 + i] ^ p_secret[i] ^ p_crypt_tab2[i]); + } + + /* + * We use two LFSR's (seeded from some of the input data bytes) to + * generate two streams of pseudo-random bits. These two bit streams + * are then combined by simply adding with carry to generate a final + * sequence of pseudo-random bits which is stored in the buffer that + * 'output' points to the end of - len is the size of this buffer. + * + * The first LFSR is of degree 25, and has a polynomial of: + * x^13 + x^5 + x^4 + x^1 + 1 + * + * The second LSFR is of degree 17, and has a (primitive) polynomial of: + * x^15 + x^1 + 1 + * + * I don't know if these polynomials are primitive modulo 2, and thus + * represent maximal-period LFSR's. + * + * + * Note that we take the output of each LFSR from the new shifted in + * bit, not the old shifted out bit. Thus for ease of use the LFSR's + * are implemented in bit reversed order. + * + */ + + /* In order to ensure that the LFSR works we need to ensure that the + * initial values are non-zero. Thus when we initialise them from + * the seed, we ensure that a bit is set. + */ + i_lfsr0 = ( p_tmp1[0] << 17 ) | ( p_tmp1[1] << 9 ) | + (( p_tmp1[2] & ~7 ) << 1 ) | 8 | ( p_tmp1[2] & 7 ); + i_lfsr1 = ( p_tmp1[3] << 9 ) | 0x100 | p_tmp1[4]; + + i_index = sizeof(p_bits); + i_carry = 0; + + do + { + for( i_bit = 0, i_val = 0 ; i_bit < 8 ; ++i_bit ) + { + + i_lfsr0_o = (unsigned char)(( ( i_lfsr0 >> 24 ) ^ ( i_lfsr0 >> 21 ) ^ + ( i_lfsr0 >> 20 ) ^ ( i_lfsr0 >> 12 ) ) & 1); + i_lfsr0 = ( i_lfsr0 << 1 ) | i_lfsr0_o; + + i_lfsr1_o = (unsigned char)(( ( i_lfsr1 >> 16 ) ^ ( i_lfsr1 >> 2 ) ) & 1); + i_lfsr1 = ( i_lfsr1 << 1 ) | i_lfsr1_o; + + i_combined = (unsigned char)(!i_lfsr1_o + i_carry + !i_lfsr0_o); + /* taking bit 1 */ + i_carry = (unsigned char)(( i_combined >> 1 ) & 1); + i_val |= ( i_combined & 1 ) << i_bit; + } + + p_bits[--i_index] = i_val; + } while( i_index > 0 ); + + /* This term is used throughout the following to + * select one of 32 different variations on the + * algorithm. + */ + i_cse = (unsigned char)(p_variants[i_css_variant] ^ p_crypt_tab2[i_css_variant]); + + /* Now the actual blocks doing the encryption. Each + * of these works on 40 bits at a time and are quite + * similar. + */ + i_index = 0; + for( i = 5, i_term = 0 ; --i >= 0 ; i_term = p_scratch[i] ) + { + i_index = (unsigned char)(p_bits[25 + i] ^ p_scratch[i]); + i_index = (unsigned char)(p_crypt_tab1[i_index] ^ ~p_crypt_tab2[i_index] ^ i_cse); + + p_tmp1[i] = (unsigned char)(p_crypt_tab2[i_index] ^ p_crypt_tab3[i_index] ^ i_term); + } + p_tmp1[4] ^= p_tmp1[0]; + + for( i = 5, i_term = 0 ; --i >= 0 ; i_term = p_tmp1[i] ) + { + i_index = (unsigned char)(p_bits[20 + i] ^ p_tmp1[i]); + i_index = (unsigned char)(p_crypt_tab1[i_index] ^ ~p_crypt_tab2[i_index] ^ i_cse); + + p_tmp2[i] = (unsigned char)(p_crypt_tab2[i_index] ^ p_crypt_tab3[i_index] ^ i_term); + } + p_tmp2[4] ^= p_tmp2[0]; + + for( i = 5, i_term = 0 ; --i >= 0 ; i_term = p_tmp2[i] ) + { + i_index = (unsigned char)(p_bits[15 + i] ^ p_tmp2[i]); + i_index = (unsigned char)(p_crypt_tab1[i_index] ^ ~p_crypt_tab2[i_index] ^ i_cse); + i_index = (unsigned char)(p_crypt_tab2[i_index] ^ p_crypt_tab3[i_index] ^ i_term); + + p_tmp1[i] = (unsigned char)(p_crypt_tab0[i_index] ^ p_crypt_tab2[i_index]); + } + p_tmp1[4] ^= p_tmp1[0]; + + for( i = 5, i_term = 0 ; --i >= 0 ; i_term = p_tmp1[i] ) + { + i_index = (unsigned char)(p_bits[10 + i] ^ p_tmp1[i]); + i_index = (unsigned char)(p_crypt_tab1[i_index] ^ ~p_crypt_tab2[i_index] ^ i_cse); + + i_index = (unsigned char)(p_crypt_tab2[i_index] ^ p_crypt_tab3[i_index] ^ i_term); + + p_tmp2[i] = (unsigned char)(p_crypt_tab0[i_index] ^ p_crypt_tab2[i_index]); + } + p_tmp2[4] ^= p_tmp2[0]; + + for( i = 5, i_term = 0 ; --i >= 0 ; i_term = p_tmp2[i] ) + { + i_index = (unsigned char)(p_bits[5 + i] ^ p_tmp2[i]); + i_index = (unsigned char)(p_crypt_tab1[i_index] ^ ~p_crypt_tab2[i_index] ^ i_cse); + + p_tmp1[i] = (unsigned char)(p_crypt_tab2[i_index] ^ p_crypt_tab3[i_index] ^ i_term); + } + p_tmp1[4] ^= p_tmp1[0]; + + for(i = 5, i_term = 0 ; --i >= 0 ; i_term = p_tmp1[i] ) + { + i_index = (unsigned char)(p_bits[i] ^ p_tmp1[i]); + i_index = (unsigned char)(p_crypt_tab1[i_index] ^ ~p_crypt_tab2[i_index] ^ i_cse); + + p_key[i] = (unsigned char)(p_crypt_tab2[i_index] ^ p_crypt_tab3[i_index] ^ i_term); + } + + return; +} diff --git a/src/libsp/win32/sp_css.h b/src/libsp/win32/sp_css.h new file mode 100644 index 0000000..ce559fd --- /dev/null +++ b/src/libsp/win32/sp_css.h @@ -0,0 +1,32 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - CSS auth. functions header (Win32) + * \file sp_css.h + * \author bombur + * \version 0.1 + * \date 4.07.2004 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#ifndef SP_CSS_WIN32 +#define SP_CSS_WIN32 + + +void CryptKey( int i_key_type, int i_variant, + unsigned char const *p_challenge, unsigned char *p_key ); + +#endif // of SP_CSS_WIN32 diff --git a/src/libsp/win32/sp_eeprom.cpp b/src/libsp/win32/sp_eeprom.cpp new file mode 100644 index 0000000..c04a881 --- /dev/null +++ b/src/libsp/win32/sp_eeprom.cpp @@ -0,0 +1,61 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - EEPROM interface functions source file + * For Technosonic-compatible players ('MP') + * \file sp_eeprom.cpp + * \author bombur + * \version 0.1 + * \date 4.05.2004 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#include +#include +#include + +#include "sp_misc.h" +#include "sp_khwl.h" +#include "sp_eeprom.h" + +BOOL eeprom_set_value(DWORD addr, DWORD val, int size) +{ + KHWL_ADDR_DATA data; + /// \warning: not usual byte order used! + for (int i = 0; i < size; i++) + { + data.Addr = addr + i; + data.Data = (val >> (i * 8)) & 0xff; + khwl_setproperty(KHWL_EEPROM_SET, eEepromAccess, sizeof(data), &data); + } + return TRUE; +} + +DWORD eeprom_get_value(DWORD addr, int size) +{ + KHWL_ADDR_DATA data; + DWORD val = 0; + /// \warning: not usual byte order used! + for (int i = 0; i < size; i++) + { + data.Addr = addr + i; + data.Data = 0; + khwl_getproperty(KHWL_EEPROM_SET, eEepromAccess, sizeof(data), &data); + val |= (data.Data & 0xff) << (i * 8); + } + return val; +} + diff --git a/src/libsp/win32/sp_fip.cpp b/src/libsp/win32/sp_fip.cpp new file mode 100644 index 0000000..c8613de --- /dev/null +++ b/src/libsp/win32/sp_fip.cpp @@ -0,0 +1,167 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - FIP interface functions source file (Win32) + * \file sp_fip.c + * \author bombur + * \version 0.1 + * \date 4.05.2004 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include + +#include "sp_misc.h" +#include "sp_fip.h" + +static unsigned char char_table[] = " -0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz-\1"; +unsigned char fipram[12]; +static unsigned char led_table[] = +{ + 0x00,0x10,0xEE,0x48,0xD6,0xDA,0x78,0xBA,0xBE,0xC8,0xFE,0xFA,0xFC,0xFE,0xA6,0xEE, + 0xB6,0xB4,0xBE,0x7C,0x48,0x4A,0x7C,0x26,0xEC,0xEC,0xEE,0xF4,0xEE,0xFC,0xBA,0xC8, + 0x6E,0x6E,0x6E,0x6C,0x78,0x82,0x02,0x1E,0x3E,0x16,0x5E,0xF6,0xB4,0xFA,0x3C,0x08, + 0x0A,0x2C,0x26,0x1C,0x1C,0x1E,0xF4,0xF8,0x14,0xBA,0x36,0x0E,0x0E,0x0E,0x0C,0x78, + 0x12,0x10,0x00 +}; +static unsigned char led_special[] = +{ + 0x01, 0x07, 0x01, 0x06, 0x03, 0x07, 0x02, 0x07, 0x05, 0x07, 0x04, 0x07, 0x07, 0x07, 0x07, 0x06, + 0x07, 0x05, 0x07, 0x04, 0x07, 0x03, 0x07, 0x02, 0x07, 0x01, 0x07, 0x00, 0x09, 0x07, 0x09, 0x06, + 0x09, 0x05, 0x09, 0x04, 0x09, 0x03, 0x09, 0x02, 0x09, 0x01, 0x09, 0x00, 0x08, 0x07, 0x08, 0x06, + 0x08, 0x05, 0x08, 0x04, 0x08, 0x03, 0x08, 0x02 +}; + +int fip_handle = -1; + +int fip_lastkey = 0; + +#include "../MP/sp_fip_codes-technosonic.h" + +extern bool khwl_msgloop(); +extern int khwl_handle; + +BOOL fip_init(BOOL /*applymodule*/) +{ + fip_deinit(); + fip_handle = 1; + return TRUE; +} + +BOOL fip_deinit() +{ + if (fip_handle == -1) + return FALSE; + + fip_handle = -1; + return TRUE; +} + +BOOL fip_clear() +{ + int i; + + if (fip_handle == -1) + return FALSE; + // clear FIP + for (i = 0; i < 12; i++) + { + fipram[i] = 0; + } + return TRUE; +} + +BOOL fip_write_char(int ch, int pos) +{ + int lr; + + if (pos < 1 || pos - 1 > 6 || fip_handle == -1) + return FALSE; + ch &= 255; + for (lr = 0; lr < 66; lr++) + if (char_table[lr] == ch) + break; + + fipram[pos-1] &= 128; + fipram[pos-1] |= led_table[lr] >> 1; + + return TRUE; +} + +BOOL fip_write_special_char(int pos, int shift, BOOL onoff) +{ + BYTE ch = fipram[pos]; + if (onoff) + fipram[pos] = (unsigned char)(ch | (1 << shift)); + else + fipram[pos] = (unsigned char)(ch & ~(1 << shift)); + if (fipram[pos] == ch) + return TRUE; + + return TRUE; +} + +BOOL fip_write_special(int id, BOOL onoff) +{ + if (id < 0 || id > 27) + return FALSE; + fip_write_special_char(led_special[id*2], led_special[id*2+1], onoff); + InvalidateRect((HWND)khwl_handle, NULL, FALSE); + UpdateWindow((HWND)khwl_handle); + return TRUE; +} + +BOOL fip_get_special(int id) +{ + if (id < 0 || id > 27) + return FALSE; + int pos = led_special[id*2]; + int shift = led_special[id*2+1]; + return (fipram[pos] >> shift) & 1; +} + +BOOL fip_write_string(const char *str) +{ + int i, len; + if (str == NULL || fip_handle == -1) + return FALSE; + len = strlen(str); + if (len > 7) + len = 7; + for (i = 0; i < 7; i++) + fip_write_char(i < len ? str[len - i - 1] : ' ', i + 1); + + InvalidateRect((HWND)khwl_handle, NULL, FALSE); + UpdateWindow((HWND)khwl_handle); + + return TRUE; +} + +int fip_read_button(BOOL ) +{ + if (fip_handle == -1) + return 0; + + if (!khwl_msgloop()) + return -1; + + int ret = fip_lastkey; + fip_lastkey = 0; + return ret; +} diff --git a/src/libsp/win32/sp_i2c.cpp b/src/libsp/win32/sp_i2c.cpp new file mode 100644 index 0000000..11778ec --- /dev/null +++ b/src/libsp/win32/sp_i2c.cpp @@ -0,0 +1,39 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - I2C data transfer functions source file. + * Win32 version (stub). + * \file sp_i2c.cpp + * \author bombur + * \version 0.1 + * \date 1.02.2005 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#include "sp_misc.h" +#include "sp_io.h" +#include "sp_i2c.h" + +BOOL i2c_data_in(BYTE addr, BYTE idx, BYTE *data, int num) +{ + return TRUE; +} + +BOOL i2c_data_out(BYTE addr, BYTE idx, BYTE *data, int num) +{ + return TRUE; +} + diff --git a/src/libsp/win32/sp_khwl.cpp b/src/libsp/win32/sp_khwl.cpp new file mode 100644 index 0000000..df741fe --- /dev/null +++ b/src/libsp/win32/sp_khwl.cpp @@ -0,0 +1,1350 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - KHWL interface functions source file (Win32) + * \file sp_khwl.cpp + * \author bombur + * \version 0.1 + * \date 4.07.2004 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include +#include + +#include "sp_misc.h" +#include "sp_khwl.h" +#include "sp_mpeg.h" +#include "sp_fip.h" +#include "sp_css.h" + +#include "resource.h" + +//#define DUMP_VIDEO_FRAMES_ORDER +//#define DUMP_VIDEO +//#define DUMP_VIDEO_PACKET + +int khwl_handle = -1; + +static HANDLE khwl_parser = NULL; +static DWORD khwl_parser_id = (DWORD)-1; +static HANDLE khwl_parser_mutex = NULL; +static int khwl_rate = 24000, khwl_scale = 1000; + +static int khwl_audio_format = 0; +static int khwl_audio_sample_rate = 0, khwl_audio_bits = 0, khwl_audio_channels = 0; +static LONGLONG khwl_audio_pts_per_Mb = 0; + +// here we store the data +static MpegPlayStruct mpeg_playstruct_storage[4]; + +/// Muxed media packets +MpegPlayStruct *MPEG_PLAY_STRUCT = &mpeg_playstruct_storage[0]; +/// Video packets +MpegPlayStruct *MPEG_VIDEO_STRUCT = &mpeg_playstruct_storage[1]; +/// Audio packets +MpegPlayStruct *MPEG_AUDIO_STRUCT = &mpeg_playstruct_storage[2]; +/// Subpicture packets +MpegPlayStruct *MPEG_SPU_STRUCT = &mpeg_playstruct_storage[3]; + +BYTE *BUF_BASE = NULL; + +const int yoffset = 110; + +const int osd_width = 640, osd_height = 480; + +static char *wndtitle = "SigmaPlayer :: version " __DATE__ " " __TIME__ " :: "; + +static struct // BITMAPINFO with 16 colors +{ + BITMAPINFOHEADER bmiHeader; + RGBQUAD bmiColors[256]; +} bi; + +static HDC hMemDC = NULL; +static HBITMAP hSrcBitmap = NULL; +static HDC curDC = NULL; +static ULONGLONG khwl_pts = 0; + +static BYTE tmp_UV[720 * 576/2]; +static BYTE tmp_Y[720 * 576]; +static int yuv_width = 720, yuv_height = 480; +static BOOL yuv_dirty = FALSE; +static BYTE tv_mask[osd_width * osd_height]; +static int tv_brightness = 500, tv_contrast = 500, tv_saturation = 500; + +static bool step_mode = false, use_tvmask = false; + +static int fip_button = -1; + +static int freq_reg = ((1 << 8) | (34 << 2) | 0x02); + + +#ifdef DUMP_VIDEO_FRAMES_ORDER +#include "sp_msg.h" +#endif + + +void CALLBACK OSDUpdate( UINT /*uID*/, UINT /*uMsg*/, DWORD /*dwUser*/, + DWORD /*dw1*/, DWORD /*dw2*/ ) +{ + khwl_osd_update(); +} + +bool khwl_msgloop(); + +int khwl_nextfeedpacket(MpegPlayStruct *feed) +{ + MpegPlayStruct *from = MPEG_PLAY_STRUCT; + MpegPacket *feedin = feed->in; + if (feedin == NULL) + return FALSE; + + //!!!!!!!!!!!!!!!!!!!!!!!! +#ifdef DUMP_VIDEO_FRAMES_ORDER + if (feedin->type == 0) + { + char frms[] = " IPB"; + int frm_type = -1; + if (feedin->pData[0] == 0 && feedin->pData[1] == 0 && feedin->pData[2] == 1 && feedin->pData[3] == 0xb6) + frm_type = (int)(feedin->pData[4] >> 6); + msg("(%c) PTS = %d\n", frms[frm_type+1], feedin->pts); + } +#endif + +#ifdef DUMP_VIDEO + if (feedin->type == 0) + { + FILE *fp; + fp = fopen("out.m4v", "ab"); + fwrite(feedin->pData, feedin->size, 1, fp); + fclose(fp); + } +#endif +#ifdef DUMP_VIDEO_PACKET + if (feedin->type == 0) + { + static int p_idx = 0; + FILE *fp; + fp = fopen("out_packets.log", "ab"); + //fprintf(fp, "[%4d] flg=%2x siz=%6d pts=" PRINTF_64d "\n", p_idx++, feedin->flags, feedin->size, feedin->pts); + fprintf(fp, "[%4d] flg=%2x siz=%6d\n", p_idx++, feedin->flags, feedin->size); + fclose(fp); + } +#endif + + //!!!!!!!!!!!!!!!!!!!!!!!! + + if (feedin->flags == 2) + khwl_pts = feedin->pts; + else if (feedin->flags == 0x80) + { + khwl_pts = INT64(90000) * feedin->pts / khwl_rate; + } + else if (feedin->flags == 0 && feedin->type == 1) + { + khwl_pts += khwl_audio_pts_per_Mb * feedin->size / (1024*1024); + } + + feed->in = feed->in->next; + + feed->num--; + feed->in_cnt++; + from->num++; + from->out_cnt++; + + if (feedin->next == NULL) + feed->out = NULL; + if (from->out == NULL) + from->in = feedin; + else + from->out->next = feedin; + from->out = feedin; + feedin->next = NULL; + + ////// simulate budidx decrement + if (feedin->bufidx) + { + if (*(feedin->bufidx) > 0) + (*feedin->bufidx)--; + } + + return TRUE; +} + +void __stdcall khwl_parser_proc(void *) +{ + for (;;) + { + WaitForSingleObject(khwl_parser_mutex, INFINITE); + if (MPEG_VIDEO_STRUCT->in != NULL) + khwl_nextfeedpacket(MPEG_VIDEO_STRUCT); + if (MPEG_AUDIO_STRUCT->in != NULL) + khwl_nextfeedpacket(MPEG_AUDIO_STRUCT); + if (MPEG_SPU_STRUCT->in != NULL) + khwl_nextfeedpacket(MPEG_SPU_STRUCT); + + ReleaseMutex(khwl_parser_mutex); + + // simulate hard work... :-) + //Sleep(3); + + // well, it isn't a real step mode 'cause we don't wait for I-frames... + /* + if (step_mode) + { + khwl_pause(); + step_mode = false; + + } + */ + + + } +} + +extern int fip_lastkey; + +KHWL_OSDSTRUCT osd; + +static BOOL module_applied = FALSE; + +DWORD *backbuf = NULL; +DWORD *osdbuf = NULL; +BYTE *buf = NULL; +KHWL_WINDOW max_wnd, dest_wnd, zoomed_wnd, src_wnd, valid_wnd, osd_wnd; + +DWORD vol[2] = { 50, 50 }; +int audio_offset = 0; + +BYTE p_challenge[10]; +BYTE p_key1[5], p_key2[5], p_key_check[5]; +int css_variant = 0; + +void khwl_calc_audio_pts_rate() +{ + if (khwl_audio_format == eAudioFormat_PCM) + { + khwl_audio_pts_per_Mb = khwl_audio_sample_rate * khwl_audio_bits * khwl_audio_channels; + if (khwl_audio_pts_per_Mb != 0) + khwl_audio_pts_per_Mb = INT64(90000)*8*1024*1024 / khwl_audio_pts_per_Mb; + } +} + +int khwl_setproperty(KHWL_PROPERTY_SET pset, int id, int size, void *value) +{ + int i; + DWORD val = *(DWORD *)value; + if (pset == KHWL_COMMON_SET && id == eDoAudioLater) + { + audio_offset = (int)val; + } + else if (pset == KHWL_BOARDINFO_SET && id == ebiCommand) + { + if (*(int *)value == ebiCommand_VideoHwBlackFrame) + { + memset(backbuf, 0, 720*480*4); + khwl_osd_update(); + } + } + else if (pset == KHWL_VIDEO_SET) + { + if (id == evDestinationWindow) + dest_wnd = *((KHWL_WINDOW *)value); + else if (id == evZoomedWindow) + zoomed_wnd = *((KHWL_WINDOW *)value); + else if (id == evSourceWindow) + src_wnd = *((KHWL_WINDOW *)value); + else if (id == evValidWindow) + valid_wnd = *((KHWL_WINDOW *)value); + else if (id == evYUVWriteParams) + { + KHWL_YUV_WRITE_PARAMS_TYPE *params = (KHWL_YUV_WRITE_PARAMS_TYPE *)value; + yuv_width = params->wWidth; + yuv_height = params->wHeight; + } + else if (id == evBrightness) + tv_brightness = val; + else if (id == evContrast) + tv_contrast = val; + else if (id == evSaturation) + tv_saturation = val; + } + else if (pset == KHWL_OSD_SET && id == eOsdDestinationWindow) + { + osd_wnd = *((KHWL_WINDOW *)value); + } + else if (pset == KHWL_TIME_SET) + { + if (id == etimSystemTimeClock) + { + KHWL_TIME_TYPE *t = (KHWL_TIME_TYPE *)value; + khwl_pts = t->pts; + } + else if (id == etimVideoCTSTimeScale) + { + khwl_rate = val; + } + } + else if (pset == KHWL_AUDIO_SET) + { + if (id == eaVolumeLeft) + vol[0] = val; + else if (id == eaVolumeRight) + vol[1] = val; + else if (id == eAudioFormat) + { + khwl_audio_format = val; + khwl_calc_audio_pts_rate(); + } + else if (id == eAudioSampleRate) + { + khwl_audio_sample_rate = val; + khwl_calc_audio_pts_rate(); + } + else if (id == eAudioNumberOfBitsPerSample) + { + khwl_audio_bits = val; + khwl_calc_audio_pts_rate(); + } + else if (id == eAudioNumberOfChannels) + { + khwl_audio_channels = val; + khwl_calc_audio_pts_rate(); + } + } + else if (pset == KHWL_EEPROM_SET && id == eEepromAccess) + { + KHWL_ADDR_DATA *data = (KHWL_ADDR_DATA *)value; + FILE *fp = fopen("settings.dat", "rb+"); + if (fp == NULL) + fp = fopen("settings.dat", "wb"); + if (fp != NULL) + { + fseek(fp, data->Addr, SEEK_SET); + BYTE d = (BYTE)data->Data; + fwrite(&d, sizeof(BYTE), 1, fp); + fclose(fp); + + } + } + else if (pset == KHWL_DECODER_SET) + { + if (id == edecCSSKey1) + { + for (i = 0 ; i < size; i++) + { + p_key1[i] = ((char *)value)[4-i]; + } + + for (i = 0 ; i < 32; ++i) + { + CryptKey(0, i, p_challenge, p_key_check); + + if (memcmp( p_key_check, p_key1, size) == 0) + { + css_variant = i; + break; + } + } + } + else if (id == edecCSSChlg2) + { + for( i = 0 ; i < size; ++i) + { + p_challenge[i] = ((char *)value)[9-i]; + } + + CryptKey(1, css_variant, p_challenge, p_key2); + } + else if (id == edecForceFixedVOPRate) + { + KHWL_FIXED_VOP_RATE_TYPE *params = (KHWL_FIXED_VOP_RATE_TYPE *)value; + khwl_scale = params->time_incr; + } + } + + return 0; +} + +int khwl_getproperty(KHWL_PROPERTY_SET pset, int id, int size, void *value) +{ + int i; + if (pset == KHWL_COMMON_SET && id == eDoAudioLater) + { + *(DWORD *)value = audio_offset; + } + else if (pset == KHWL_TIME_SET && id == etimSystemTimeClock) + ((KHWL_TIME_TYPE *)value)->pts = khwl_pts; + else if (pset == KHWL_VIDEO_SET) + { + if (id == evMaxDisplayWindow) + *((KHWL_WINDOW *)value) = max_wnd; + else if (id == evDestinationWindow) + *((KHWL_WINDOW *)value) = dest_wnd; + else if (id == evSourceWindow) + *((KHWL_WINDOW *)value) = src_wnd; + else if (id == evZoomedWindow) + *((KHWL_WINDOW *)value) = zoomed_wnd; + else if (id == evValidWindow) + *((KHWL_WINDOW *)value) = valid_wnd; + else if (id == evBrightness) + *(DWORD *)value = tv_brightness; + else if (id == evContrast) + *(DWORD *)value = tv_contrast; + else if (id == evSaturation) + *(DWORD *)value = tv_saturation; + } + else if (pset == KHWL_OSD_SET && id == eOsdDestinationWindow) + *((KHWL_WINDOW *)value) = osd_wnd; + else if (pset == KHWL_TIME_SET && id == etimVideoFrameDisplayedTime) + ((KHWL_TIME_TYPE *)value)->pts = khwl_pts; + else if (pset == KHWL_EEPROM_SET && id == eEepromAccess) + { + KHWL_ADDR_DATA *data = (KHWL_ADDR_DATA *)value; + FILE *fp = fopen("settings.dat", "rb"); + if (fp != NULL) + { + if (fseek(fp, data->Addr, SEEK_SET) == 0) + { + BYTE d; + fread(&d, sizeof(BYTE), 1, fp); + data->Data = d; + } + fclose(fp); + + } + } + else if (pset == KHWL_AUDIO_SET && id == eaVolumeLeft) + { + *(DWORD *)value = vol[0]; + } + else if (pset == KHWL_AUDIO_SET && id == eaVolumeRight) + { + *(DWORD *)value = vol[1]; + } + else if (pset == KHWL_DECODER_SET && id == edecCSSChlg) + { + for (i = 0 ; i < size; ++i ) + ((char *)value)[9-i] = p_challenge[i] = (BYTE)i; + } + else if (pset == KHWL_DECODER_SET && id == edecCSSKey2) + { + for (i = 0; i < size; ++i) + { + ((char *)value)[4-i] = p_key2[i]; + } + } + return 0; +} + +/////////////////////////////////////////////////////////// + +extern unsigned char fipram[8]; + +static HBITMAP hbm = 0; + +void UpdateYUV() +{ + if (yuv_dirty) + { + int maxoff = 720 * 480; + BYTE *ybuf = (BYTE *)((char *)tmp_Y); + BYTE *uvbuf = (BYTE *)((char *)tmp_UV); + + for (int i = 0; i < maxoff; i++) + { + BYTE r, g, b; + int o = i; + int ui = (o / yuv_width) / 4 * yuv_width + (o % yuv_width)/2; + int vi = (479 - MIN(o / yuv_width, 479)) * 720 + o % yuv_width; + + khwl_jpegyuvtorgb(ybuf[i], uvbuf[ui*2+1], uvbuf[ui*2], &r, &g, &b); + khwl_tvyuvtovgargb(ybuf[i], uvbuf[ui*2+1], uvbuf[ui*2], &r, &g, &b); + + if (vi >= 0 && vi < maxoff) + backbuf[vi] = (b << 16) | (g << 8) | r; + } + + yuv_dirty = FALSE; + } +} + +void UpdateFIP(HDC hDC) +{ + static HBRUSH hbrw[3] = { 0 }; + + if (hbrw[0] == 0) + { + LOGBRUSH lb; + lb.lbColor = RGB(49, 1, 15); + lb.lbStyle = BS_SOLID; + lb.lbHatch = 0; + hbrw[0] = CreateBrushIndirect(&lb); + } + if (hbrw[1] == 0) + { + LOGBRUSH lb; + lb.lbColor = RGB(178, 242, 249); + lb.lbStyle = BS_SOLID; + lb.lbHatch = 0; + hbrw[1] = CreateBrushIndirect(&lb); + } + if (hbrw[2] == 0) + { + LOGBRUSH lb; + lb.lbColor = RGB(255, 10, 0); + lb.lbStyle = BS_SOLID; + lb.lbHatch = 0; + hbrw[2] = CreateBrushIndirect(&lb); + } + + SelectObject(hMemDC, hbm); + BitBlt(hDC, 0, 0, 720, yoffset, hMemDC, 0, 0, SRCCOPY); + + RECT r; + + const int w = 2, h = 9, h2 = 5; + const int cx[] = { w, 0, w+h, w, 0, w+h, w }; + const int cy[] = { w+h+w+h, w+h+w, w+h+w, w+h, w, w, 0 }; + const int cw[] = { h, w, w, h, w, w, h }; + const int ch[] = { w, h, h, w, h, h, w }; + int i, posx = 500; + int posy = 11; + for (i = 0; i < 7; i++) + { + posx += (w + h + w + w); + if (i == 3 || i == 5) + posx += h; + for (int k = 0; k < 7; k++) + { + int bit = (fipram[6-i] >> k) & 1; + r.left = posx + cx[k]; + r.top = posy + cy[k]; + r.right = r.left + cw[k]; + r.bottom = r.top + ch[k]; + FillRect(hDC, &r, hbrw[!bit ? 0 : (i < 2 ? 2 : 1)] ); + } + } + posx = 500; + if (fip_get_special(FIP_SPECIAL_COLON1)) + { + r.left = posx + (w+h)*5+w+h2; + r.top = posy + (w+h/2); + r.right = r.left + w; + r.bottom = r.top + w; + FillRect(hDC, &r, hbrw[1] ); + r.top = posy + (w+h)+(w+h/2); + r.bottom = r.top + w; + FillRect(hDC, &r, hbrw[1] ); + } + if (fip_get_special(FIP_SPECIAL_COLON2)) + { + r.left = posx + (w+h)*9+w; + r.top = posy + (w+h/2); + r.right = r.left + w; + r.bottom = r.top + w; + FillRect(hDC, &r, hbrw[1] ); + r.top = posy + (w+h)+(w+h/2); + r.bottom = r.top + w; + FillRect(hDC, &r, hbrw[1] ); + } + if (fip_get_special(FIP_SPECIAL_PLAY)) + { + POINT p[3]; + const int r = 5; + const int off = 4; + p[0].x = posx + off; + p[0].y = posy + (w+h/2); + p[1].x = p[0].x - r; + p[1].y = p[0].y - r; + p[2].x = p[0].x - r; + p[2].y = p[0].y + r; + HBRUSH oldbr = (HBRUSH)SelectObject(hDC, hbrw[1]); + Polygon(hDC, p, 3); + SelectObject(hDC, oldbr); + } + if (fip_get_special(FIP_SPECIAL_PAUSE)) + { + const int d = 8; + const int off = 5; + r.left = posx + off; + r.top = posy + (w); + r.right = r.left + w; + r.bottom = r.top + d; + FillRect(hDC, &r, hbrw[2]); + r.left = posx + off + w*2; + r.right = r.left + w; + FillRect(hDC, &r, hbrw[2]); + } + + HBRUSH oldbr = (HBRUSH)SelectObject(hDC, hbrw[1]); + const int rad = 15, srad = 7; + const int off = 5; + int cposx = posx - rad - off; + int cposy = posy + rad; + const int cpie[12][4] = + { + { -66, 74, -30, 95 }, + { -95, 30, -74, 66 }, + { -97, -20, -97, 20 }, + { -74, -66, -95, -30 }, + { -30, -95, -66, -74 }, + { 20, -97, -20, -97 }, + { 66, -74, 30, -95 }, + { 95, -30, 74, -66 }, + { 97, 20, 97, -20 }, + { 74, 66, 95, 30 }, + { 30, 95, 66, 74 }, + { -20, 97, 20, 97 }, + + }; + for (i = FIP_SPECIAL_CIRCLE_1; i <= FIP_SPECIAL_CIRCLE_12; i++) + { + if (fip_get_special(i)) + { + POINT v1, v2; + POINT l1, l2; + int j = i - FIP_SPECIAL_CIRCLE_1; + v1.x = cpie[j][0]; + v1.y = cpie[j][1]; + v2.x = cpie[j][2]; + v2.y = cpie[j][3]; + l1.x = v1.x + cposx; + l1.y = v1.y + cposy; + l2.x = v2.x + cposx; + l2.y = v2.y + cposy; + Pie(hDC, cposx - rad, cposy - rad, cposx + rad, cposy + rad, l1.x, l1.y, l2.x, l2.y); + } + } + SelectObject(hDC, ::GetStockObject(BLACK_BRUSH)); + Ellipse(hDC, cposx - srad, cposy - srad, cposx + srad, cposy + srad); + SelectObject(hDC, oldbr); +} + +inline void FillHorzLine(BYTE *d, int x1, int x2, int y, BYTE color) +{ + d += y * osd_width + x1; + memset(d, color, x2 - x1 + 1); +} + +inline void FillVertLine(BYTE *d, int x, int y1, int y2, BYTE color) +{ + d += y1 * osd_width + x; + y2 -= y1; + for (int i = 0; i <= y2; i++) + { + *d = color; + d += osd_width; + } +} + +static void CreateTvMask() +{ + const int num_scan_lines = 13; + int i, scan_lines[2][num_scan_lines] = + { + { 45, 47, 48, 50, 52, 54, 57, 62, 68, 77, 92, 122, 212 }, + { 37, 39, 40, 42, 44, 46, 49, 54, 60, 69, 84, 114, 204 }, + }; + + memset(tv_mask, 0xff, osd_width * osd_height); + for (i = 0; i <= 23; i++) + { + memset(tv_mask + i * osd_width, 0, osd_width); + memset(tv_mask + (i + 456) * osd_width, 0, osd_width); + } + for (i = 0; i < osd_height; i++) + { + memset(tv_mask + i * osd_width, 0, 32); + memset(tv_mask + i * osd_width + 608, 0, 32); + } + for (i = 0; i < num_scan_lines; i++) + { + FillHorzLine(tv_mask, 0, scan_lines[0][i], 36 - i, 0); + FillHorzLine(tv_mask, 0, scan_lines[0][i], 443 + i, 0); + FillHorzLine(tv_mask, osd_width - scan_lines[0][i], osd_width - 1, 36 - i, 0); + FillHorzLine(tv_mask, osd_width - scan_lines[0][i], osd_width - 1, 443 + i, 0); + FillVertLine(tv_mask, 44 - i, 0, scan_lines[1][i], 0); + FillVertLine(tv_mask, 595 + i, 0, scan_lines[1][i], 0); + FillVertLine(tv_mask, 44 - i, osd_height - scan_lines[1][i], osd_height - 1, 0); + FillVertLine(tv_mask, 595 + i, osd_height - scan_lines[1][i], osd_height - 1, 0); + } +} + +void Generate_yuv2rgb_tables(void); +void new_rgbtoyuv(BYTE R, BYTE G, BYTE B, BYTE *y, BYTE *u, BYTE *v); +void new_yuvtorgb(BYTE y, BYTE u, BYTE v, BYTE *R, BYTE *G, BYTE *B); + + +LRESULT FAR PASCAL CALLBACK MainWindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + switch (uMsg) + { + case WM_CREATE: + break; + case WM_CLOSE: + PostQuitMessage(0); + break; + case WM_ERASEBKGND: + return 0; + case WM_PAINT: + { + if (buf != NULL) + { + PAINTSTRUCT ps; + BeginPaint((HWND)khwl_handle, &ps); + + HDC hDC = GetDC((HWND)khwl_handle); + HBITMAP hBitmap; + + if (hDC != curDC) + { + if (hMemDC != NULL) + DeleteDC(hMemDC); + hMemDC = CreateCompatibleDC(hDC); + if (hSrcBitmap != NULL) + DeleteObject(hSrcBitmap); + hSrcBitmap = CreateCompatibleBitmap(hDC, (WORD)720, 480); + curDC = hDC; + } + + int i; + RGBQUAD pal[256] = { 0 }; + for (i = 0; i < 256; i++) + { + khwl_tvyuvtovgargb(buf[8+i*4+1], buf[8+i*4+2], buf[8+i*4+3], + &pal[i].rgbRed, &pal[i].rgbGreen, &pal[i].rgbBlue); + } + + UpdateYUV(); + + BYTE *bb = buf + 8 + 1024 + 640*480, *b = bb; + BYTE *tvmm = tv_mask + 640*480, *tvm = tvmm; + DWORD *osd = osdbuf, *src = backbuf; + BYTE *ach = buf + 8; + + /// \TODO: use MMX/SSE for optimization... + for (i = 0; i < 480; i++) + { + bb -= 640; + tvmm -= 640; + for (int ix = 0; ix < 720; ix++) + { + register int dix = ix * 640 / 720; + b = bb + dix; + tvm = tvmm + dix; + int a = ach[*b << 2]; + if (*tvm || !use_tvmask) + { + if (a == 0) + *osd = *src; + else if (a == 0xff) + *osd = *(DWORD *)(pal + *b); + else + { + int y = (*src >> 16) & 0xff; + *osd = ((a * (pal[*b].rgbRed - y) / 255 + y) << 16); + y = (*src >> 8) & 0xff; + *osd |= ((a * (pal[*b].rgbGreen - y) / 255 + y) << 8); + y = (*src) & 0xff; + *osd |= ((a * (pal[*b].rgbBlue - y) / 255 + y)); + } + } else + *osd = 0; + osd++; + src++; + } + } + + SetDIBits(hDC, hSrcBitmap, 0, 480, osdbuf, (BITMAPINFO *)&bi, DIB_RGB_COLORS); + hBitmap = (HBITMAP)SelectObject(hMemDC, hSrcBitmap); + + BitBlt(hDC, 0, yoffset, 720, 480, hMemDC, 0, 0, SRCCOPY); + + //Rectangle(hDC, zoomed_wnd.x, zoomed_wnd.y+yoffset, zoomed_wnd.x + zoomed_wnd.w, zoomed_wnd.y+zoomed_wnd.h+yoffset); + + UpdateFIP(hDC); + + SelectObject(hMemDC, hBitmap); + ReleaseDC((HWND)khwl_handle, hDC); + + EndPaint((HWND)khwl_handle, &ps); + } + } + break; + case WM_KEYDOWN: + { + struct + { + int vk_key; + int fip_key; + } keytabl[] = + { + { VK_LEFT, FIP_KEY_LEFT }, + { VK_RIGHT, FIP_KEY_RIGHT }, + { VK_UP, FIP_KEY_UP }, + { VK_DOWN, FIP_KEY_DOWN }, + { VK_HOME, FIP_KEY_REWIND }, + { VK_END, FIP_KEY_FORWARD }, + { VK_PRIOR, FIP_KEY_SKIP_PREV }, + { VK_NEXT, FIP_KEY_SKIP_NEXT }, + { VK_ESCAPE, FIP_KEY_RETURN }, + { 13, FIP_KEY_ENTER }, + { VK_BACK, FIP_KEY_RETURN }, + + { VK_SPACE, FIP_KEY_PLAY }, + { VK_TAB, FIP_KEY_STOP }, + + { '1', FIP_KEY_ONE }, + { '2', FIP_KEY_TWO }, + { '3', FIP_KEY_THREE }, + { '4', FIP_KEY_FOUR }, + { '5', FIP_KEY_FIVE }, + { '6', FIP_KEY_SIX }, + { '7', FIP_KEY_SEVEN }, + { '8', FIP_KEY_EIGHT }, + { '9', FIP_KEY_NINE }, + { '0', FIP_KEY_ZERO }, + + { 'A', FIP_KEY_ANGLE }, + { 'B', FIP_KEY_AB }, + { 'C', FIP_KEY_CANCEL }, + { 'D', FIP_KEY_AUDIO }, + { 'E', FIP_KEY_TITLE }, + { 'I', FIP_KEY_PBC }, + { 'L', FIP_KEY_SLOW }, + { 'M', FIP_KEY_MENU }, + { 'N', FIP_KEY_PN }, + { 'O', FIP_KEY_PROGRAM }, + { 'P', FIP_KEY_PAUSE }, + { 'R', FIP_KEY_REPEAT }, + { 'S', FIP_KEY_SEARCH }, + { 'T', FIP_KEY_SUBTITLE }, + { 'U', FIP_KEY_MUTE }, + { 'V', FIP_KEY_VMODE }, + { 'Z', FIP_KEY_ZOOM }, + + { 187, FIP_KEY_VOLUME_UP }, + { 189, FIP_KEY_VOLUME_DOWN }, + + { VK_F1, FIP_KEY_POWER }, + { VK_F2, FIP_KEY_EJECT }, + { VK_F3, FIP_KEY_SETUP }, + { VK_F4, FIP_KEY_OSD }, + { -1, -1 } + }; + + if (wParam != 0) + { + for (int i = 0; keytabl[i].vk_key != -1; i++) + { + if ((int)wParam == keytabl[i].vk_key) + { + fip_lastkey = keytabl[i].fip_key; + break; + } + } + } + if (wParam == VK_F9) + { + use_tvmask = !use_tvmask; + InvalidateRect((HWND)khwl_handle, NULL, FALSE); + UpdateWindow((HWND)khwl_handle); + } + break; + } + case WM_KEYUP: + //fip_lastkey = 0; + break; + case WM_MOUSEMOVE: + { + int x = LOWORD(lParam); + int y = HIWORD(lParam); + static char buf[256]; + if (y >= yoffset) + { + sprintf(buf, "%s (%d, %d)", wndtitle, x, y-yoffset); + SetWindowText(hWnd, buf); + } + else + { + struct + { + int fip_key; + int x1, y1, x2, y2; + } keytabl[] = + { + { FIP_KEY_FRONT_EJECT, 418, 28, 445, 42 }, + { FIP_KEY_FRONT_PLAY, 421, 68, 439, 85 }, + { FIP_KEY_FRONT_STOP, 458, 68, 476, 85 }, + { FIP_KEY_FRONT_PAUSE, 497, 68, 515, 85 }, + { FIP_KEY_FRONT_SKIP_PREV, 579, 68, 597, 85 }, + { FIP_KEY_FRONT_SKIP_NEXT, 609, 68, 627, 85 }, + { FIP_KEY_FRONT_REWIND, 653, 68, 671, 85 }, + { FIP_KEY_FRONT_FORWARD, 683, 68, 701, 85 }, + { -1, -1, -1, -1, -1 } + }; + + fip_button = -1; + for (int i = 0; keytabl[i].fip_key != -1; i++) + { + if (x >= keytabl[i].x1 && x <= keytabl[i].x2 + && y >= keytabl[i].y1 && y <= keytabl[i].y2) + { + fip_button = keytabl[i].fip_key; + break; + } + } + + if (fip_button != -1) + SetCursor(::LoadCursor(NULL, MAKEINTRESOURCE(32649))); + else + SetCursor(::LoadCursor(NULL, IDC_ARROW)); + + SetWindowText(hWnd, wndtitle); + } + break; + } + case WM_LBUTTONDOWN: + { + if (fip_button != -1) + fip_lastkey = fip_button; + } + break; + default: + return(DefWindowProc(hWnd, uMsg, wParam, lParam)); + } + return((LRESULT)NULL); +} + +///////////////////////////////////////////// + +static BOOL init(void) +{ + //InitCommonControls(); + + WNDCLASS wc; + + wc.style = (UINT)NULL; + wc.lpfnWndProc = (WNDPROC)MainWindowProc; + wc.cbClsExtra = 0; + wc.cbWndExtra = 0; + wc.hInstance = GetModuleHandle(NULL); + wc.hIcon = NULL; + wc.hCursor = LoadCursor(NULL, IDC_ARROW); + wc.hbrBackground = (HBRUSH)GetStockObject(GRAY_BRUSH); + wc.lpszMenuName = NULL; + wc.lpszClassName ="SPInitClass"; + return (RegisterClass(&wc) != 0); +} + +static void cwnd() +{ + HWND hWnd; + int w = 720,h = 480+yoffset; + int xcoord = GetSystemMetrics(SM_CXSCREEN)/2-w/2; + int ycoord = GetSystemMetrics(SM_CYSCREEN)/2-h/2; + hWnd = CreateWindow("SPInitClass", wndtitle, WS_OVERLAPPED|WS_SYSMENU, xcoord, ycoord, w, h, NULL, NULL, GetModuleHandle(NULL), NULL); + RECT dstrect; + GetClientRect(hWnd, &dstrect); + int dx = w*2 - (dstrect.right - dstrect.left); + int dy = h*2 - (dstrect.bottom - dstrect.top); + SetWindowPos(hWnd, HWND_TOP, GetSystemMetrics(SM_CXSCREEN) / 2 - dx / 2, + GetSystemMetrics(SM_CYSCREEN) / 2 - dy / 2, dx, dy, 0); + + khwl_handle = (int)hWnd; + ShowWindow(hWnd, SW_SHOW); + UpdateWindow(hWnd); +} + +BOOL khwl_init(BOOL /*applymodule*/) +{ + khwl_inityuv(); + + khwl_deinit(); + + init(); + cwnd(); + + buf = (BYTE *)SPmalloc(640*480 + 1024 + 8); + backbuf = (DWORD *)SPmalloc(720 * 480 * sizeof(DWORD)); + memset(backbuf, 0, 720*480*sizeof(DWORD)); + osdbuf = (DWORD *)SPmalloc(720 * 480 * sizeof(DWORD)); + + int bs = 1048576; + BUF_BASE = (BYTE *)VirtualAlloc((void *)0x016c0000/*0x01680000*/, bs, MEM_RESERVE, PAGE_READWRITE); + BUF_BASE = (BYTE *)VirtualAlloc(BUF_BASE, bs, MEM_COMMIT, PAGE_READWRITE); + if (BUF_BASE == NULL) + { + //int err = GetLastError(); + BUF_BASE = (BYTE *)VirtualAlloc(NULL, bs, MEM_COMMIT, PAGE_READWRITE); + } + if (BUF_BASE == NULL) + return FALSE; + + + khwl_parser_mutex = CreateMutex(NULL, FALSE, NULL); + khwl_parser = CreateThread (NULL, 16384, (LPTHREAD_START_ROUTINE)khwl_parser_proc, + (void *)0, CREATE_SUSPENDED, &khwl_parser_id); + + memset(&bi, 0, sizeof(bi)); + + bi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + bi.bmiHeader.biWidth = 720; + bi.bmiHeader.biHeight = 480; + bi.bmiHeader.biPlanes = 1; + bi.bmiHeader.biBitCount = 32; + bi.bmiHeader.biCompression = BI_RGB; + + if (hbm == NULL) + { + hbm = LoadBitmap(GetModuleHandle(NULL), MAKEINTRESOURCE(IDB_FIP)); + + if (hbm == NULL) + { + int err = GetLastError(); + char tmp[25]; + sprintf(tmp, "Resources loading error = %d", err); + MessageBox((HWND)khwl_handle, tmp, "Error", MB_OK|MB_ICONEXCLAMATION); + return FALSE; + } + } + + max_wnd.x = 0; + max_wnd.y = 0; + max_wnd.w = 720; + max_wnd.h = 480; + + src_wnd = zoomed_wnd = valid_wnd = dest_wnd = max_wnd; + osd_wnd = max_wnd; + + CreateTvMask(); + + return TRUE; +} + +BOOL khwl_restoreparams() +{ + + return TRUE; +} + +BOOL khwl_deinit() +{ + if (khwl_handle == -1) + return FALSE; + + SPfree(buf); + SPfree(backbuf); + SPfree(osdbuf); + + CloseHandle(khwl_parser); + CloseHandle(khwl_parser_mutex); + + VirtualFree(BUF_BASE, 0, MEM_RELEASE); + BUF_BASE = NULL; + + DestroyWindow((HWND)khwl_handle); + khwl_handle = -1; + return TRUE; +} + +BOOL khwl_reset() +{ + if (khwl_handle == -1) + return FALSE; + + return 0; +} + +int khwl_osd_switch(KHWL_OSDSTRUCT *_osd, BOOL autoupd) +{ + static int TimerID = -1; + if (khwl_handle == -1) + return FALSE; + if (_osd->flags != 0 || autoupd) + { + if (TimerID != -1) + timeKillEvent(TimerID); + TimerID = timeSetEvent(1000, 1, OSDUpdate, 0, TIME_PERIODIC); + } + osd.addr = buf; + osd.bpp = 8; + osd.width = 640; + osd.height = 480; + *_osd = osd; + + + return 0; +} + +void khwl_get_osd_size(int *width, int *height) +{ + *width = 640;//osd.width; + *height = 480;//osd.height; +} + +bool khwl_msgloop() +{ + MSG msg; + while (PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE)) + { + if (GetMessage(&msg, NULL, 0, 0) == 0) + return false; + //if (TranslateAccelerator(hWnd, hAccel, &msg) == 0) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + } + return true; +} + +BOOL khwl_osd_update() +{ + InvalidateRect((HWND)khwl_handle, NULL, FALSE); + UpdateWindow((HWND)khwl_handle); + + return khwl_msgloop(); +} + +int khwl_osd_setalpha(int ) +{ + return 0; +} + +void khwl_osd_setpalette(BYTE *pal, int entry, BYTE r, BYTE g, BYTE b, BYTE a) +{ + // assert(entry >= 0 && entry < 256); + BYTE y, u, v; + entry <<= 2; + khwl_vgargbtotvyuv(r, g, b, &y, &u, &v); + pal[entry++] = a; // alpha + pal[entry++] = y; + pal[entry++] = u; + pal[entry] = v; +} + +void khwl_osd_setfullscreen(BOOL) +{ +} + +int *khwl_get_samplerates() +{ + static int rates[2][12] = + { + // chip rev.A + { 16000, 22050, 24000, 32000, 44100, 48000, -1 }, + // chip rev.B supports more samplerates + { 11025, 12000, 16000, 22050, 24000, 32000, 44100, 48000, 64000, 88200, 96000, -1 }, + }; + return rates[0/*inl(SYS_REVID_REG) & 1*/]; +} + +#define MIN(a, b) (((a) <= (b)) ? (a) : (b)) +#define MAX(a, b) (((a) >= (b)) ? (a) : (b)) + +int khwl_displayYUV(KHWL_YUV_FRAME *f) +{ + if (f->uv_buf != NULL) + { + memcpy(tmp_UV + f->uv_offs, f->uv_buf, f->uv_num); + yuv_dirty = TRUE; + } + if (f->y_buf != NULL) + { + memcpy(tmp_Y + f->y_offs, f->y_buf, f->y_num); + yuv_dirty = TRUE; + } + return 0; +} + +int khwl_audioswitch(BOOL /*ison*/) +{ + //int val = ison ? 1 : 0; + //return ioctl(khwl_handle, KHWL_AUDIOSWITCH, &val); + return TRUE; +} + +int khwl_play(int mode) +{ + if (mode == KHWL_PLAY_MODE_STEP) + { + if (!step_mode) + { + khwl_pause(); + step_mode = true; + } + return 0; + } + int last; + while ((last = ResumeThread(khwl_parser)) > 1) + ; + step_mode = false; + return 0; +} + +int khwl_pause() +{ + WaitForSingleObject(khwl_parser_mutex, INFINITE); + SuspendThread(khwl_parser); + ReleaseMutex(khwl_parser_mutex); + return 0; +} + +int khwl_stop() +{ + WaitForSingleObject(khwl_parser_mutex, INFINITE); + SuspendThread(khwl_parser); + + for (;;) + { + if (MPEG_VIDEO_STRUCT->in != NULL) + { + khwl_nextfeedpacket(MPEG_VIDEO_STRUCT); + continue; + } + if (MPEG_AUDIO_STRUCT->in != NULL) + { + khwl_nextfeedpacket(MPEG_AUDIO_STRUCT); + continue; + } + if (MPEG_SPU_STRUCT->in != NULL) + { + khwl_nextfeedpacket(MPEG_SPU_STRUCT); + continue; + } + break; + } + + + ReleaseMutex(khwl_parser_mutex); + + return 0; +} + +BOOL khwl_blockirq(BOOL block) +{ + if (block) + WaitForSingleObject(khwl_parser_mutex, INFINITE); + else + ReleaseMutex(khwl_parser_mutex); + + return TRUE; +} + +BOOL khwl_happeningwait(DWORD *) +{ + // do nothing + khwl_msgloop(); + Sleep(1); + return TRUE; +} + +BOOL khwl_poll(WORD , DWORD ) +{ + khwl_msgloop(); + Sleep(1); + return TRUE; +} + +BOOL khwl_ideswitch(BOOL ) +{ + return TRUE; +} + +int khwl_getfrequency() +{ + int temp = freq_reg; + + int div = (temp >> 8) & 0xff; + int mul = (temp >> 2) & 63; + + int freq = (27 * (mul + 2)) / ((div + 2) * 2); + return freq; +} + +BOOL khwl_setfrequency(int freq) +{ + if (freq < 100 || freq > 202) + return FALSE; + + int temp = freq_reg; + temp = temp & 0xF000; + int div = (temp >> 8) & 0xff; + //int mul = (temp >> 2) & 63; + + int mul = (freq * ((10 * div+20)*20) / 270 - 20 /* + 5 */) / 10; + if (mul < 0 || mul > 63) + return FALSE; + + temp = temp | 0x02; + temp = temp | (div << 8); + temp = temp | (mul << 2); + + freq_reg = temp & 0x7FFF; + return TRUE; +} + +char *khwl_gethw() +{ + static char hw[256]; +/* + char b[128]; + b[0] = '\0'; + khwl_getproperty(KHWL_BOARDINFO_SET, ebiBoardNameString, 256, b); + + DWORD v_ebiDeviceId, v_ebiSubId, v_ebiBoardVersion, v_ebiHwLibVersion, v_ebiUcodeVersion; + khwl_getproperty(KHWL_BOARDINFO_SET, ebiDeviceId, sizeof(DWORD), &v_ebiDeviceId); + khwl_getproperty(KHWL_BOARDINFO_SET, ebiSubId, sizeof(DWORD), &v_ebiSubId); + khwl_getproperty(KHWL_BOARDINFO_SET, ebiBoardVersion, sizeof(DWORD), &v_ebiBoardVersion); + khwl_getproperty(KHWL_BOARDINFO_SET, ebiHwLibVersion, sizeof(DWORD), &v_ebiHwLibVersion); + khwl_getproperty(KHWL_BOARDINFO_SET, ebiUcodeVersion , sizeof(DWORD), &v_ebiUcodeVersion); +*/ + sprintf(hw, "%X.%c", 0x8500/*inl(SYS_CHIPID_REG)*/, 0/*inl(SYS_REVID_REG)*/ + 'A'); + + return hw; +} + +///////////////////////////////////////////////////////// + +extern int main(int argc, char *argv[]); + +int PASCAL WinMain (HINSTANCE /*hInst*/, HINSTANCE /*hPrevInst*/, LPSTR lpszCmdLine, int /*nCmdShow*/) +{ + char *args[2] = { lpszCmdLine, NULL }; + + AllocConsole(); + + int hCrt = _open_osfhandle((long) GetStdHandle(STD_OUTPUT_HANDLE), _O_TEXT); + FILE *hf = _fdopen( hCrt, "w" ); + //FILE *hf = fopen("sp.log", "w"); + *stdout = *hf; + int ret = setvbuf( stdout, NULL, _IONBF, 0 ); + + hCrt = _open_osfhandle((long) GetStdHandle(STD_ERROR_HANDLE), _O_TEXT); + hf = _fdopen( hCrt, "w" ); + *stderr = *hf; + ret = setvbuf( stderr, NULL, _IONBF, 0 ); + + return main(1, args); +} + diff --git a/src/libsp/win32/sp_memory.cpp b/src/libsp/win32/sp_memory.cpp new file mode 100644 index 0000000..267bbd2 --- /dev/null +++ b/src/libsp/win32/sp_memory.cpp @@ -0,0 +1,995 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer support lib memory debugging routines + * \file sp_memory.cpp + * \author ***** + * \version 0.1 + * \date 14.07.2002 (23.08.2001) + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include + +#include + +//#define MEMDUMP +//#define TEST_UCLIBC + +// undefine our macros +#undef new +#undef delete + + +static bool special_case = false; + +////////////////////////////////////////////////////////////////////////// +// this is needed to ensure that mm starts first and ends last +int _crt_deinit() +{ +#ifdef SP_MEMDEBUG + special_case = true; + delete SPMemoryManager::mm; + special_case = false; +#endif + return 0; +} +int _crt_init() +{ + onexit(_crt_deinit); + +#ifdef SP_MEMDEBUG + special_case = true; + SPMemoryManager::mm = new SPMemoryManager(); + special_case = false; +#endif + + return 0; +} + +// this is VC-specific hack for calling before CRT +typedef int cb(void); +#pragma data_seg(".CRT$XIU") +static cb *autostart[] = { _crt_init }; +#pragma data_seg(".CRT$XPU") +static cb *autoexit[] = { _crt_deinit }; +#pragma data_seg() // reset data-segment + +#ifdef SP_MEMDEBUG + +#ifdef _DEBUG +#define SP_CRT_DEBUG +#endif + + +// global manager definition +SPMemoryManager *SPMemoryManager::mm = NULL; + +#ifdef SP_CRT_DEBUG +#define _NORMAL_BLOCK 1 +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ +_CRTIMP void * __cdecl _malloc_dbg(size_t,int,const char *,int); +_CRTIMP void * __cdecl _realloc_dbg(void *,size_t,int,const char *,int); +_CRTIMP void __cdecl _free_dbg(void *,int); +#ifdef __cplusplus +} +#endif +#endif + +// Global new/new[] & delete/delete[] +////////////////////////////////////////////////////////////////////////// + +void *operator new(size_t reqSize) +{ + if (special_case) // used by mm itself + return calloc(reqSize, 1); + +#ifdef TEST_UCLIBC + return SPcalloc(reqSize); +#else + + SPMemoryManager &memManager = SPMemoryManager::GetHandle(); + + // zero allocation is valid (but strange...) + if(reqSize == 0) + { + reqSize = 1; + memManager.Log("Warning: Zero allocation in %s (%d)", memManager. + GetSourceName(memManager.srcFile), memManager.srcLine); + } + void *ptr = memManager.Alloc(memManager.srcFile, + memManager.srcLine, reqSize, SP_ALLOC_NEW); + + return ptr; +#endif +} + +// ----------------------------------------------------------------------- + +void *operator new[](size_t reqSize) +{ +#ifdef TEST_UCLIBC + return SPcalloc(reqSize); +#else + SPMemoryManager &memManager = SPMemoryManager::GetHandle(); + + // zero allocation is valid (but strange...) + if(reqSize == 0) + { + reqSize = 1; + memManager.Log("Warning: Zero allocation in %s (%d)", memManager. + GetSourceName(memManager.srcFile), memManager.srcLine); + } + void *ptr = memManager.Alloc(memManager.srcFile, + memManager.srcLine, reqSize, SP_ALLOC_NEW_ARRAY); + + return ptr; +#endif +} + +// ----------------------------------------------------------------------- + +void operator delete(void *reqAddress) +{ + if (special_case) // used by mm itself + { + free(reqAddress); + return; + } + +#ifdef TEST_UCLIBC + SPfree(reqAddress); +#else + + SPMemoryManager &memManager = SPMemoryManager::GetHandle(); + + // zero deallocation is valid (but strange...) + if (!reqAddress) + { + memManager.Log("Warning: Zero deallocation in %s (%d)", + memManager.GetSourceName(memManager.srcFile), + memManager.srcLine); + return; + } + + memManager.Dealloc(memManager.srcFile, memManager.srcLine, + SP_ALLOC_DELETE, reqAddress); +#endif +} + +// ----------------------------------------------------------------------- + +void operator delete[](void *reqAddress) +{ +#ifdef TEST_UCLIBC + SPfree(reqAddress); +#else + SPMemoryManager &memManager = SPMemoryManager::GetHandle(); + + // zero deallocation is valid (but strange...) + if (!reqAddress) + { + memManager.Log("Warning: Zero deallocation in %s (%d)", + memManager.GetSourceName(memManager.srcFile), + memManager.srcLine); + return; + } + memManager.Dealloc(memManager.srcFile, memManager.srcLine, + SP_ALLOC_DELETE_ARRAY, reqAddress); +#endif +} + +// SPMemoryManager member-functions +////////////////////////////////////////////////////////////////////////// + +SPMemoryManager::SPMemoryManager() : srcFile(NULL), srcLine(0), + prefixPattern(0xAAAAAAAA), postfixPattern(0xBBBBBBBB), + unusedPattern(0xCCCCCCCC), releasedPattern(0xDDDDDDDD), + currentAllocated(0), unitsBufferSize(0), borderSize(8), units(NULL), + unitsBuffer(NULL) +{ + memset(&memStats, 0, sizeof(memStats)); + BeginLog(); +} + +void* SPMemoryManager::Alloc(const char *file, const DWORD line, + const DWORD reqSize, const DWORD allocType) +{ + currentAllocated++; + if(!units) + { + // allocate 256 unit elements + units = (SPAllocUnit *)malloc(sizeof(SPAllocUnit) * 256); + if(units == NULL) + { + OutOfMemory(); + return NULL; + } + + memset(units, 0, sizeof(SPAllocUnit) * 256); + for(DWORD i = 0; i < 256 - 1; i++) + units[i].next = &units[i+1]; + SPAllocUnit **temp = (SPAllocUnit **)realloc(unitsBuffer, + (unitsBufferSize + 1) * sizeof(SPAllocUnit *)); + if(temp) + { + unitsBuffer = temp; + unitsBuffer[unitsBufferSize++] = units; + } + else + { + OutOfMemory(); + return NULL; + } + } + + SPAllocUnit *cur = units; + units = cur->next; + + // fill alloc unit + cur->reqSize = reqSize; + cur->actualSize = reqSize + borderSize * sizeof(DWORD) * 2; + cur->actualAddress = malloc(cur->actualSize); + if (cur->actualAddress == NULL) + { + OutOfMemory(); + return NULL; + } + cur->allocationType = allocType; + cur->reportedAddress = reinterpret_cast(cur->actualAddress) + + borderSize * sizeof(DWORD); + strncpy(cur->sourceFile, GetSourceName(const_cast(file)), + sizeof(cur->sourceFile) - 1); + cur->sourceLine = line; + cur->allocationNumber = currentAllocated; + + // insert unit into hash table + DWORD hashIndex = (reinterpret_cast(cur->reportedAddress) >> + 4) & SP_MM_HASHSIZEM; + if(hashTable[hashIndex]) + hashTable[hashIndex]->prev = cur; + cur->next = hashTable[hashIndex]; + cur->prev = NULL; + hashTable[hashIndex] = cur; + + // update stats + memStats.totalReportedMemory += cur->reqSize; + memStats.totalActualMemory += cur->actualSize; + memStats.totalAllocUnitCount++; + if(memStats.totalReportedMemory > memStats.peakReportedMemory) + memStats.peakReportedMemory = memStats.totalReportedMemory; + if(memStats.totalActualMemory > memStats.peakActualMemory) + memStats.peakActualMemory = memStats.totalActualMemory; + if(memStats.totalAllocUnitCount > memStats.peakAllocUnitCount) + memStats.peakAllocUnitCount = memStats.totalAllocUnitCount; + memStats.accumulatedReportedMemory += cur->reqSize; + memStats.accumulatedActualMemory += cur->actualSize; + memStats.accumulatedAllocUnitCount++; + + // prepare memory block + FillWithPattern(cur, unusedPattern); + + // calloc expects zero-filled memory + if(allocType == SP_ALLOC_CALLOC) + memset(cur->reportedAddress, 0, cur->reqSize); + + // preserve globals + ClearGlobals(); + +#ifdef MEMDUMP + FILE *fp = fopen(SP_MMDUMP_LOGFILENAME, "at"); + fprintf(fp, "+[%d] %s (%d):\t\t%d\t\t[%d]\n", currentAllocated, file, line, reqSize,memStats.totalReportedMemory); + fclose(fp); +#endif + + return cur->reportedAddress; +} + +// ----------------------------------------------------------------------- + +char* SPMemoryManager::Strdup(const char *file, const DWORD line, + const char *source) +{ + DWORD reqSize; + if(!source) + reqSize = 1; + else + reqSize = strlen(source) + 1; + DWORD allocType = SP_ALLOC_STRDUP; + + currentAllocated++; + if(!units) + { + // allocate 256 unit elements + units = (SPAllocUnit *)malloc(sizeof(SPAllocUnit) * 256); + if(units == NULL) + { + OutOfMemory(); + return NULL; + } + + memset(units, 0, sizeof(SPAllocUnit) * 256); + for(DWORD i = 0; i < 256 - 1; i++) + units[i].next = &units[i+1]; + SPAllocUnit **temp = (SPAllocUnit **)realloc(unitsBuffer, + (unitsBufferSize + 1) * sizeof(SPAllocUnit *)); + if (temp) + { + unitsBuffer = temp; + unitsBuffer[unitsBufferSize++] = units; + } + else + { + OutOfMemory(); + return NULL; + } + } + + SPAllocUnit *cur = units; + units = cur->next; + + // fill alloc unit + cur->reqSize = reqSize; + cur->actualSize = reqSize + borderSize * sizeof(DWORD) * 2; + cur->actualAddress = malloc(cur->actualSize); + if(!cur->actualAddress) + { + OutOfMemory(); + return NULL; + } + cur->allocationType = allocType; + cur->reportedAddress = reinterpret_cast(cur->actualAddress) + + borderSize * sizeof(DWORD); + strncpy(cur->sourceFile, GetSourceName(const_cast(file)), + sizeof(cur->sourceFile) - 1); + cur->sourceLine = line; + cur->allocationNumber = currentAllocated; + + // insert unit into hash table + DWORD hashIndex = (reinterpret_cast(cur->reportedAddress) >> + 4) & SP_MM_HASHSIZEM; + if(hashTable[hashIndex]) + hashTable[hashIndex]->prev = cur; + cur->next = hashTable[hashIndex]; + cur->prev = NULL; + hashTable[hashIndex] = cur; + + // update stats + memStats.totalReportedMemory += cur->reqSize; + memStats.totalActualMemory += cur->actualSize; + memStats.totalAllocUnitCount++; + if(memStats.totalReportedMemory > memStats.peakReportedMemory) + memStats.peakReportedMemory = memStats.totalReportedMemory; + if(memStats.totalActualMemory > memStats.peakActualMemory) + memStats.peakActualMemory = memStats.totalActualMemory; + if(memStats.totalAllocUnitCount > memStats.peakAllocUnitCount) + memStats.peakAllocUnitCount = memStats.totalAllocUnitCount; + memStats.accumulatedReportedMemory += cur->reqSize; + memStats.accumulatedActualMemory += cur->actualSize; + memStats.accumulatedAllocUnitCount++; + + // prepare memory block + FillWithPattern(cur, unusedPattern); + + // copy strdup string + if(source) + strcpy(static_cast(cur->reportedAddress), source); + + // preserve globals + ClearGlobals(); + +#ifdef MEMDUMP + FILE *fp = fopen(SP_MMDUMP_LOGFILENAME, "at"); + fprintf(fp, "+[%d] %s (%d):\t\t%d\t\t[%d]\n", currentAllocated, file, line, reqSize,memStats.totalReportedMemory); + fclose(fp); +#endif + + return static_cast(cur->reportedAddress); +} + +// ----------------------------------------------------------------------- + +void* SPMemoryManager::Realloc(const char *file, const DWORD line, + const DWORD reqSize, void* reqAddress, const DWORD reallocType) +{ + if(!reqAddress) + return Alloc(file, line, reqSize, reallocType); + + SPAllocUnit *u = FindAllocUnit(reqAddress); + if(u == NULL) + { + Log("Request to reallocate RAM that was never allocated in %s (%d)", + GetSourceName(const_cast(file)), line); + return NULL; + } + + currentAllocated++; + + ValidateUnit(u); + + if(!(u->allocationType == SP_ALLOC_MALLOC|| u->allocationType == + SP_ALLOC_CALLOC || u->allocationType == SP_ALLOC_REALLOC)) + Log("Allocation/deallocation mismatch in %s (%d)", + GetSourceName(const_cast(file)), line); + + DWORD originalSize = u->reqSize; + + // do the reallocation + void *oldReqAddress = reqAddress; + DWORD newActualSize = reqSize + borderSize * sizeof(DWORD) * 2; + void *newActualAddress = realloc(u->actualAddress, newActualSize); + if(!newActualAddress) + { + OutOfMemory(); + return NULL; + } + + memStats.totalReportedMemory -= u->reqSize; + memStats.totalActualMemory -= u->actualSize; + + u->actualSize = newActualSize; + u->actualAddress = newActualAddress; + u->reqSize = reqSize; + u->reportedAddress = reinterpret_cast(newActualAddress) + + borderSize * sizeof(DWORD); + u->allocationType = reallocType; + u->sourceLine = line; + u->allocationNumber = currentAllocated; + + strncpy(u->sourceFile, GetSourceName(const_cast(file)), + sizeof(u->sourceFile) - 1); + + DWORD hashIndex; + if(oldReqAddress != u->reportedAddress) + { + // remove this allocation unit from the hash table + { + hashIndex = (reinterpret_cast(oldReqAddress) >> 4) & + SP_MM_HASHSIZEM; + if(hashTable[hashIndex] == u) + hashTable[hashIndex] = hashTable[hashIndex]->next; + else + { + if(u->prev) u->prev->next = u->next; + if(u->next) u->next->prev = u->prev; + } + } + + // re-insert it back into the hash table + hashIndex = (reinterpret_cast(u->reportedAddress) >> 4) & + SP_MM_HASHSIZEM; + if(hashTable[hashIndex]) + hashTable[hashIndex]->prev = u; + u->next = hashTable[hashIndex]; + u->prev = NULL; + hashTable[hashIndex] = u; + } + + memStats.totalReportedMemory += u->reqSize; + memStats.totalActualMemory += u->actualSize; + if(memStats.totalReportedMemory > memStats.peakReportedMemory) + memStats.peakReportedMemory = memStats.totalReportedMemory; + if(memStats.totalActualMemory > memStats.peakActualMemory) + memStats.peakActualMemory = memStats.totalActualMemory; + DWORD deltaReportedSize = reqSize - originalSize; + if (deltaReportedSize > 0) + { + memStats.accumulatedReportedMemory += deltaReportedSize; + memStats.accumulatedActualMemory += deltaReportedSize; + } + // prepare the allocation + FillWithPattern(u, unusedPattern, originalSize); + + // preserve globals + ClearGlobals(); + +#ifdef MEMDUMP + FILE *fp = fopen(SP_MMDUMP_LOGFILENAME, "at"); + fprintf(fp, "*[%d] %s (%d):\t\t%d->%d\t\t[%d]\n", currentAllocated, file, line, originalSize, reqSize, memStats.totalReportedMemory); + fclose(fp); +#endif + + return u->reportedAddress; +} + +// ----------------------------------------------------------------------- + +void SPMemoryManager::Dealloc(const char *file, const DWORD line, + const DWORD deallocType, void* reqAddress) +{ + SPAllocUnit *u = FindAllocUnit(reqAddress); + if (u == NULL) + { + Log("Request to deallocate RAM that was never allocated in %s" + " (%d)", GetSourceName(const_cast(file)), line); + return; + } + + ValidateUnit(u); + + if(!((deallocType == SP_ALLOC_DELETE && u->allocationType == SP_ALLOC_NEW) || + (deallocType == SP_ALLOC_DELETE_ARRAY && u->allocationType == SP_ALLOC_NEW_ARRAY) || + (deallocType == SP_ALLOC_FREE && u->allocationType == SP_ALLOC_MALLOC) || + (deallocType == SP_ALLOC_FREE && u->allocationType == SP_ALLOC_CALLOC) || + (deallocType == SP_ALLOC_FREE && u->allocationType == SP_ALLOC_REALLOC) || + (deallocType == SP_ALLOC_FREE && u->allocationType == SP_ALLOC_STRDUP) || + (deallocType == SP_ALLOC_UNKNOWN))) + { + Log("Allocation/deallocation mismatch in %s (%d)", + GetSourceName(const_cast(file)), line); + } + + // unfortunately, code has no effect because of MS debug libs :( + FillWithPattern(u, releasedPattern); + + free(u->actualAddress); + + DWORD hashIndex = ((DWORD) u->reportedAddress >> 4) & SP_MM_HASHSIZEM; + if (hashTable[hashIndex] == u) + hashTable[hashIndex] = u->next; + else + { + if (u->prev) u->prev->next = u->next; + if (u->next) u->next->prev = u->prev; + } + + // remove this allocation from our stats + memStats.totalReportedMemory -= u->reqSize; + memStats.totalActualMemory -= u->actualSize; + memStats.totalAllocUnitCount--; + + memset(u, 0, sizeof(SPAllocUnit)); + u->next = units; + units = u; + + // preserve globals + ClearGlobals(); + +#ifdef MEMDUMP + FILE *fp = fopen(SP_MMDUMP_LOGFILENAME, "at"); + fprintf(fp, "-[%d] %s (%d):\t\t%d\t\t[%d]\n", currentAllocated, file, line, u->reqSize, memStats.totalReportedMemory); + fclose(fp); +#endif + +} + +// ----------------------------------------------------------------------- + +void SPMemoryManager::Log(char *message, ...) +{ + va_list l; + va_start(l, message); + + char *lasterr = (char *)malloc (1024); + vsprintf(lasterr, message, l); + + FILE *fp = fopen(SP_MM_LOGFILENAME, "at"); + vfprintf(fp, message, l); + fprintf(fp, "\n"); + fclose(fp); + + free(lasterr); +} + +// ----------------------------------------------------------------------- + +void SPMemoryManager::BeginLog() +{ + FILE *fp = fopen(SP_MM_LOGFILENAME, "wt"); + + fprintf(fp, "SigmaPlayer memory manager report\n"); + fprintf(fp, "--------------------------------------------------------" + "------------------------\n\n"); + fclose(fp); + +#ifdef MEMDUMP + fp = fopen(SP_MMDUMP_LOGFILENAME, "wt"); + fclose(fp); +#endif + +} + +// ----------------------------------------------------------------------- + +void SPMemoryManager::LeakReport() +{ + // log final stats + LogStats(); + + FILE *fp = fopen(SP_MM_LOGFILENAME, "at"); + if (!fp) return; + + static char timeString[25]; + memset(timeString, 0, sizeof(timeString)); + time_t t = time(NULL); + struct tm *tme = localtime(&t); + + fprintf(fp, "\n------------------------------------------------------" + "--------------------------\n"); + fprintf(fp, "Memory leak report for: %02d/%02d/%04d %02d:%02d:%02d\n" + , tme->tm_mon + 1, tme->tm_mday, tme->tm_year + 1900, + tme->tm_hour, tme->tm_min, tme->tm_sec); + fprintf(fp, "--------------------------------------------------------" + "------------------------\n\n"); + + if (memStats.totalAllocUnitCount) + fprintf(fp, "%d memory leak%s found:\n", memStats. + totalAllocUnitCount, memStats.totalAllocUnitCount == 1 ? "":"s"); + else + { + fprintf(fp, "No memory leaks found.\n"); + + // free our memory + if (unitsBuffer) + { + for (DWORD i = 0; i < unitsBufferSize; i++) + free(unitsBuffer[i]); + free(unitsBuffer); + unitsBuffer = 0; + unitsBufferSize = 0; + units = NULL; + } + } + fprintf(fp, "\n"); + + if(memStats.totalAllocUnitCount) + DumpAllocs(fp); + fclose(fp); +} + +// ----------------------------------------------------------------------- + +void SPMemoryManager::LogStats() +{ + FILE *fp = fopen(SP_MM_LOGFILENAME, "at"); + if (!fp) return; + + static char timeString[25]; + memset(timeString, 0, sizeof(timeString)); + time_t t = time(NULL); + struct tm *tme = localtime(&t); + + fprintf(fp, "\nMemory stats for: %02d/%02d/%04d %02d:%02d:%02d\n", + tme->tm_mon + 1, tme->tm_mday, tme->tm_year + 1900, tme->tm_hour, + tme->tm_min, tme->tm_sec); + fprintf(fp, "--------------------------------------------------------" + "------------------------\n\n"); + + fprintf(fp, "Total allocated memory : 0x%08X (%10u)\n", + memStats.totalActualMemory, memStats.totalActualMemory); + fprintf(fp, "Total reported memory : 0x%08X (%10u)\n", + memStats.totalReportedMemory, memStats.totalReportedMemory); + fprintf(fp, "Total allocation units : 0x%08X (%10u)\n\n", + memStats.totalAllocUnitCount, memStats.totalAllocUnitCount); + + fprintf(fp, "Peak allocated memory : 0x%08X (%10u)\n", + memStats.peakActualMemory, memStats.peakActualMemory); + fprintf(fp, "Peak reported memory : 0x%08X (%10u)\n", + memStats.peakReportedMemory, memStats.peakReportedMemory); + fprintf(fp, "Peak allocation units : 0x%08X (%10u)\n\n", + memStats.peakAllocUnitCount, memStats.peakAllocUnitCount); + + fprintf(fp, "Accum allocated memory : 0x%08X (%10u)\n", + memStats.accumulatedActualMemory, + memStats.accumulatedActualMemory); + fprintf(fp, "Accum reported memory : 0x%08X (%10u)\n", + memStats.accumulatedReportedMemory, + memStats.accumulatedReportedMemory); + fprintf(fp, "Accum allocation units : 0x%08X (%10u)\n", + memStats.accumulatedAllocUnitCount, + memStats.accumulatedAllocUnitCount); + + fclose(fp); +} + +// ----------------------------------------------------------------------- + +void SPMemoryManager::DumpAllocs(FILE *fp) +{ + fprintf(fp, "Alloc. Addr Size Addr Size " + " \n"); + fprintf(fp, "Number Reported Required Actual Actual Unus" + "ed Method Allocated by \n"); + fprintf(fp, "------ ---------- ---------- ---------- ---------- -----" + "----- -------- -----------------\n"); + + for (DWORD i = 0; i < 4096; i++) + { + SPAllocUnit *ptr = hashTable[i]; + while(ptr) + { + fprintf(fp, "%06d 0x%08X 0x%08X 0x%08X 0x%08X 0x%08X %-8s %s" + " (%d)\n", + ptr->allocationNumber, + reinterpret_cast(ptr->reportedAddress), + ptr->reqSize, + reinterpret_cast(ptr->actualAddress), + ptr->actualSize, + CalculateUnused(ptr), + allocTypes[ptr->allocationType], + GetSourceName(ptr->sourceFile), + ptr->sourceLine); + + ptr = ptr->next; + } + } +} + +void SPMemoryManager::DumpLastAlloc(FILE *fp) +{ + for (DWORD i = 0; i < 4096; i++) + { + SPAllocUnit *ptr = hashTable[i]; + while(ptr) + { + if (ptr->allocationNumber == currentAllocated) + { + fprintf(fp, "%06d 0x%08X 0x%08X 0x%08X 0x%08X 0x%08X %-8s %s" + " (%d)\n", + ptr->allocationNumber, + reinterpret_cast(ptr->reportedAddress), + ptr->reqSize, + reinterpret_cast(ptr->actualAddress), + ptr->actualSize, + CalculateUnused(ptr), + allocTypes[ptr->allocationType], + GetSourceName(ptr->sourceFile), + ptr->sourceLine); + return; + } + ptr = ptr->next; + } + } +} + +void SPMemoryManager::TEST_DUMP() +{ + static int j = -1; + static SPAllocUnit au[10000]; + + FILE *fp = fopen(SP_MM_LOGFILENAME, "at"); + if (!fp) return; + fprintf(fp, "\n\n ------- MEM.DIFFERENCE --------\n"); + + if (j < 0) + { + fprintf(fp, "STAGE 0\n\n"); + j = 0; + for (DWORD i = 0; i < 4096; i++) + { + SPAllocUnit *ptr = hashTable[i]; + while(ptr) + { + memcpy(&au[j++], ptr, sizeof(SPAllocUnit)); + ptr = ptr->next; + if (j > 10000) + { + exit(0); + } + } + } + } else + { + fprintf(fp, "STAGE 1\n\n"); + for (DWORD i = 0; i < 4096; i++) + { + SPAllocUnit *ptr = hashTable[i]; + while(ptr) + { + bool was = false; + for (int k = 0; k < j; k++) + { + if (au[k].allocationNumber == ptr->allocationNumber) + { + was = true; + au[k].next = (SPAllocUnit *)1; + break; + } + } + if (!was) + { + fprintf(fp, "%06d 0x%08X 0x%08X 0x%08X 0x%08X 0x%08X %-8s %s" + " (%d)\n", + ptr->allocationNumber, + reinterpret_cast(ptr->reportedAddress), + ptr->reqSize, + reinterpret_cast(ptr->actualAddress), + ptr->actualSize, + CalculateUnused(ptr), + allocTypes[ptr->allocationType], + GetSourceName(ptr->sourceFile), + ptr->sourceLine); + } + ptr = ptr->next; + } + } + + fprintf(fp, "\n-------------------------------------------\n\n"); + for (int k = 0; k < j; k++) + { + if ((DWORD)au[k].next != 1) + { + SPAllocUnit *ptr = &au[k]; + fprintf(fp, "%06d 0x%08X 0x%08X 0x%08X 0x%08X 0x%08X %-8s %s" + " (%d)\n", + ptr->allocationNumber, + reinterpret_cast(ptr->reportedAddress), + ptr->reqSize, + reinterpret_cast(ptr->actualAddress), + ptr->actualSize, + CalculateUnused(ptr), + allocTypes[ptr->allocationType], + GetSourceName(ptr->sourceFile), + ptr->sourceLine); + } + } + + j = -1; + } + fclose(fp); +} + +// ----------------------------------------------------------------------- + +void SPMemoryManager::SetOwner(const char *file, const DWORD line) +{ + srcFile = const_cast(file); + srcLine = line; +} + +// ----------------------------------------------------------------------- + +void SPMemoryManager::ClearGlobals() +{ + srcFile = ""; + srcLine = NULL; +} + +// ----------------------------------------------------------------------- + +char* SPMemoryManager::GetSourceName(char *sourceFile) +{ + char *ptr = strrchr(sourceFile, '\\'); + if(ptr) return ptr + 1; + return sourceFile; +} + +// ----------------------------------------------------------------------- + +SPAllocUnit* SPMemoryManager::FindAllocUnit(void *reqAddress) +{ + if(!reqAddress) + return NULL; + + DWORD hashIndex = ((DWORD)reqAddress >> 4) & SP_MM_HASHSIZEM; + SPAllocUnit *ptr = hashTable[hashIndex]; + while(ptr) + { + if(ptr->reportedAddress == reqAddress) + return ptr; + ptr = ptr->next; + } + return NULL; +} + +// ----------------------------------------------------------------------- + +void SPMemoryManager::FillWithPattern(SPAllocUnit *u, DWORD pattern, + DWORD originalSize) +{ + DWORD i; + DWORD *lptr = reinterpret_cast(reinterpret_cast + (u->reportedAddress) + originalSize); + int length = u->reqSize - originalSize; + if(length > 0) + { + for(i = 0; i<(static_cast(length) >> 2); i++, lptr++) + { + *lptr = pattern; + } + // fill the remainder + DWORD shiftCount = 0; + char *cptr = reinterpret_cast(lptr); + for(i = 0; i<(static_cast(length) & 0x3); i++, cptr++, + shiftCount += 8) + { + *cptr = (char)((pattern & (0xff << shiftCount)) >> shiftCount); + } + } + + // write in the prefix/postfix bytes + DWORD *pre = reinterpret_cast(u->actualAddress); + DWORD *post = reinterpret_cast(reinterpret_cast + (u->actualAddress) + u->actualSize - borderSize * sizeof(DWORD)); + for(i = 0; i < borderSize; i++, pre++, post++) + { + *pre = prefixPattern; + *post = postfixPattern; + } +} + +// ----------------------------------------------------------------------- + +DWORD SPMemoryManager::CalculateUnused(SPAllocUnit *u) +{ + const DWORD *ptr = (const DWORD *)u->reportedAddress; + DWORD count = 0; + for(DWORD i = 0; i < u->reqSize; i += sizeof(DWORD), ptr++) + if(*ptr == unusedPattern) count += sizeof(DWORD); + return count; +} + +// ----------------------------------------------------------------------- + +BOOL SPMemoryManager::ValidateUnit(SPAllocUnit *u) +{ + // make sure the borders are untouched + DWORD *pre = reinterpret_cast(u->actualAddress); + DWORD *post = reinterpret_cast(reinterpret_cast + (u->actualAddress) + u->actualSize - borderSize * sizeof(DWORD)); + BOOL errorFlag = false; + + for(DWORD i = 0; i < borderSize; i++, pre++, post++) + { + if (*pre != prefixPattern) + { + Log("A memory allocation unit was corrupt because of an underrun:"); + DumpUnit(u); + errorFlag = true; + } + + if (*post != postfixPattern) + { + Log("A memory allocation unit was corrupt because of an overrun:"); + DumpUnit(u); + errorFlag = true; + } + } + return !errorFlag; +} + +// ----------------------------------------------------------------------- + +void SPMemoryManager::DumpUnit(SPAllocUnit *u) +{ + Log("Address (reported): %010p", u->reportedAddress); + Log("Address (actual) : %010p", u->actualAddress); + Log("Size (required) : 0x%08X", u->reqSize); + Log("Size (actual) : 0x%08X", u->actualSize); + Log("Owner : %s(%d)", u->sourceFile, u->sourceLine); + Log("Allocation type : %s", allocTypes[u->allocationType]); + Log("Allocation number : %d", u->allocationNumber); +} + +// ----------------------------------------------------------------------- + +void SPMemoryManager::OutOfMemory() +{ + Log("Ran out of memory in %s (%d)!", + GetSourceName(srcFile), srcLine); + SPMemoryManager::GetHandle().LeakReport(); + abort(); +} + +// ----------------------------------------------------------------------- + +#endif // SP_MEMDEBUG \ No newline at end of file diff --git a/src/libsp/win32/sp_module.cpp b/src/libsp/win32/sp_module.cpp new file mode 100644 index 0000000..631bf19 --- /dev/null +++ b/src/libsp/win32/sp_module.cpp @@ -0,0 +1,100 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - Module system support source file. + * For Win32. + * \file sp_module.cpp + * \author bombur + * \version 0.1 + * \date 10.12.2008 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#include +#include + +extern "C" +{ + +// portions of code from uClibc... +#ifdef COMPILE_MODULE + +extern int main(int argc, char **argv, char **envp); + +__declspec( dllexport ) void ModuleLink(char *fname, char *arg) +{ + char *argv[2]; + argv[0] = fname; + argv[1] = arg; + main(2, argv, NULL); +} + +BOOL WINAPI DllMain(HINSTANCE , DWORD , LPVOID) +{ + return TRUE; +} + +void module_wait() +{ +} + +#else ///////////////////////////////////////////////////////////////////////// + +extern "C" +{ + typedef void (*ModuleLink_decl)(char *, char *); + ModuleLink_decl ModuleLink; +} + +int module_binary_load(char *fname, char *arg) +{ + char *fpath = (fname[0] == '/') ? fname + 1 : fname; + HMODULE h = LoadLibrary(fpath); + if (h == NULL) + { + DWORD err = GetLastError(); + printf("Module: Load error = %d.\n", err); + return -1; + } + + ModuleLink = (ModuleLink_decl)GetProcAddress(h, "ModuleLink"); + if (ModuleLink == NULL) + return -1; + ModuleLink(fname, arg); + + return (int)h; +} + +int module_binary_unload(int pid) +{ + HMODULE h = (HMODULE)pid; + return FreeLibrary(h) ? 0 : -1; +} + +void module_copy_func(void *from, void *to) +{ + #define JMP_OPCODE_SIZE 5 + ULONGLONG diff = (DWORD)from - (DWORD)to - JMP_OPCODE_SIZE; + *((ULONGLONG *)to) = 0xE9 | (diff << 8); +} + +void module_clear_func(void *func) +{ + *((ULONGLONG *)func) = 0; +} + +#endif +} \ No newline at end of file diff --git a/src/libsp/win32/win32-stuff.c b/src/libsp/win32/win32-stuff.c new file mode 100644 index 0000000..e3e266b --- /dev/null +++ b/src/libsp/win32/win32-stuff.c @@ -0,0 +1,247 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - win32 compatibility stuff... + * \file win32-stuff.c + * \author bombur + * \version 0.1 + * \date 4.05.2004 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#include +#undef _WINSOCKAPI_ +#include "win32-stuff.h" +#define _WINSOCKAPI_ // no timeval +#pragma warning (disable : 4115) +#include +#include +#pragma warning (default : 4115) +#include + +typedef void (*sigaction_type) (int, siginfo_t *, void *); + +static HANDLE sigathr = NULL; +static DWORD sigathr_id = 0; +static sigaction_type sa_siga = NULL; + + +DWORD WINAPI SigThreadProc(LPVOID lpParameter) +{ + // wait 10 sec and call sigaction + Sleep(10000); + if (sa_siga != NULL) + { + siginfo_t sigi; + memset(&sigi, 0, sizeof(sigi)); + sigi.si_pid = (int)lpParameter; + sa_siga(SIGCHLD, &sigi, 0); + } + return 0; +} + + +#undef _WINSOCKAPI_ + +#pragma comment(lib, "winmm.lib") + +#pragma warning (disable : 4100) +#pragma warning (disable : 4054) + +extern int fcntl (int __fd, int __cmd, ...) +{ + return 0; +} + +void usleep(unsigned int usecs) +{ + Sleep(usecs / 1000); +} + +unsigned int sleep (unsigned int __seconds) +{ + Sleep(__seconds); + return 0; +} + +unsigned int alarm (unsigned int seconds) +{ + struct itimerval old, new; + new.it_interval.tv_usec = 0; + new.it_interval.tv_sec = 0; + new.it_value.tv_usec = 0; + new.it_value.tv_sec = (long int) seconds; + if (setitimer (ITIMER_REAL, &new, &old) < 0) + return 0; + else + return (unsigned int)old.it_value.tv_sec; +} + +int kill (__pid_t __pid, int __sig) +{ + //if (__pid == 1) + { + TerminateThread(sigathr, 0); + sigathr = NULL; + } + return 0; +} +__pid_t vfork (void) +{ + int pid = 1; + // we currently won't allow more than 1 childs... + if (sigathr != NULL) + { + TerminateThread(sigathr, 0); + sigathr = NULL; + } + + sigathr = CreateThread(NULL, 0, SigThreadProc, (void *)pid, + 0, &sigathr_id); + return pid; +} + +__pid_t waitpid (__pid_t __pid, int *__stat_loc, int __options) +{ + return 1; +} + +int poll (struct pollfd *__fds, nfds_t __nfds, int __timeout) +{ + usleep(10000); + return 0; +} + +int openpty (int *__amaster, int *__aslave, char *__name, + struct termios *__termp, struct winsize *__winp) +{ + *__amaster = 0; + *__aslave = 0; + if (__name != NULL) + *__name = '\0'; + return 0; +} + +int tcgetattr (int __fd, struct termios *__termios_p) +{ + return 0; +} + +int tcsetattr (int __fd, int __optional_actions, + struct termios *__termios_p) +{ + return -1; +} + +int setitimer (enum __itimer_which __which, const struct itimerval *__new, struct itimerval *__old) +{ + return 0; +} + +int sigaction (int __sig, struct sigaction *__act, struct sigaction *__oact) +{ + if (__act != NULL) + { + sa_siga = __act->sa_sigaction; + } + return 0; +} + +int gettimeofday (struct timeval *__tv, struct timezone *__tz) +{ + DWORD ms = timeGetTime(); + __tv->tv_sec = ms / 1000; + __tv->tv_usec = (ms % 1000) * 1000; + return 0; +} + + +void *alloca (unsigned __size) +{ + return _alloca(__size); +} + +int fsync(int fd) +{ + return 0; +} + +int fchdir (int __fd) +{ + return 0; +} + +#ifndef _WINSOCKAPI_ + +int select (int __nfds, fd_set *__readfds, + fd_set *__writefds, fd_set *__exceptfds, + struct timeval *__timeout) +{ + return 0; +} + +#endif + +#if 0 +void *mmap (void *__addr, int __len, int __prot, + int __flags, int __fd, int __offset) +{ + return malloc(__len); +} + +int munmap (void *__addr, int __len) +{ + free(__addr); + return 0; +} +#endif + +int sysinfo (struct sysinfo *__info) +{ + if (__info != NULL) + { + MEMORYSTATUS mstat; + memset(__info, 0, sizeof(struct sysinfo)); + + GlobalMemoryStatus (&mstat); + + __info->uptime = clock(); + __info->totalram = mstat.dwTotalPhys; + __info->freeram = mstat.dwAvailPhys; + __info->procs = 1; + } + return 0; +} + +int ioctl (int __fd, unsigned long int __request, ...) +{ + return 0; +} + +int ftruncate(int fd, off_t length) +{ + return 0; +} + +__int64 strtoll (const char *__nptr, char ** __endptr, int __base) +{ + return (__int64)strtol(__nptr, __endptr, __base); +} + +unsigned __int64 strtoull (const char * __nptr, char **__endptr, int __base) +{ + return (unsigned __int64)strtoul(__nptr, __endptr, __base); +} diff --git a/src/media.cpp b/src/media.cpp new file mode 100644 index 0000000..7512f4b --- /dev/null +++ b/src/media.cpp @@ -0,0 +1,647 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - media access source file. Uses libdvdnav. + * \file media.cpp + * \author bombur + * \version 0.1 + * \date 2.08.2004 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "player.h" +#include "dvd.h" +#include "audio.h" +#include "video.h" +#include "cdda.h" +#include "media.h" + + +static int cur_bufpos = 0, cur_bufleft = -1; +static int cur_chunkleft = -1; +static BYTE *cur_copybuf = NULL; +static MEDIA_TYPES media_type = MEDIA_TYPE_UNKNOWN; +// fip 'disc' index +static int fipdisc_i = 0; +static int update_counter = 0; +static int fd = -1; +static int numread = 0; +static LONGLONG file_pos = 0; + +#ifdef INTERNAL_VIDEO_PLAYER +static VIDEO_CHUNK_TYPE chtype = VIDEO_CHUNK_UNKNOWN; +#endif + +int media_open(const char *path, MEDIA_TYPES mtype) +{ + cur_bufleft = -1; + media_type = mtype; + numread = 0; + file_pos = 0; + if (mtype == MEDIA_TYPE_DVD) + { + MPEG_PACKET_LENGTH = 2048; + MPEG_NUM_PACKETS = 512; + + if (dvd_open(path) < 0) + return -1; + + } + else if (mtype == MEDIA_TYPE_CDDA) + { + MPEG_PACKET_LENGTH = 2352; + MPEG_NUM_PACKETS = 512; + + if (!cdda_open()) + { + msg_error("Media: Couldn't open CDDA: %s\n", path); + return -1; + } + + } +#ifdef INTERNAL_VIDEO_PLAYER + else if (mtype == MEDIA_TYPE_VIDEO) + { + MPEG_PACKET_LENGTH = 0; // use variable packet size + MPEG_NUM_PACKETS = 2048; + + if (!video_open(path)) + { + video_close(); + msg_error("Media: Couldn't open video: %s\n", path); + return -1; + } + } +#endif +#ifdef INTERNAL_AUDIO_PLAYER + else if (mtype == MEDIA_TYPE_AUDIO) + { + MPEG_PACKET_LENGTH = 4096; + MPEG_NUM_PACKETS = 512; + + if (!audio_open(path)) + { + audio_close(); + msg_error("Media: Couldn't open audio: %s\n", path); + return -1; + } + } +#endif + + cur_bufpos = 0; + cur_bufleft = mpeg_getbufsize(MPEG_BUFFER_1); + cur_chunkleft = cur_bufleft; + cur_copybuf = NULL; + + fipdisc_i = 0; + fip_write_special(FIP_SPECIAL_CIRCLE_1 + fipdisc_i, 1); + + return 1; +} + +void set_media_type(MEDIA_TYPES mtype) +{ + media_type = mtype; +} + +inline int media_get_next_free(const MPEG_BUFFER which, MEDIA_EVENT *event, bool &update_fip) +{ + int ret = mpeg_find_free_blocks(which); + if (ret == 0) + { + // the buffer is not ready yet - wait... + *event = MEDIA_EVENT_NOP; + return 0; + } + cur_bufpos = 0; + cur_bufleft = mpeg_getbufsize(which); + cur_chunkleft = cur_bufleft; + + if (update_counter++ >= 8) + { + update_fip = true; + update_counter = 0; + } + return ret; +} + +//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +//extern int num_packets; +//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +int media_get_next_block(BYTE **buf, MEDIA_EVENT *event, int *len) +{ + if (cur_bufleft < 0) + { + cur_bufleft = mpeg_getbufsize(MPEG_BUFFER_1); + } + bool update_fip = false; + + *event = MEDIA_EVENT_OK; + + if (media_type == MEDIA_TYPE_DVD) + { + *len = MPEG_PACKET_LENGTH; + if (cur_bufleft < MPEG_PACKET_LENGTH) + { + if (media_get_next_free(MPEG_BUFFER_1, event, update_fip) == 0) + return 0; + } + BYTE *tmpbuf = mpeg_getcurbuf(MPEG_BUFFER_1); + + BYTE *bb = tmpbuf + cur_bufpos; + int ret = dvd_get_next_block(&bb, (int *)event, len); + if (ret < 0) + return -1; + else if (ret) + media_accept_block(); + *buf = bb; + } +#ifdef INTERNAL_VIDEO_MPEG_PLAYER + else if (media_type == MEDIA_TYPE_MPEG) + { + *len = MPEG_PACKET_LENGTH; + if (cur_bufleft < MPEG_PACKET_LENGTH) + { + if (media_get_next_free(MPEG_BUFFER_1, event, update_fip) == 0) + return 0; + } + BYTE *tmpbuf = mpeg_getcurbuf(MPEG_BUFFER_1); + + if (cur_bufpos == 0) + { + //numread = read(fd, tmpbuf, mpeg_getbufsize(MPEG_BUFFER_1)); + numread = video_read(tmpbuf, mpeg_getbufsize(MPEG_BUFFER_1)); + file_pos += numread; + if (update_counter++ >= 8) + { + update_fip = true; + update_counter = 0; + } + // sorry, we won't show the last few bytes... + numread &= ~(MPEG_PACKET_LENGTH - 1); + MPEG_PACKET_LENGTH = numread; + } + if (cur_bufpos + MPEG_PACKET_LENGTH > numread || numread < 1) + { + if (numread < 1 || video_eof()) + { + *buf = tmpbuf + cur_bufpos; + *event = MEDIA_EVENT_STOP; + return 1; + } + else + { + *event = MEDIA_EVENT_NOP; // retry? + return 0; + } + } + *buf = tmpbuf + cur_bufpos; + media_accept_block(); + } +#endif + else if (media_type == MEDIA_TYPE_CDDA) + { + *len = MPEG_PACKET_LENGTH; + if (cur_bufleft < MPEG_PACKET_LENGTH) + { + if (media_get_next_free(MPEG_BUFFER_1, event, update_fip) == 0) + return 0; + } + BYTE *tmpbuf = mpeg_getcurbuf(MPEG_BUFFER_1); + + if (cur_bufpos == 0) + { + numread = cdda_read(tmpbuf, mpeg_getbufsize(MPEG_BUFFER_1) / MPEG_PACKET_LENGTH); + numread *= MPEG_PACKET_LENGTH; + if (update_counter++ >= 6) + { + update_fip = true; + update_counter = 0; + } + } + else if (cur_bufpos + MPEG_PACKET_LENGTH > numread) + { + if (cdda_feof()) + { + *event = MEDIA_EVENT_STOP; + } + else + { + *event = MEDIA_EVENT_NOP; // retry? + return 0; + } + } + *buf = tmpbuf + cur_bufpos; + media_accept_block(); + } +#ifdef INTERNAL_VIDEO_PLAYER + else if (media_type == MEDIA_TYPE_VIDEO) + { + if (cur_bufleft < 1) + { + if (media_get_next_free(MPEG_BUFFER_1, event, update_fip) == 0) + return 0; + cur_copybuf = NULL; + } + + BYTE *tmpbuf = mpeg_getcurbuf(MPEG_BUFFER_1); +fragmented: + if (cur_bufleft < 16) + { + // read one more buffer + /* + numread = mpeg_getbufsize(MPEG_BUFFER_1) - cur_bufleft; + if (cur_copybuf == NULL) + cur_copybuf = tmpbuf + cur_bufpos; + int len = cur_bufleft; + if (media_get_next_free(MPEG_BUFFER_1, event, update_fip) == 0) + return 0; + tmpbuf = mpeg_getcurbuf(MPEG_BUFFER_1); + *buf = tmpbuf + cur_bufpos; + memcpy(tmpbuf, cur_copybuf, len); + cur_copybuf = NULL; + if ((numread = video_read(tmpbuf + len, numread)) == 0) + { + *event = MEDIA_EVENT_STOP; + return 1; + } + numread += len; + video_shiftpos(len); + */ + + if (media_skip_buffer(&tmpbuf) == 0) + return 0; + numread = mpeg_getbufsize(MPEG_BUFFER_1); + + } else + { + *buf = tmpbuf + cur_bufpos; + if (cur_bufpos == 0) + { + numread = mpeg_getbufsize(MPEG_BUFFER_1); + } + } + + for (;;) + { + chtype = video_getnext(tmpbuf + cur_bufpos, numread, &cur_bufpos, &cur_bufleft, &MPEG_PACKET_LENGTH); + if (chtype == VIDEO_CHUNK_FRAGMENT) + goto fragmented; + if (chtype != VIDEO_CHUNK_UNKNOWN) + break; + if (media_skip_buffer(&tmpbuf) == 0) + return 0; + numread = mpeg_getbufsize(MPEG_BUFFER_1); + } + *buf = tmpbuf + cur_bufpos; + + if (chtype == VIDEO_CHUNK_EOF) + { + *event = MEDIA_EVENT_STOP; + return 1; + } + if (chtype == VIDEO_CHUNK_RECOVERY) + { + if (cur_bufpos > 0) + media_skip_buffer(&tmpbuf); + return 0; + } + + else if (chtype == VIDEO_CHUNK_AUDIO) + *event = MEDIA_EVENT_AUDIO; + else if (chtype == VIDEO_CHUNK_VIDEO) + *event = MEDIA_EVENT_VIDEO; + else if (chtype == VIDEO_CHUNK_AUDIO_PARTIAL) + *event = MEDIA_EVENT_AUDIO_PARTIAL; + else if (chtype == VIDEO_CHUNK_VIDEO_PARTIAL) + *event = MEDIA_EVENT_VIDEO_PARTIAL; + if (update_counter++ >= 6) + { + update_fip = true; + update_counter = 0; + } + *len = MPEG_PACKET_LENGTH; + MPEG_PACKET_LENGTH = PAD_EVEN(MPEG_PACKET_LENGTH); + media_accept_block(); + + if ((*event & MEDIA_EVENT_PARTIAL) == 0) + { + cur_chunkleft = cur_bufleft; + } + } +#endif +#ifdef INTERNAL_AUDIO_PLAYER + else if (media_type == MEDIA_TYPE_AUDIO) + { + if (cur_bufleft < 1) + { + if (media_get_next_free(MPEG_BUFFER_1, event, update_fip) == 0) + return 0; + } + + BYTE *tmpbuf = mpeg_getcurbuf(MPEG_BUFFER_1); + *buf = tmpbuf + cur_bufpos; + if (cur_bufpos == 0) + { + numread = mpeg_getbufsize(MPEG_BUFFER_1); + if (audio_read(tmpbuf, &numread) == 0) + { + *event = MEDIA_EVENT_STOP; + return 1; + } + } + if (cur_bufpos + MPEG_PACKET_LENGTH > numread) + *len = MAX(numread - cur_bufpos, 0); + else + *len = MPEG_PACKET_LENGTH; + + *buf = tmpbuf + cur_bufpos; + + if (update_counter++ >= 6) + { + update_fip = true; + update_counter = 0; + } + media_accept_block(); + + } +#endif + + // update FIP + if (update_fip) + { + media_update_fip(); + } + + return 1; +} + +int media_update_fip() +{ + fip_write_special(FIP_SPECIAL_CIRCLE_1 + fipdisc_i, 0); + fipdisc_i++; + if (fipdisc_i >= 12) + fipdisc_i = 0; + fip_write_special(FIP_SPECIAL_CIRCLE_1 + fipdisc_i, 1); + return 0; +} + +int media_skip_buffer(BYTE **buf) +{ + int ret = mpeg_find_free_blocks(MPEG_BUFFER_1); + if (ret == 0) // sorry, out of buffers... + return 0; + cur_bufpos = 0; + cur_bufleft = mpeg_getbufsize(MPEG_BUFFER_1); + cur_chunkleft = cur_bufleft; + if (buf != NULL) + *buf = mpeg_getcurbuf(MPEG_BUFFER_1); + return 1; +} + +int media_seek_curleft() +{ +#ifdef INTERNAL_VIDEO_PLAYER + video_lseek(-cur_chunkleft, SEEK_CUR); + media_skip_buffer(NULL); +#endif + return 0; +} + +LONGLONG media_get_filepos() +{ + return file_pos; +} + +void media_set_filepos(LONGLONG fp) +{ + file_pos = fp; +} + +int media_read_block(BYTE **buf, MEDIA_EVENT *event, int *len) +{ +#ifdef INTERNAL_VIDEO_PLAYER + *event = MEDIA_EVENT_NOP; + if (media_type == MEDIA_TYPE_VIDEO) + { + if (len == NULL || *len == 0) + { + *buf = mpeg_getcurbuf(MPEG_BUFFER_1); + *event = MEDIA_EVENT_STOP; + return 1; + } + + bool update_fip = false; + MPEG_PACKET_LENGTH = PAD_EVEN(*len); + if (cur_bufleft < 16 || (MPEG_PACKET_LENGTH > cur_bufleft)) + { + if (media_get_next_free(MPEG_BUFFER_1, event, update_fip) == 0) + return 0; + } + BYTE *tmpbuf = mpeg_getcurbuf(MPEG_BUFFER_1); + *buf = tmpbuf + cur_bufpos; + int numread = MIN(cur_bufleft, MPEG_PACKET_LENGTH); + if ((numread = video_read(*buf, numread)) == 0) + { + *event = MEDIA_EVENT_STOP; + return 1; + } + if (numread < *len) + { + *len = numread; + MPEG_PACKET_LENGTH = numread; + } + *event = MEDIA_EVENT_OK; + media_accept_block(); + media_update_fip(); + return 1; + } +#endif + return -1; +} + +int media_accept_block() +{ + cur_bufleft -= MPEG_PACKET_LENGTH; + cur_bufpos += MPEG_PACKET_LENGTH; + return 1; +} + +int media_free_block(BYTE *data) +{ + if (media_type == MEDIA_TYPE_DVD) + { + if (dvd_free_block(data) < 0) + return -1; + } + else if (media_type == MEDIA_TYPE_MPEG) + { + } + else if (media_type == MEDIA_TYPE_CDDA) + { + } + else if (media_type == MEDIA_TYPE_VIDEO) + { + } + else if (media_type == MEDIA_TYPE_AUDIO) + { + } + return 1; +} + +int media_close() +{ + if (media_type == MEDIA_TYPE_DVD) + { + if (dvd_close() < 0) + return -1; + } +#ifdef INTERNAL_VIDEO_MPEG_PLAYER + else if (media_type == MEDIA_TYPE_MPEG) + { + video_close(); + } +#endif + else if (media_type == MEDIA_TYPE_CDDA) + { + // we'll close when disc changes + //cdda_close(); + } +#ifdef INTERNAL_VIDEO_PLAYER + else if (media_type == MEDIA_TYPE_VIDEO) + { + video_close(); + } +#endif +#ifdef INTERNAL_AUDIO_PLAYER + else if (media_type == MEDIA_TYPE_AUDIO) + { + audio_close(); + } +#endif + return 1; +} + +int media_seek(int blockid, int from) +{ + if (media_type == MEDIA_TYPE_DVD) + { + // not used + return -1; + } + else if (media_type == MEDIA_TYPE_MPEG) + { + if (lseek(fd, blockid * mpeg_getbufsize(MPEG_BUFFER_1), from) != 0) + return -1; + } + else if (media_type == MEDIA_TYPE_CDDA) + { + // TODO: + return -1; + } + else if (media_type == MEDIA_TYPE_VIDEO) + { + // TODO: + return -1; + } + else if (media_type == MEDIA_TYPE_AUDIO) + { + // TODO: + return -1; + } + return 1; +} + +int media_rewind() +{ + cur_bufpos = 0; + cur_bufleft = mpeg_getbufsize(MPEG_BUFFER_1); + cur_chunkleft = cur_bufleft; + + if (media_type == MEDIA_TYPE_DVD) + { + if (dvd_reset() < 0) + return -1; + } + else if (media_type == MEDIA_TYPE_MPEG) + { + lseek(fd, 0, SEEK_SET); + } + else if (media_type == MEDIA_TYPE_CDDA) + { + // TODO: + return -1; + } + else if (media_type == MEDIA_TYPE_VIDEO) + { + // TODO: + return -1; + } + else if (media_type == MEDIA_TYPE_AUDIO) + { + // TODO: + return -1; + } + return 1; +} + +const char *media_geterror() +{ + if (media_type == MEDIA_TYPE_DVD) + { + return dvd_error_string(); + } + else if (media_type == MEDIA_TYPE_MPEG) + { + static char tmp[15]; + sprintf(tmp, "%d", 0); + return tmp; + } + else if (media_type == MEDIA_TYPE_CDDA) + { + // TODO: + return ""; + } + else if (media_type == MEDIA_TYPE_VIDEO) + { + // TODO: + return ""; + } + else if (media_type == MEDIA_TYPE_AUDIO) + { + // TODO: + return ""; + } + return ""; + +} + diff --git a/src/media.h b/src/media.h new file mode 100644 index 0000000..b2f72dd --- /dev/null +++ b/src/media.h @@ -0,0 +1,87 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - input media header file + * \file media.h + * \author bombur + * \version 0.1 + * \date 12.10.2004 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#ifndef SP_MEDIA_H +#define SP_MEDIA_H + + +#ifdef __cplusplus +extern "C" { +#endif + +int media_open(const char *dvdpath, MEDIA_TYPES mtype); +void set_media_type(MEDIA_TYPES mtype); + +typedef enum +{ + MEDIA_EVENT_OK = 0, + MEDIA_EVENT_NOP = 1, + MEDIA_EVENT_STOP = 8, + MEDIA_EVENT_PARTIAL = 0x10, + + MEDIA_EVENT_AUDIO = 0x1000, + MEDIA_EVENT_VIDEO = 0x1001, + MEDIA_EVENT_AUDIO_PARTIAL = MEDIA_EVENT_AUDIO | MEDIA_EVENT_PARTIAL, + MEDIA_EVENT_VIDEO_PARTIAL = MEDIA_EVENT_VIDEO | MEDIA_EVENT_PARTIAL, +} MEDIA_EVENT; + +int media_get_next_block(BYTE **buf, MEDIA_EVENT *event, int *len); + +// call this if we needed the block +int media_accept_block(); + +int media_free_block(BYTE *data); + +/// used by AVI. +/// \todo: UGLY! +int media_read_block(BYTE **buf, MEDIA_EVENT *event, int *len); +int media_seek_curleft(); + +// called by cache if cache-miss happens +int media_skip_buffer(BYTE **buf); + +int media_close(); + +int media_seek(int blockid, int from); + +int media_rewind(); + +// used by mpeg player to seek 'bad' files +LONGLONG media_get_filepos(); +void media_set_filepos(LONGLONG); + +const char *media_geterror(); + +#ifndef DVDNAV_H_INCLUDED +typedef struct dvdnav_s dvdnav_t; +#endif + +int media_update_fip(); + + +#ifdef __cplusplus +} +#endif + +#endif // of SP_MEDIA_H diff --git a/src/mmsl/mmsl-file.cpp b/src/mmsl/mmsl-file.cpp new file mode 100644 index 0000000..5f8c086 --- /dev/null +++ b/src/mmsl/mmsl-file.cpp @@ -0,0 +1,149 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - MMSL file loader/saver impl. + * \file mmsl/mmsl-file.cpp + * \author bombur + * \version 0.1 + * \date 10.04.2010 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +static DWORD crc_table[256]; +static BOOL crc_was_init = 0; + +static void mmso_init_crc() +{ + DWORD c, poly; + unsigned n; + static const BYTE p[] = { 0,1,2,4,5,7,8,10,11,12,16,22,23,26 }; + + poly = 0L; + for (n = 0; n < sizeof(p); n++) + poly |= 1L << (31 - p[n]); + + for (n = 0; n < 256; n++) + { + c = (DWORD)n; + for(int k = 0; k < 8; k++) + c= (c & 1) ? poly ^ (c >> 1) : c >> 1; + crc_table[n] = c; + } + crc_was_init = 1; +} + +static DWORD mmso_crc(BYTE *str, int size) +{ + if (!crc_was_init) + mmso_init_crc(); + + DWORD crc32 = 0xffffffffL; + for(int i = 0; i < size;i++) + { + BYTE c = str[i]; + crc32 = crc_table[(crc32 & 0xff) ^ c] ^ (crc32 >> 8); + } + return (crc32 ^ 0xffffffffL); +} + + +BYTE *mmso_load(const char *fname, int *buflen) +{ + BYTE *buf = NULL; + FILE *fp = fopen(fname, "rb"); + if (fp != NULL) + { + fseek(fp, 0, SEEK_END); + *buflen = ftell(fp); + rewind(fp); + + MmslSerFileHeader hdr; + if (*buflen <= (int)sizeof(hdr)) + { + msg("Mmso: File %s is too small.\n", fname); + fclose(fp); + return NULL; + } + fread(&hdr, sizeof(hdr), 1, fp); + *buflen -= sizeof(hdr); + + if (memcmp(hdr.magic, "MMSO", 4) != 0 || hdr.ver > MMSO_VERSION) + { + msg("Mmso: Wrong file header or version (%d).\n", hdr.ver); + fclose(fp); + return NULL; + } + + buf = (BYTE *)SPmalloc(*buflen + 1); + if (buf != NULL) + fread(buf, *buflen, 1, fp); + + // now check crc + DWORD real_crc = mmso_crc(buf, *buflen); + if (real_crc != hdr.crc) + { + msg("Mmso: Wrong checksum!\n"); + fclose(fp); + SPSafeFree(buf); + return NULL; + } + + fclose(fp); + } + + return buf; +} + +BOOL mmso_save(const char *fname, BYTE *buf, int buflen) +{ + int prev_buflen; + BYTE *prev_buf = mmso_load(fname, &prev_buflen); + if (prev_buf != NULL) + { + BOOL is_the_same = (buflen == prev_buflen && memcmp(buf, prev_buf, buflen) == 0); + SPSafeFree(prev_buf); + if (is_the_same) + return TRUE; + } + + FILE *fp = fopen(fname, "wb"); + if (fp != NULL) + { + MmslSerFileHeader hdr; + memcpy(hdr.magic, "MMSO", 4); + hdr.ver = MMSO_VERSION; + hdr.crc = mmso_crc(buf, buflen); + fwrite(&hdr, sizeof(hdr), 1, fp); + + fwrite(buf, buflen, 1, fp); + fclose(fp); + } + + return TRUE; +} diff --git a/src/mmsl/mmsl-file.h b/src/mmsl/mmsl-file.h new file mode 100644 index 0000000..0a85fe7 --- /dev/null +++ b/src/mmsl/mmsl-file.h @@ -0,0 +1,91 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - MMSL file manager header file + * \file mmsl/mmsl-file.h + * \author bombur + * \version 0.1 + * \date 10.04.2010 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#ifndef SP_MMSL_FILE_H +#define SP_MMSL_FILE_H + +#define MMSO_VERSION 100 + +#ifdef WIN32 +#pragma pack(1) +#endif + +typedef struct ATTRIBUTE_PACKED +{ + char magic[4]; + DWORD ver; + DWORD crc; +} MmslSerFileHeader; + +typedef struct ATTRIBUTE_PACKED +{ + int num_var; + int var_buf_size; + int num_class; + int num_event; + int num_assignedobj; + int num_expr; + int num_lut; + int num_clut; + int num_assignedvar; + int num_cond; + int num_token; +} MmslSerHeader; + +typedef struct ATTRIBUTE_PACKED +{ + short ID; + short idx_vars1, idx_vars2; +} MmslSerClass; + +typedef struct ATTRIBUTE_PACKED +{ + short idx_triggertokens1, idx_triggertokens2; + short idx_conditions1, idx_conditions2; + short idx_execute1, idx_execute2; +} MmslSerEvent; + +typedef struct ATTRIBUTE_PACKED +{ + short idx_assigned_vars1, idx_assigned_vars2; +} MmslSerAssignedObject; + +typedef struct ATTRIBUTE_PACKED +{ + short idx_tokens1, idx_tokens2; +} MmslSerExpression; + +#ifdef WIN32 +#pragma pack() +#endif + + +/// Load MMSO file into the buffer (with buffer's memory allocation) +BYTE *mmso_load(const char *fname, int *buflen); + +/// Save buffer into the MMSO file +BOOL mmso_save(const char *fname, BYTE *buf, int buflen); + + +#endif // of SP_MMSL_FILE_H diff --git a/src/mmsl/mmsl-parser.cpp b/src/mmsl/mmsl-parser.cpp new file mode 100644 index 0000000..b9689b5 --- /dev/null +++ b/src/mmsl/mmsl-parser.cpp @@ -0,0 +1,1320 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - MMSL interpreter impl. + * \file mmsl/mmsl-parser.cpp + * \author bombur + * \version 0.1 + * \date 4.10.2006 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include + + + +#define USE_CONSTANTS_OPTIMISATION + + +const int num_spaces_in_tab = 4; + +MmslParser *mmsl_parser = NULL; + + +////////////////////////////// +// We combine multiple lists into one global list. +// There macros are wrappers for this list. + +#define IDXLIST_ADD(prefix, name, value) { if (prefix idx_##name##1 < 0) \ + prefix idx_##name##1 = prefix idx_##name##2 = mmsl_parser->name.Add(value); \ + else mmsl_parser->name.Insert(value, ++prefix idx_##name##2); } +#define IDXLIST_ADD_SHIFT(list,P, i, name, value) { if (list[i] P idx_##name##1 < 0) \ + list[i] P idx_##name##1 = list[i] P idx_##name##2 = mmsl_parser->name.Add(value); \ + else { mmsl_parser->name.Insert(value, ++list[i] P idx_##name##2); \ + for (int __i = 0; __i < list.GetN(); __i++) { \ + if (__i == i) continue; \ + if (list[__i] P idx_##name##1 >= list[i] P idx_##name##2) list[__i] P idx_##name##1++; \ + if (list[__i] P idx_##name##2 < 0) list[__i] P idx_##name##2 = list[__i] P idx_##name##1; \ + else if (list[__i] P idx_##name##2 >= list[i] P idx_##name##2) list[__i] P idx_##name##2++; \ + } } } +#define IDXLIST_MERGE_SHIFTPTR(list, i, name, value) { if (mmsl_parser->name.Get(value, list[i]->idx_##name##1, list[i]->idx_##name##2) == -1) { \ + IDXLIST_ADD_SHIFT(list,->,i,name,value); } } + +#define IDXLIST_PUT(prefix, name, value, idx) { mmsl_parser->name[prefix idx_##name##1 + idx] = value; } +#define IDXLIST_NUM(prefix, name) (prefix idx_##name##1 < 0 ? 0 : (prefix idx_##name##2 - prefix idx_##name##1 + 1)) + +/////////////////////////////////////////////////////////////////////////////// + +/// Output MMSL error message to the console. +void mmsl_error(const char *text, ...) +{ + va_list args; + va_start(args, text); + char *msgbuf = (char *)SPalloca(4096); + vsprintf(msgbuf, text, args); + va_end(args); + if (mmsl != NULL) + { + if (mmsl_parser->curfile != NULL && mmsl_parser->curfile->row > 0) + { + msg("Mmsl (%s:%d,%d): %s\n", *mmsl_parser->curfile->fname, mmsl_parser->curfile->row, mmsl_parser->curfile->col, msgbuf); + return; + } + } + msg("Mmsl: %s\n", msgbuf); +} + +const int max_varspace = 130016; + +MmslParser::MmslParser() +{ + varscache_used = 0; + varscache = new SPClassicList(4000); + + varspace = new char [max_varspace+256]; // 256 is for safety + maxvaridx = 0; + + pvars.l.Reserve(4600); + events.Reserve(900); + assigned_objs.Reserve(100); + lut.Reserve(1000); + clut.Reserve(100); + assigned_vars.Reserve(1400); + conditions.Reserve(1500); + tokens.Reserve(12000); + execute.Reserve(2200); + + parse_events_stack.Reserve(100); + parse_events_stack_idx = -1; + + token_stack.Reserve(256); + token_stack_idx = -1; + + last_data = NULL; + + curfile = NULL; + + was_overflow = false; +} + +MmslParser::~MmslParser() +{ + SPSafeDeleteArray(varspace); + + pvars.hash.Clear(); + for (int i = 0; i < pvars.l.GetN(); i++) + { + if (pvars.l[i]->temporary) + { + SPSafeDelete(pvars.l[i]); + } + else + pvars.l[i] = NULL; + } + + pclasses.hash.DeleteObjects(); + +#ifdef USE_CONSTANTS_OPTIMISATION + pconstsvars.hash.DeleteObjects(); +#endif + + SPSafeDelete(varscache); +} + +char *MmslParser::LockVariableName() +{ + // overflow + if (maxvaridx >= max_varspace) + { + msg_critical("Error! Variable space overflow.\n"); + return NULL; + } + return varspace + maxvaridx; +} + +void MmslParser::UnlockVariableName(int len) +{ + maxvaridx += len; +} + +MmslToken MmslParser::GetNextToken(char * &data) +{ + static bool was_newline = true, was_realnewline = true, was_semicolon = false; + static int last_indent = -1; + static char *last_newline = NULL; + static int num_spaces = 0, num_tabs = 0; + static MmslFile *curf = NULL; + static struct + { + MMSL_TOKEN_TYPE type; + const char *name; + } lexemes[] = + { + { MMSL_TOKEN_BREAK, "break" }, + { MMSL_TOKEN_DELETE, "delete" }, + { MMSL_TOKEN_INCLUDE, "include" }, + { MMSL_TOKEN_ON, "on" }, + { MMSL_TOKEN_ADD, "add" }, + { MMSL_TOKEN_UNDEFINED, NULL }, + }; + int lexeme_idx = -1, i = 0, j; + int ignore_sym = false; + bool hex_number = false; + + if (curfile != NULL && curfile != curf) + { + curf = curfile; + if (curfile->row == 0) + { + was_newline = true; + was_realnewline = true; + last_newline = NULL; + } else + { + was_newline = false; + was_realnewline = false; + last_newline = data; // wrong but who cares... + } + was_semicolon = false; + last_indent = -1; + num_spaces = 0; + num_tabs = 0; + } + + last_data = data; + + if (last_newline == NULL || was_realnewline) + { + last_newline = data; + AddRow(); + } + MmslToken token; + token.type = MMSL_TOKEN_UNDEFINED; + token.ival = 0; + + while (*data != '\0') + { +cont: + // comments + if (*data == '/') + { + if (data[1] == '/') + { + data+= 2; + while (*data != '\n' && *data != '\0') + data++; + continue; + } + else if (data[1] == '*') + { + data += 2; + while ((*data != '*' || data[1] != '/') && *data != '\0') + { + if (*data == '\n') + { + AddRow(); + last_newline = data + 1; + } + data++; + } + if (*data != '\0') + data += 2; + continue; + } + } + if (was_newline) + { + if (*data == ' ') + num_spaces++; + else if (*data == '\t') + num_tabs++; + else if (*data != '\r' && *data != '\n') + { + token.type = MMSL_TOKEN_INDENT; + token.ival = was_semicolon ? last_indent : num_tabs + num_spaces / num_spaces_in_tab; + last_indent = token.ival; + + was_semicolon = false; + was_newline = false; + was_realnewline = false; + return token; + } + } + switch (token.type) + { + case MMSL_TOKEN_UNDEFINED: + // IDs and built-in commands + if (isalpha(*data) || *data == '.') + { + SetCol(data - last_newline + 1, num_tabs); + token.sval = LockVariableName(); + if (token.sval == NULL) + { + token.type = MMSL_TOKEN_EOF; + return token; + } + token.sval[0] = (char)tolower(*data); + for (j = 0; lexemes[j].name != NULL; j++) + { + if (lexemes[j].name[0] == token.sval[0]) + { + lexeme_idx = j; + break; + } + } + token.type = MMSL_TOKEN_ID; + i = 1; + } + // numbers + else if (isdigit(*data)) + { + SetCol(data - last_newline + 1, num_tabs); + token.type = MMSL_TOKEN_NUMBER; + hex_number = false; + token.ival = (int)(*data) - '0'; + } + // strings + else if (*data == '\"' || *data == '\'') + { + SetCol(data - last_newline + 1, num_tabs); + token.type = MMSL_TOKEN_STRING; + token.sval = LockVariableName(); + if (token.sval == NULL) + { + token.type = MMSL_TOKEN_EOF; + return token; + } + i = 0; + char term = *data; + data++; + while (*data != term && *data != '\0' && *data != '\n') + { + if (*data == '\\') + { + int k = 0; + switch (data[1]) + { + case '\r': + case '\n': // line-continuation + data += 2; + if (*data == '\r' || *data == '\n') + data++; + break; + case '\"': + case '\'': + case '\\': + case '?': + data++; + token.sval[i++] = *data++; + break; + case 'a': + data += 2; + token.sval[i++] = '\a'; + break; + case 'b': + data += 2; + token.sval[i++] = '\b'; + break; + case 'f': + data += 2; + token.sval[i++] = '\f'; + break; + case 'n': + data += 2; + token.sval[i++] = '\n'; + break; + case 'r': + data += 2; + token.sval[i++] = '\r'; + break; + case 't': + data += 2; + token.sval[i++] = '\t'; + break; + case 'v': + data += 2; + token.sval[i++] = '\v'; + break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + // octal + token.sval[i] = 0; + do + { + token.sval[i] = (char)(token.sval[i] * 8 + ((int)(*data) - '0')); + data++; + k++; + } while (k < 3 && *data >= '0' && *data <= '7'); + i++; + break; + case 'x': + // hex + data += 2; + token.sval[i] = 0; + while (k < 3 && ((*data >= '0' && *data <= '9') || + (*data >= 'a' && *data <= 'f') || + (*data >= 'A' && *data <= 'F'))) + { + token.sval[i] = (char)(token.sval[i] * 16 + hex2char(*data)); + data++; + k++; + } + i++; + break; + default: + token.sval[i++] = *data++; + } + continue; + } + token.sval[i++] = *data++; + } + if (*data == term) + data++; + token.sval[i] = '\0'; + UnlockVariableName(i); + return token; + } + // arithmetic operators + else switch (*data) + { + case '\\': + if (data[1] == '\n' || data[1] == '\r') + { + data += 2; + if (*data == '\r' || *data == '\n') + data++; + if (*data != '\0') + goto cont; + } + break; + case ';': + was_semicolon = true; + case '\n': + SetCol(data - last_newline + 1, num_tabs); + token.type = MMSL_TOKEN_EOL; + was_newline = true; + num_spaces = 0; + num_tabs = 0; + was_realnewline = (*data == '\n'); + if (was_realnewline) + was_semicolon = false; + data++; + return token; + case '(': + SetCol(data - last_newline + 1, num_tabs); + token.type = MMSL_TOKEN_LP; + data++; + return token; + case ')': + SetCol(data - last_newline + 1, num_tabs); + token.type = MMSL_TOKEN_RP; + data++; + return token; + case '+': + SetCol(data - last_newline + 1, num_tabs); + token.type = MMSL_TOKEN_PLUS; + data++; + return token; + case '-': + SetCol(data - last_newline + 1, num_tabs); + token.type = MMSL_TOKEN_MINUS; + data++; + return token; + case '*': + SetCol(data - last_newline + 1, num_tabs); + token.type = MMSL_TOKEN_MUL; + data++; + return token; + case '/': + SetCol(data - last_newline + 1, num_tabs); + token.type = MMSL_TOKEN_DIV; + data++; + return token; + case '%': + SetCol(data - last_newline + 1, num_tabs); + token.type = MMSL_TOKEN_MOD; + data++; + return token; + case '!': + if (data[1] == '=') + { + SetCol(data - last_newline + 1, num_tabs); + token.type = MMSL_TOKEN_NONEQU; + data += 2; + return token; + } + break; + case '&': + if (data[1] == '&') + { + SetCol(data - last_newline + 1, num_tabs); + token.type = MMSL_TOKEN_ANDAND; + data += 2; + return token; + } + break; + case '|': + if (data[1] == '|') + { + SetCol(data - last_newline + 1, num_tabs); + token.type = MMSL_TOKEN_OROR; + data += 2; + return token; + } + break; + case '>': + SetCol(data - last_newline + 1, num_tabs); + data++; + if (*data == '=') + { + token.type = MMSL_TOKEN_GTEQU; + data++; + } else + token.type = MMSL_TOKEN_GT; + return token; + case '<': + SetCol(data - last_newline + 1, num_tabs); + data++; + if (*data == '=') + { + token.type = MMSL_TOKEN_LTEQU; + data++; + } else + token.type = MMSL_TOKEN_LT; + return token; + case '=': + SetCol(data - last_newline + 1, num_tabs); + data++; + if (*data == '=') + { + token.type = MMSL_TOKEN_EQUEQU; + data++; + } else + token.type = MMSL_TOKEN_EQU; + return token; + default: + if (!ignore_sym && *data != ' ' && *data != '\t' && *data != '\n' && *data != '\r') + { + mmsl_error("Ignoring unrecognized symbol."); + ignore_sym = true; + } + break; + } + break; + case MMSL_TOKEN_ID: + if (!isalnum(*data) && *data != '.' && *data != '_') + { + if (lexeme_idx >= 0 && lexemes[lexeme_idx].name[i] == '\0') + { + token.type = lexemes[lexeme_idx].type; + token.ival = 0; + } + else + token.sval[i] = '\0'; + UnlockVariableName(i); + + return token; + } + token.sval[i] = (char)tolower(*data); + // first, check built-in lexemes + if (lexeme_idx >= 0) + { + if (lexemes[lexeme_idx].name[i] != token.sval[i]) + lexeme_idx = -1; + } + i++; + break; + case MMSL_TOKEN_NUMBER: + if (*data == 'x') + { + hex_number = true; + break; + } + else if (hex_number && ((*data >= '0' && *data <= '9') || + (*data >= 'A' && *data <= 'F') || + (*data >= 'a' && *data <= 'f'))) + { + token.ival = token.ival * 16 + hex2char((int)(*data)); + } + else if (!isdigit(*data)) + { + return token; + } + else + token.ival = token.ival * 10 + ((int)(*data) - '0'); + break; + default: + ; + } + data++; + } + if (token.type == MMSL_TOKEN_UNDEFINED) + token.type = MMSL_TOKEN_EOF; + return token; +} + +void MmslParser::AddRow() +{ + if (curfile != NULL) + curfile->row++; + SetCol(1, 0); +} + +void MmslParser::SetCol(int col, int num_tabs) +{ + if (curfile != NULL) + { + curfile->col = col + num_tabs * (num_spaces_in_tab - 1); + } +} + +void MmslParser::UndoToken(char * &data) +{ + if (last_data != NULL) + data = last_data; +} + +int MmslToken::GetPriority(MMSL_TOKEN_TYPE type) +{ + switch(type) + { + case MMSL_TOKEN_LP: + return 1; + case MMSL_TOKEN_EQU: + return 2; + case MMSL_TOKEN_ANDAND: + case MMSL_TOKEN_OROR: + return 3; + case MMSL_TOKEN_EQUEQU: + case MMSL_TOKEN_NONEQU: + return 4; + case MMSL_TOKEN_GT: + case MMSL_TOKEN_GTEQU: + case MMSL_TOKEN_LT: + case MMSL_TOKEN_LTEQU: + return 5; + case MMSL_TOKEN_PLUS: + case MMSL_TOKEN_MINUS: + return 6; + case MMSL_TOKEN_MUL: + case MMSL_TOKEN_DIV: + case MMSL_TOKEN_MOD: + return 7; + case MMSL_TOKEN_UNARY: + return 8; + default: + return 0; + } +} + +MMSL_OPERATOR_TYPE MmslToken::GetOperator(MMSL_TOKEN_TYPE type) +{ + MMSL_OPERATOR_TYPE op = (MMSL_OPERATOR_TYPE)(MMSL_OPERATOR_LP + type - MMSL_TOKEN_LP); + if (op <= MMSL_OPERATOR_UNKNOWN1 || op >= MMSL_OPERATOR_UNKNOWN2) + return MMSL_OPERATOR_UNKNOWN1; + return op; +} + +/////////////////////////////////////////////////////////////////////// + +BOOL MmslParser::ParseFile(const char *fname, int start_indent) +{ + int i; + if (fname == NULL) + return FALSE; + FILE *fp = fopen(fname, "rb"); + if (fp == NULL) + { + char *newfname = new char [4096]; + sprintf(newfname, "mmsl/%s", fname); + fp = fopen(newfname, "rb"); + if (fp == NULL) + { + mmsl_error("Cannot include file '%s'.", newfname); + delete [] newfname; + return FALSE; + } + delete [] newfname; + } + + MmslFile fil; + fil.fname = SPString(fname); + files.Add(fil); + curfile = &files[files.GetN() - 1]; + + fseek(fp, 0, SEEK_END); + int size = ftell(fp); + rewind(fp); + char *buf = (char *)SPmalloc(size + 1); + fread(buf, size, 1, fp); + for (i = 0; i < size; i++) + { + if (buf[i] == '\0') + buf[i] = ' '; + } + buf[size] = '\0'; + fclose(fp); + + char *b = buf; + int cur_event; + + if (events.GetN() < 1) + { + MmslEvent event; + IDXLIST_ADD(event.trigger., tokens, MMSL_CONST_TRUE); + cur_event = events.Add(event); + parse_events_stack_idx = -1; + parse_events_stack.Put(cur_event, ++parse_events_stack_idx); + + } + + int cur_indent = start_indent; + cur_event = FindParentEvent(cur_indent); + + for (;;) + { + MmslToken token = GetNextToken(b); + + if (token.type == MMSL_TOKEN_EOL) + continue; + + // include MMSL file + if (token.type == MMSL_TOKEN_INCLUDE) + { + MmslToken tfname = GetNextToken(b); + if (tfname.type != MMSL_TOKEN_STRING) + mmsl_error("Cannot include file - string expected."); + else + { + if (strcasecmp(tfname.sval, fname) == 0) + { + mmsl_error("This file is already included."); + } else + { + ParseFile(tfname.sval, cur_indent); + } + } + } + + // debug_print_token(token); + + if (token.type == MMSL_TOKEN_INDENT) + { + cur_indent = token.ival + start_indent; + cur_event = FindParentEvent(cur_indent); + } + + if (token.type == MMSL_TOKEN_ON) + { + MmslEvent event; + event.indent = cur_indent; + ParseExpression(b, event.trigger, events.GetN()); + if (IDXLIST_NUM(event.trigger., tokens) < 1) + { + mmsl_error("No condition set for event."); + } + // do not add root condition (it's always true) + for (int i = 1; i <= parse_events_stack_idx; i++) + IDXLIST_ADD(event., conditions, parse_events_stack[i]); + cur_event = events.Add(event); + parse_events_stack.Put(cur_event, ++parse_events_stack_idx); + } + + else if (token.type == MMSL_TOKEN_ADD) + { + MmslExpression e; + IDXLIST_ADD(e., tokens, MMSL_OPERATOR_ADD); + + token = GetNextToken(b); + if (token.type != MMSL_TOKEN_ID) + mmsl_error("'Object Type' identifier expected for 'add' operator."); + else + { + MmslParserClass *cls = pclasses.Get(token.sval); + if (cls == NULL) + mmsl_error("Wrong object type specified for 'add' operator."); + else + { + IDXLIST_ADD(e., tokens, cls->index); + + token = GetNextToken(b); + if (token.type != MMSL_TOKEN_ID) + mmsl_error("'Object Type' identifier expected for 'add' operator."); + else + { + SPString nam(token.sval); + MmslAssignedObject an; + // now register variables + for (int i = 0; i < pvars.l.GetN(); i++) + { + if (pvars.l[i]->obj_id == cls->ID) + { + if (pvars.l[i]->name && pvars.l[i]->name[0] == '.') // only abstact vars like ".anyvar" + { + int idx = RegisterVariable(nam + pvars.l[i]->name); + // copy var data + *pvars.l[idx] = *pvars.l[i]; + IDXLIST_ADD(an., assigned_vars, idx); + } + } + } + int an_idx = assigned_objs.Add(an); + IDXLIST_ADD(e., tokens, an_idx); + IDXLIST_ADD_SHIFT(events,., cur_event, execute, e); + } + } + } + } + else if (token.type == MMSL_TOKEN_DELETE) + { + MmslExpression e; + IDXLIST_ADD(e., tokens, MMSL_OPERATOR_DELETE); + ParseExpression(b, e, -2); + if (IDXLIST_NUM(e., tokens) < 1) + { + mmsl_error("No condition set for delete."); + } else + { + IDXLIST_ADD_SHIFT(events,.,cur_event, execute, e); + } + } + else if (token.type == MMSL_TOKEN_ID) + { + UndoToken(b); + MmslExpression e; + ParseExpression(b, e, -1); + IDXLIST_ADD_SHIFT(events,., cur_event, execute, e); + } + + if (token.type == MMSL_TOKEN_EOF) + break; + } + + files.Remove(files.GetN() - 1); + curfile = files.GetN() > 0 ? &files[files.GetN() - 1] : NULL; + + SPfree(buf); + return TRUE; +} + +int MmslParser::FindParentEvent(int indent) +{ + if (indent == 0) + { + parse_events_stack_idx = 0; + return 0; + } + for ( ; parse_events_stack_idx >= 0; parse_events_stack_idx--) + { + int idx = parse_events_stack[parse_events_stack_idx]; + if (idx >= 0 && idx < events.GetN() && events[idx].indent < indent) + return idx; + } + return 0; +} + +void MmslParser::ParseExpression(char * &b, MmslExpression &expr, int event_idx) +{ + int num_par = 0; + bool was_op = false, was_id = false; + token_stack_idx = -1; + +#ifdef MMSL_DEBUG + if (curfile != NULL) + expr.pos = *curfile; +#endif + + for (;;) + { + MmslToken token = GetNextToken(b); + if (token.type == MMSL_TOKEN_EOL || token.type == MMSL_TOKEN_EOF) + break; + if (token.type == MMSL_TOKEN_ID) + { + int idx = RegisterVariable(token.sval); + if (event_idx >= 0) + { + IDXLIST_MERGE_SHIFTPTR(pvars.l, idx, lut, event_idx); + } + IDXLIST_ADD(expr., tokens, idx); + was_op = false; + was_id = true; + } + else if (token.type == MMSL_TOKEN_NUMBER) + { + int idx = RegisterConstant(token.ival); + IDXLIST_ADD(expr., tokens, idx); + was_op = false; + was_id = true; + } + else if (token.type == MMSL_TOKEN_STRING) + { + int idx = RegisterConstant(token.sval); + IDXLIST_ADD(expr., tokens, idx); + was_op = false; + was_id = true; + } + else if (token.type == MMSL_TOKEN_LP) + { + token_stack.Put(MMSL_TOKEN_LP, ++token_stack_idx); + num_par++; + was_op = false; + } + else if (token.type == MMSL_TOKEN_RP) + { + if (was_op) + mmsl_error("Syntax error."); + MMSL_TOKEN_TYPE tok; + while (token_stack_idx >= 0 && (tok = token_stack[token_stack_idx--]) != MMSL_TOKEN_LP && num_par > 0) + { + IDXLIST_ADD(expr., tokens, MmslToken::GetOperator(tok)); + } + num_par--; + was_op = false; + } + else // operator? + { + MMSL_OPERATOR_TYPE op = token.GetOperator(token.type); + if (op != MMSL_OPERATOR_UNKNOWN1) + { + MMSL_TOKEN_TYPE type = token.type; + // fix '==' typos for conditional expression + if (op == MMSL_OPERATOR_EQU && event_idx != -1) + { + op = MMSL_OPERATOR_EQUEQU; + type = MMSL_TOKEN_EQUEQU; + } + + // process unary +/- operators + int cur_pri = MmslToken::GetPriority(type); + if (!was_id && (op == MMSL_OPERATOR_PLUS || op == MMSL_OPERATOR_MINUS)) + { + int idx = RegisterConstant(0); + IDXLIST_ADD(expr., tokens, idx); + cur_pri = MmslToken::GetPriority(MMSL_TOKEN_UNARY); + } + was_op = true; + was_id = false; + + for ( ; token_stack_idx >= 0 + && cur_pri <= MmslToken::GetPriority(token_stack[token_stack_idx]); + token_stack_idx--) + { + IDXLIST_ADD(expr., tokens, MmslToken::GetOperator(token_stack[token_stack_idx])); + } + if (token_stack_idx < 0 || cur_pri > MmslToken::GetPriority(token_stack[token_stack_idx])) + token_stack.Put(type, ++token_stack_idx); + } else + { + mmsl_error("Unknown token in expression."); + } + } + } + // flush operators + for ( ; token_stack_idx >= 0; token_stack_idx--) + { + IDXLIST_ADD(expr., tokens, MmslToken::GetOperator(token_stack[token_stack_idx])); + } +} + +MmslParserVariable *MmslParser::GetNewVariable() +{ + if (varscache_used < varscache->GetN()) + { + MmslParserVariable *var = &(*varscache)[varscache_used++]; + var->temporary = false; + return var; + } + return new MmslParserVariable(); +} + +int MmslParser::RegisterVariable(const char *name, int ID) +{ + if (name == NULL || ID < 0) + return -1; + MmslParserVariable *var = GetNewVariable(); + var->ID = ID; + var->SetConstName(name); + var->type = MMSL_VARIABLE_INTEGER; + return RegisterVariable(var); +} + +int MmslParser::RegisterObject(const char *name, int ID) +{ + if (name == NULL || ID < 0) + return -1; + MmslParserClass *cls = new MmslParserClass(); + cls->ID = ID; + cls->SetConstName(name); + return RegisterObjectClass(cls); +} + +int MmslParser::RegisterObjectVariable(int obj_ID, const char *var_name, int var_ID) +{ + if (obj_ID < 0 || var_name == NULL || var_ID < 0) + return -1; + MmslParserVariable *cvar = pvars.Get((char *)var_name); + if (cvar == NULL) + { + cvar = GetNewVariable(); + if (cvar == NULL) + return -1; + cvar->type = MMSL_VARIABLE_OBJECTVAR; + cvar->SetConstName(var_name); + // init pclasses LUT + for (int i = 0; i < pclasses.l.GetN(); i++) + IDXLIST_ADD(cvar->, clut, -1); + RegisterVariable(cvar); + } + + for (int i = 0; i < pclasses.l.GetN(); i++) + { + if (pclasses.l[i]->ID == obj_ID) + { + MmslParserVariable *var = GetNewVariable(); + if (var == NULL) + return -1; + var->ID = var_ID; + var->obj_id = obj_ID; + var->obj = NULL; // will be assigned dynamically for a real variable + var->type = MMSL_VARIABLE_INTEGER; + var->SetConstName(var_name); + + var->index = RegisterVariable(var); + + // write class index + IDXLIST_PUT(cvar->, clut, var->index, i); + + return var->index; + } + } + + return -1; +} + +int MmslParser::RegisterVariable(char *name) +{ + MmslParserVariable *var = pvars.Get(name); + if (var != NULL) + return var->index; + var = GetNewVariable(); + var->SetName(name); + var->type = MMSL_VARIABLE_INTEGER; + return RegisterVariable(var); +} + +int MmslParser::RegisterConstant(char *strval) +{ + SPString *s = new_SPString(strval); +#ifdef USE_CONSTANTS_OPTIMISATION + MmslParserHashVariable tmphash(s); + MmslParserHashVariable *ret = pconstsvars.hash.Get(tmphash); + if (ret) + { + delete_SPString(s); + return ret->var->index; + } +#endif + // add hash search? + MmslParserVariable *var = GetNewVariable(); + var->type = MMSL_VARIABLE_CONST_STRING; + var->sval = s; +#ifdef USE_CONSTANTS_OPTIMISATION + MmslParserHashVariable *newphv = new MmslParserHashVariable(var); + pconstsvars.hash.Add(newphv); +#endif + return RegisterVariable(var); +} + +int MmslParser::RegisterConstant(int val) +{ +#ifdef USE_CONSTANTS_OPTIMISATION + // use raw search + for (int i = 0; i < pconstivars.GetN(); i++) + { + if (pconstivars[i]->ival == val) + return pconstivars[i]->index; + } +#endif + // add hash search? + MmslParserVariable *var = GetNewVariable(); + var->type = MMSL_VARIABLE_CONST_INTEGER; + var->ival = val; +#ifdef USE_CONSTANTS_OPTIMISATION + pconstivars.Add(var); +#endif + return RegisterVariable(var); + +} + +int MmslParser::RegisterVariable(MmslParserVariable *var) +{ + if (var == NULL) + return -1; + pvars.hash.Add(var); + var->index = pvars.l.Add(var); +/* + if (var->ID >= 0) + { + MmslID *vid = new MmslID; + vid->ID = var->ID; + vid->index = var->index; + idvars.Add(vid); + } +*/ + return var->index; +} + +int MmslParser::RegisterObjectClass(MmslParserClass *obj) +{ + pclasses.hash.Add(obj); + obj->index = pclasses.l.Add(obj); + return obj->index; +} + +///////////////////////////////////////////////////////////////////// + +BYTE *MmslParser::Save(int *buflen) +{ + const int max_buf_size = 512*1024; // 512 Kb + BYTE *buf0 = (BYTE *)SPmalloc(max_buf_size); + BYTE *buf = buf0; + int i; + + was_overflow = false; + + MmslSerHeader *hdr = (MmslSerHeader *)buf; + buf += sizeof(MmslSerHeader); + + BYTE *varbuf = (BYTE *)buf; + buf += (hdr->var_buf_size = CalcSerVarSize()); + hdr->num_var = pvars.l.GetN(); + + MmslSerClass *serclass = (MmslSerClass *)buf; + buf += sizeof(MmslSerClass) * (hdr->num_class = pclasses.l.GetN()); + MmslSerEvent *serevent = (MmslSerEvent *)buf; + buf += sizeof(MmslSerEvent) * (hdr->num_event = events.GetN()); + MmslSerAssignedObject *serassignedobj = (MmslSerAssignedObject *)buf; + buf += sizeof(MmslSerAssignedObject) * (hdr->num_assignedobj = assigned_objs.GetN()); + MmslSerExpression *serexpr = (MmslSerExpression *)buf; + buf += sizeof(MmslSerExpression) * (hdr->num_expr = execute.GetN()); + + short *serlut = (short *)buf; + buf += sizeof(short) * (hdr->num_lut = lut.GetN()); + short *serclut = (short *)buf; + buf += sizeof(short) * (hdr->num_clut = clut.GetN()); + short *serassignedvars = (short *)buf; + buf += sizeof(short) * (hdr->num_assignedvar = assigned_vars.GetN()); + short *sercond = (short *)buf; + buf += sizeof(short) * (hdr->num_cond = conditions.GetN()); + short *sertoken = (short *)buf; + buf += sizeof(short) * (hdr->num_token = tokens.GetN()); + + int str_len = 0; + for (i = 0; i < pvars.l.GetN(); i++) + { + if (pvars.l[i]->sval) + str_len += pvars.l[i]->sval->GetLength() + 1; + } + + char *str_buf = (char *)buf; + buf += str_len; + + int buf_size = (int)(buf - buf0); + // at this moment, the buffer is not filled yet, so let's check its size + if (buf_size > max_buf_size) + { + msg("Mmsl: Max. buffer size exceeded.\n"); + SPfree(buf0); + return NULL; + } + + char *curstr = str_buf; + + for (i = 0; i < pvars.l.GetN(); i++) + { + *varbuf++ = (BYTE)pvars.l[i]->type; // type contains variable bit-fields also (see CalcSerVarSize()) + if (pvars.l[i]->type & MMSL_VARIABLE_HAS_ID) + WriteShort(varbuf, pvars.l[i]->ID); + if (pvars.l[i]->type & MMSL_VARIABLE_HAS_LUT) + { + WriteShort(varbuf, pvars.l[i]->idx_lut1); + WriteShort(varbuf, pvars.l[i]->idx_lut2); + } + if (pvars.l[i]->type & MMSL_VARIABLE_HAS_CLUT) + { + WriteChar(varbuf, pvars.l[i]->idx_clut1); + WriteChar(varbuf, pvars.l[i]->idx_clut2); + } + if (pvars.l[i]->type & MMSL_VARIABLE_HAS_OBJID) + WriteChar(varbuf, pvars.l[i]->obj_id); + + pvars.l[i]->type = (MMSL_VARIABLE_TYPE)(pvars.l[i]->type & MMSL_VARIABLE_MASK); + + // raw integer or strings index + if (pvars.l[i]->type == MMSL_VARIABLE_CONST_STRING) + { + if (pvars.l[i]->sval) + { + WriteInt(varbuf, (int)(curstr - str_buf)); + int l = pvars.l[i]->sval->GetLength() + 1; + memcpy(curstr, *pvars.l[i]->sval, l); + curstr += l; + } else + WriteInt(varbuf, -1); + } + else + WriteInt(varbuf, pvars.l[i]->ival); + } + + for (i = 0; i < pclasses.l.GetN(); i++) + { + serclass[i].ID = GetShort(pclasses.l[i]->ID); + serclass[i].idx_vars1 = GetShort(pclasses.l[i]->idx_vars1); + serclass[i].idx_vars2 = GetShort(pclasses.l[i]->idx_vars2); + } + + for (i = 0; i < events.GetN(); i++) + { + serevent[i].idx_triggertokens1 = GetShort(events[i].trigger.idx_tokens1); + serevent[i].idx_triggertokens2 = GetShort(events[i].trigger.idx_tokens2); + serevent[i].idx_conditions1 = GetShort(events[i].idx_conditions1); + serevent[i].idx_conditions2 = GetShort(events[i].idx_conditions2); + serevent[i].idx_execute1 = GetShort(events[i].idx_execute1); + serevent[i].idx_execute2 = GetShort(events[i].idx_execute2); + } + + for (i = 0; i < assigned_objs.GetN(); i++) + { + serassignedobj[i].idx_assigned_vars1 = GetShort(assigned_objs[i].idx_assigned_vars1); + serassignedobj[i].idx_assigned_vars2 = GetShort(assigned_objs[i].idx_assigned_vars2); + } + + for (i = 0; i < execute.GetN(); i++) + { + serexpr[i].idx_tokens1 = GetShort(execute[i].idx_tokens1); + serexpr[i].idx_tokens2 = GetShort(execute[i].idx_tokens2); + } + + //memcpy(serlut, lut.GetData(), sizeof(serlut[0]) * lut.GetN()); + for (i = 0; i < lut.GetN(); i++) + serlut[i] = GetShort(lut[i]); + //memcpy(serclut, clut.GetData(), sizeof(serclut[0]) * clut.GetN()); + for (i = 0; i < clut.GetN(); i++) + serclut[i] = GetShort(clut[i]); + //memcpy(serassignedvars, assigned_vars.GetData(), sizeof(serassignedvars[0]) * assigned_vars.GetN()); + for (i = 0; i < assigned_vars.GetN(); i++) + serassignedvars[i] = GetShort(assigned_vars[i]); + //memcpy(sercond, conditions.GetData(), sizeof(sercond[0]) * conditions.GetN()); + for (i = 0; i < conditions.GetN(); i++) + sercond[i] = GetShort(conditions[i]); + //memcpy(sertoken, tokens.GetData(), sizeof(sertoken[0]) * tokens.GetN()); + for (i = 0; i < tokens.GetN(); i++) + sertoken[i] = GetShort(tokens[i]); + + if (was_overflow) + { + msg("MmslParser: Serialization overflow! Not saved!\n"); + SPfree(buf0); + return NULL; + } + + *buflen = buf_size; + return buf0; +} + +short MmslParser::GetShort(int v) +{ + short b = (short)v; + if (v != (int)b) + was_overflow = true; + return b; +} + +void MmslParser::WriteChar(BYTE* &buf, int v) +{ + if (v != (int)((char)v)) + was_overflow = true; + *buf++ = (BYTE)((char)v); +} + +void MmslParser::WriteShort(BYTE* &buf, int v) +{ + *buf++ = (BYTE)((char)(v & 0xff)); + *buf++ = (BYTE)((char)(v >> 8)); +} + +void MmslParser::WriteInt(BYTE* &buf, int v) +{ + DWORD d = *((DWORD *)&v); + *buf++ = (BYTE)d; + *buf++ = (BYTE)(d >>= 8); + *buf++ = (BYTE)(d >>= 8); + *buf++ = (BYTE)(d >>= 8); +} + +int MmslParser::CalcSerVarSize() +{ + int ser_var_size = 0; + for (int i = 0; i < pvars.l.GetN(); i++) + { + ser_var_size += 5; // pvars.l[i]->type + ival/sval_idx + + pvars.l[i]->type = (MMSL_VARIABLE_TYPE)(pvars.l[i]->type & MMSL_VARIABLE_MASK); + + if (pvars.l[i]->ID >= 0) + { + ser_var_size += 2; + pvars.l[i]->type = (MMSL_VARIABLE_TYPE)(pvars.l[i]->type | MMSL_VARIABLE_HAS_ID); + } + if (pvars.l[i]->idx_lut1 >= 0 || pvars.l[i]->idx_lut2 >= 0) + { + ser_var_size += 4; + pvars.l[i]->type = (MMSL_VARIABLE_TYPE)(pvars.l[i]->type | MMSL_VARIABLE_HAS_LUT); + } + if (pvars.l[i]->idx_clut1 >= 0 || pvars.l[i]->idx_clut2 >= 0) + { + ser_var_size += 2; + pvars.l[i]->type = (MMSL_VARIABLE_TYPE)(pvars.l[i]->type | MMSL_VARIABLE_HAS_CLUT); + } + if (pvars.l[i]->obj_id >= 0) + { + ser_var_size ++; + pvars.l[i]->type = (MMSL_VARIABLE_TYPE)(pvars.l[i]->type | MMSL_VARIABLE_HAS_OBJID); + } + } + return PAD_EVEN(ser_var_size); +} diff --git a/src/mmsl/mmsl-parser.h b/src/mmsl/mmsl-parser.h new file mode 100644 index 0000000..ba4fa18 --- /dev/null +++ b/src/mmsl/mmsl-parser.h @@ -0,0 +1,264 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - MMSL interpreter header file + * \file mmsl/mmsl-parser.h + * \author bombur + * \version 0.1 + * \date 4.10.2006 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#ifndef SP_MMSL_PARSER_H +#define SP_MMSL_PARSER_H + +#include +#include + +//////////////////////////////////////////////////////////////////// + +template +class HashList +{ +public: + /// ctor + HashList() + { + hash.SetN(n); + } + + /// Not thread-safe! + T *Get(char *name) + { + static T tmp; + tmp.SetConstName(name); + T *ret = hash.Get(tmp); + tmp.name = NULL; + return ret; + } + +public: + SPHashListAbstract hash; + SPList l; +}; + +class MmslToken +{ +public: + MMSL_TOKEN_TYPE type; + union + { + int ival; + char *sval; + }; + + static int GetPriority(MMSL_TOKEN_TYPE type); + static MMSL_OPERATOR_TYPE GetOperator(MMSL_TOKEN_TYPE type); +}; + +/// Built-in or user-defined variable (also object var.) +class MmslParserVariable : public Resource, public MmslVariable +{ +public: + /// ctor + MmslParserVariable() {} + + /// dtor + ~MmslParserVariable() {} + + MmslParserVariable (const MmslParserVariable & v) : MmslVariable(v) + { + } + + MmslParserVariable & operator = (const MmslParserVariable & v) + { + MmslVariable::operator =(v); + + return (*this); + } + +public: +}; + +/// Registered object class (for 'add [object] [name]' command) +class MmslParserClass : public Resource, public MmslClass +{ +public: + MmslParserClass() + { + add_callback = 0; + delete_callback = 0; + param = NULL; + } + + /// dtor + ~MmslParserClass() + { + } + +public: + MmslObjectCallback add_callback, delete_callback; + void *param; + +}; + + +const int parservar_num_hash = 57; + +/// Used for const-strings search optimisation +class MmslParserHashVariable +{ +public: + MmslParserHashVariable(SPString *str) + { + prev = next = NULL; + var = NULL; + sval = str; + const char *ss = **str; + hash = SPStringHashFunc((char * const &)ss, parservar_num_hash); + } + + MmslParserHashVariable(MmslParserVariable *v) + { + prev = next = NULL; + var = v; + sval = v->sval; + hash = v->sval ? SPStringHashFunc(*v->sval, parservar_num_hash) : 0; + } + + /// compare + template + bool operator == (const T & phv) + { + if (sval == NULL || phv.sval == NULL) + return false; + return var->sval->Compare(*phv.sval) == 0; + } + + inline operator DWORD () const + { + return hash; + } + + const MmslParserHashVariable * const GetItem() const + { + return this; + } + + MmslParserHashVariable *prev, *next; + +public: + MmslParserVariable *var; + SPString *sval; + DWORD hash; +}; + + +//////////////////////////////////////////////////////////////// + +/// MMSL Parser/Interpreter singleton class +class MmslParser : public Mmsl +{ +public: + /// ctor + MmslParser(); + /// dtor + ~MmslParser(); + + /// Register predefined external variable (names must be string constants). + int RegisterVariable(const char *name, int ID); + + /// Register dynamic object type - to create/delete it from script + /// (names must be string constants). + int RegisterObject(const char *name, int ID); + + /// Register object variables + /// (names must be string constants). + int RegisterObjectVariable(int obj_ID, const char *var_name, int var_ID); + + /// Read and process MMSL file + BOOL ParseFile(const char *fname, int start_indent = 0); + + /// Serialization: Save all created data into the buffer + BYTE *Save(int *buflen); + +public: + HashList pvars; + + /// registered object pclasses + HashList pclasses; + + SPList pconstivars; + HashList pconstsvars; + +protected: + friend class MmslParserVariable; + friend void mmsl_error(const char *, ...); + + SPList parse_events_stack; + int parse_events_stack_idx; + + SPList token_stack; + int token_stack_idx; + + MmslToken GetNextToken(char * &data); + void UndoToken(char * &data); + + char *varspace; + int maxvaridx; + char *last_data; + + SPClassicList *varscache; + int varscache_used; + + /// Parsing file info stack (fname, row, col) for nested includes + SPClassicList files; + MmslFile *curfile; + + /// Return a new variable pointer + char *LockVariableName(); + /// Close variable pointer + void UnlockVariableName(int len); + + int FindParentEvent(int indent); + void ParseExpression(char * &b, MmslExpression &expr, int event_idx); + + void AddRow(); + void SetCol(int col, int numtabs); + + int RegisterObjectClass(MmslParserClass *obj); + int RegisterVariable(char *name); + int RegisterConstant(char *val); + int RegisterConstant(int val); + int RegisterVariable(MmslParserVariable *var); + + MmslParserVariable *GetNewVariable(); + + ////////////////////////////// + inline short GetShort(int v); + inline void WriteChar(BYTE* &buf, int v); + inline void WriteShort(BYTE* &buf, int v); + inline void WriteInt(BYTE* &buf, int v); + + int CalcSerVarSize(); + +private: + bool was_overflow; +}; + +extern MmslParser *mmsl_parser; + +#endif // of SP_MMSL_PARSER_H diff --git a/src/mmsl/mmsl.cpp b/src/mmsl/mmsl.cpp new file mode 100644 index 0000000..96236a2 --- /dev/null +++ b/src/mmsl/mmsl.cpp @@ -0,0 +1,1105 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - MMSL interpreter impl. + * \file mmsl/mmsl.cpp + * \author bombur + * \version 0.1 + * \date 4.10.2006 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +#include + +const int num_spaces_in_tab = 4; + +#ifdef WIN32 +//#define MMSL_DEBUG +#endif + +//#define MMSL_DUMP + +Mmsl *mmsl = NULL; + +////////////////////////////// +// We combine multiple lists into one global list. +// There macros are wrappers for this list. + +#define IDXLIST_NUM(prefix, name) (prefix idx_##name##1 < 0 ? 0 : (prefix idx_##name##2 - prefix idx_##name##1 + 1)) +#define IDXLIST_LAST(i, prefix, name) (prefix idx_##name##1 < 0 ? false : (i == prefix idx_##name##2)) +#define IDXLIST_FOR(i, prefix, name) for (i = prefix idx_##name##1; i >= 0 && i <= prefix idx_##name##2; i++) +#define IDXLIST_FOR_OFFS(i, prefix, name, start) for (i = prefix idx_##name##1 + start; i >= 0 && i <= prefix idx_##name##2; i++) +#define IDXLIST(prefix, name, idx) (mmsl->name[prefix idx_##name##1 + idx]) +#define IDXLISTABS(prefix, name, idx) (mmsl->name[idx]) + +/////////////////////////////////////////////////////////////////////////////// + +/// Output MMSL error message to the console. +void mmsl_run_error(const char *text, ...) +{ + va_list args; + va_start(args, text); + char *msgbuf = (char *)SPalloca(4096); + vsprintf(msgbuf, text, args); + va_end(args); + msg("Mmsl: %s\n", msgbuf); +} + +const int max_varspace = 86016; + +Mmsl::Mmsl() +{ + cur_counter = 0; + need_to_run_events = false; + + objects.SetN(mmsl_num_hash); +} + +Mmsl::~Mmsl() +{ + objects.DeleteObjects(); +} + +int Mmsl::BindVariable(int ID, MmslVariableCallback Get, MmslVariableCallback Set, void *param) +{ + if (ID < 0 || Get == NULL || Set == NULL) + return -1; + MmslVariable *var = GetVariable(ID); + if (var) + { + var->get_callback = Get; + var->set_callback = Set; + var->param = param; + var->type = MMSL_VARIABLE_INTEGER; + return 0; + } + return -1; +} + +int Mmsl::BindObject(int ID, MmslObjectCallback Add, MmslObjectCallback Delete, void *param) +{ + if (ID < 0 || Add == NULL || Delete == NULL) + return -1; + MmslClass *cls = GetClass(ID); + if (cls) + { + cls->add_callback = Add; + cls->delete_callback = Delete; + cls->param = param; + return 0; + } + return -1; +} + +int Mmsl::BindObjectVariable(int obj_ID, int var_ID, + MmslVariableCallback Get, MmslVariableCallback Set, void *param) +{ + int i; + bool was_any = false; + if (obj_ID < 0 || var_ID < 0 || Get == NULL || Set == NULL) + return -1; + for (i = 0; i < vars.GetN(); i++) + { + MmslVariable &var = vars[i]; + + if (var.obj_id == obj_ID && var.type != MMSL_VARIABLE_OBJECTVAR) + { + var.get_callback = Get; + var.set_callback = Set; + var.param = param; + var.obj = NULL; // will be assigned dynamically for a real variable + var.type = MMSL_VARIABLE_INTEGER; + was_any = true; + } + } + return was_any ? 0 : -1; +} + + +/////////////////////////////////////////////////////////////////////// + +MmslVariable::MmslVariable() +{ + type = MMSL_VARIABLE_CONST_INTEGER; + ival = 0; + sval = NULL; + get_callback = NULL; + set_callback = NULL; + param = NULL; + + obj_id = -1; + obj = NULL; + + idx_lut1 = idx_lut2 = -1; + idx_clut1 = idx_clut2 = -1; + + ref = NULL; + + ID = -1; +} + +void MmslVariable::Set(int value) +{ + // cannot change constants + if (type == MMSL_VARIABLE_CONST_INTEGER || type == MMSL_VARIABLE_CONST_STRING) + return; + + if (type == MMSL_VARIABLE_STRING || type == MMSL_VARIABLE_RET_STRING) + delete_SPString(sval); + type = MMSL_VARIABLE_INTEGER; + ival = value; + //Update(false); +} + +void MmslVariable::Set(const SPString & value) +{ + // cannot change constants + if (type == MMSL_VARIABLE_CONST_INTEGER || type == MMSL_VARIABLE_CONST_STRING) + return; + + if (type == MMSL_VARIABLE_STRING || type == MMSL_VARIABLE_RET_STRING) + { + *sval = value; + } else + { + sval = new_SPString(value); + } + type = MMSL_VARIABLE_STRING; + ival = 0; + //Update(false); +} + +void MmslVariable::Set(const MmslVariable & from) +{ + // cannot change constants + if (type == MMSL_VARIABLE_CONST_INTEGER || type == MMSL_VARIABLE_CONST_STRING) + return; + // cannot set from 'objectvars' + if (type == MMSL_VARIABLE_OBJECTVAR || from.type == MMSL_VARIABLE_OBJECTVAR) + return; + + if ((from.type & MMSL_VARIABLECLASS_STRING) == MMSL_VARIABLECLASS_STRING) + { + if (type == MMSL_VARIABLE_STRING || type == MMSL_VARIABLE_RET_STRING) + { + if (sval != NULL) + { + if (from.sval == NULL) + *sval = ""; + else + *sval = *from.sval; + } + else + sval = (from.sval == NULL) ? new_SPString() : new_SPString(*from.sval); + } else + sval = (from.sval == NULL) ? new_SPString() : new_SPString(*from.sval); + ival = 0; + type = (from.type == MMSL_VARIABLE_RET_STRING) ? MMSL_VARIABLE_RET_STRING : MMSL_VARIABLE_STRING; + } + else if ((from.type & MMSL_VARIABLECLASS_INTEGER) == MMSL_VARIABLECLASS_INTEGER) + { + if (type == MMSL_VARIABLE_STRING || type == MMSL_VARIABLE_RET_STRING) + delete_SPString(sval); + + ival = from.ival; + sval = NULL; + type = MMSL_VARIABLE_INTEGER; + } + + // pass new value for registered vars + if (ref == NULL) + { + if (ID >= 0 && set_callback != NULL) + set_callback(ID, this, param, obj_id, &obj); + Update(false); + } +} + +void MmslVariable::Update(bool immediate_trigger) +{ + if (mmsl == NULL) + return; + if (type == MMSL_VARIABLE_OBJECTVAR) + return; + + // UpdateTriggers() + int i, num_e = mmsl->events.GetN(); + for (i = idx_lut1; i >= 0 && i <= idx_lut2; i++) + { + int event_idx = mmsl->lut[i]; + if (event_idx < 0 || event_idx >= num_e) + continue; + + mmsl->Evaluate(mmsl->events[event_idx].trigger); + } + + for (i = idx_lut1; i >= 0 && i <= idx_lut2; i++) + { + mmsl->TriggerEvent(mmsl->lut[i], immediate_trigger); + } +} + +int MmslVariable::GetInteger() +{ + if ((type & MMSL_VARIABLECLASS_INTEGER) == MMSL_VARIABLECLASS_INTEGER) + return ival; + if ((type & MMSL_VARIABLECLASS_STRING) == MMSL_VARIABLECLASS_STRING) + { + if (sval != NULL) + return atol(*sval); + } + return 0; +} + +SPString &MmslVariable::GetString() +{ + if ((type & MMSL_VARIABLECLASS_STRING) == MMSL_VARIABLECLASS_STRING) + return *sval; + static SPString str; + str.FromInteger(ival); + return str; +} + +//////////////////////////////////////////////////// + +MmslObject::~MmslObject() +{ + // call 'on_delete' + if (class_idx >= 0 && class_idx < mmsl->classes.GetN()) + { + MmslClass *cls = &mmsl->classes[class_idx]; + if (cls->delete_callback != NULL) + cls->delete_callback(cls->ID, &obj, cls->param); + } +} + +//////////////////////////////////////////////////// + +MmslVariable *Mmsl::GetVariable(int ID) +{ + if (ID >= 0 && ID < idvars.GetN()) + { + return &vars[idvars[ID]]; + } + return NULL; +} + +MmslClass *Mmsl::GetClass(int ID) +{ + if (ID >= 0 && ID < idclasses.GetN()) + { + return &classes[idclasses[ID]]; + } + return NULL; +} + +BOOL Mmsl::UpdateVariable(int ID) +{ + MmslVariable *ret = GetVariable(ID); + if (ret != NULL) + { + ret->Update(false); + return TRUE; + } + return FALSE; +} + +BOOL Mmsl::UpdateObjectVariable(MMSL_OBJECT obj, int ID) +{ + MmslObject tmp, *ret; + tmp.obj = obj; + ret = objects.Get(tmp); + if (ret != NULL && ret->assigned_obj != NULL && ret->class_idx >= 0 && ret->class_idx < classes.GetN()) + { + MmslClass &c = classes[ret->class_idx]; + if (ID >= 0 && ID < IDXLIST_NUM(c., idobjvars)) + { + int vi = IDXLIST(c., idobjvars, ID); + if (vi >= 0 && vi < IDXLIST_NUM(ret->assigned_obj->, assigned_vars)) + { + int vidx = IDXLIST(ret->assigned_obj->, assigned_vars, vi); + if (vidx >= 0 && vidx < vars.GetN()) + { + vars[vidx].Update(false); + return TRUE; + } + } + } + } + return FALSE; +} + +void Mmsl::TriggerEvent(int event_idx, bool immediate_trigger) +{ + if (event_idx < 0 || event_idx >= events.GetN()) + return; + int i; + MmslEvent &e = events[event_idx]; + + // we don't care if there's nothing to execute... + if (IDXLIST_NUM(e., execute) < 1) + return; + + // check additional conditions + IDXLIST_FOR(i, e., conditions) + { + int idx = IDXLISTABS(e., conditions, i); + if (idx < 0 || idx >= events.GetN()) + continue; + +#ifdef MMSL_DEBUG + if (events[idx].trigger.value >= 0) + { + int oldval = events[idx].trigger.value; + + Evaluate(events[idx].trigger); + if (events[idx].trigger.value != oldval) + { + //MmslFile *oldcurfile = curfile; + //curfile = &events[idx].trigger.pos; + + mmsl_run_error("Trigger expression mismatch for event %d!", idx); + + //curfile = oldcurfile; + + events[idx].trigger.value = -1; + } + } +#endif + if (events[idx].trigger.value == 0 || (events[idx].trigger.value < 0 && !Evaluate(events[idx].trigger))) + return; + } + + // check trigger condition + if (e.trigger.value == 1 || (e.trigger.value < 0 && Evaluate(e.trigger))) + { + if (!immediate_trigger && e.counter >= cur_counter) + { + need_to_run_events = true; + e.counter = cur_counter + 1; + return; + } + e.counter = cur_counter; + IDXLIST_FOR(i, e., execute) + { + Evaluate(IDXLISTABS(e., execute, i)); + } + if (e.counter <= cur_counter) + e.counter = 0; + } +} + +bool Mmsl::Evaluate(MmslExpression &e) +{ + e.value = 0; + if (IDXLIST_NUM(e., tokens) < 1) + return false; + if (IDXLIST(e., tokens, 0) == MMSL_CONST_TRUE) + { + e.value = 1; + return true; + } + if (IDXLIST(e., tokens, 0) == MMSL_OPERATOR_ADD) + { + // tokens[1] = type (index into classes) + if (IDXLIST(e., tokens, 1) < 0 || IDXLIST(e., tokens, 1) > classes.GetN()) + return false; + // tokens[2] = name (index into assigned_objs) + if (IDXLIST(e., tokens, 2) < 0 || IDXLIST(e., tokens, 2) > assigned_objs.GetN()) + return false; + MmslAssignedObject *an = &assigned_objs[IDXLIST(e., tokens, 2)]; + if (an == NULL) + return false; + // remove old object reference + if (an->obj != NULL) + an->obj->assigned_obj = NULL; + int index = IDXLIST(e., tokens, 1); + MmslClass *cls = &classes[index]; + if (cls == NULL || cls->add_callback == NULL) + return false; + MMSL_OBJECT obj = NULL; + cls->add_callback(cls->ID, &obj, cls->param); + MmslObject *newobj = new MmslObject(); + if (newobj == NULL) + return false; + newobj->class_idx = index; + newobj->obj = obj; + // set new object reference + newobj->assigned_obj = an; + an->obj = newobj; + int i; + IDXLIST_FOR(i, an->, assigned_vars) + { + int vi = IDXLISTABS(an->, assigned_vars, i); + if (vi >= 0 && vi < vars.GetN()) + { + vars[vi].obj = obj; + } + } + objects.Add(newobj); + return true; + } + if (IDXLIST(e., tokens, 0) == MMSL_OPERATOR_DELETE) + { + MmslObject *cur = objects.GetFirst(), *next = NULL; + bool was_del = false; + while (cur != NULL) + { + next = objects.GetNext(*cur); + if (EvaluateMath(e, 1, cur, true)) + { + // unset object variables + if (cur->assigned_obj != NULL) + { + cur->assigned_obj->obj = NULL; + int i; + IDXLIST_FOR(i, cur->assigned_obj->, assigned_vars) + { + int vi = IDXLISTABS(cur->assigned_obj->, assigned_vars, i); + if (vi > 0 && vi < vars.GetN()) + { + vars[vi].obj = NULL; + } + } + } + objects.Delete(*cur); + was_del = true; + } + cur = next; + } + return was_del; + } + e.value = EvaluateMath(e, 0, NULL) != 0 ? 1 : 0; + return e.value != 0; +} + + +int Mmsl::EvaluateMath(const MmslExpression & e, int start, MmslObject *obj, bool del) +{ + //msg("----------------- EVAL %d %d\n", e.pos.row, e.pos.col); + + const int max_vars_in_stack = 64; + /// this is a bit dirty way, because ctor is not called, but it's ok + MmslVariable *var_stack = (MmslVariable *)SPalloca(max_vars_in_stack * sizeof(MmslVariable)); + if (var_stack == NULL) + { + mmsl_run_error("Cannot allocate variable stack."); + return 0; + } + int i, var_stack_idx = -1; + + // for correct error/debug report + bool was_err = false; +#ifdef MMSL_DEBUG + //curfile = (MmslFile *)&e.pos; +#endif + + IDXLIST_FOR_OFFS(i, e., tokens, start) + { + int tokens_i = IDXLISTABS(e., tokens, i); + if (tokens_i > MMSL_OPERATOR_UNKNOWN1 && tokens_i < MMSL_OPERATOR_UNKNOWN2) // operator + { + if (var_stack_idx < 1) + { + mmsl_run_error("Cannot evaluate expression (operands expected)."); + was_err = true; + break; + } + MMSL_OPERATOR_TYPE op = (MMSL_OPERATOR_TYPE)tokens_i; + MmslVariable *ret = Calc(op, var_stack[var_stack_idx - 1].ref, + var_stack[var_stack_idx].ref, IDXLIST_LAST(i, e., tokens)); + if (ret == NULL) + { + was_err = true; + break; + } + + // clean-up top-stack var + if (var_stack[var_stack_idx].type == MMSL_VARIABLE_STRING + || var_stack[var_stack_idx].type == MMSL_VARIABLE_RET_STRING) + delete_SPString(var_stack[var_stack_idx].sval); + var_stack[--var_stack_idx].Set(*ret); + var_stack[var_stack_idx].ref = &var_stack[var_stack_idx]; + } else + { + if (tokens_i >= 0 && tokens_i < vars.GetN()) + { + MmslVariable *v = &vars[tokens_i]; + // evaluate abstract object variable using given object idx + if (v->type == MMSL_VARIABLE_OBJECTVAR && obj != NULL) + { + if (obj->class_idx < 0 || obj->class_idx >= IDXLIST_NUM(v->, clut)) + { + mmsl_run_error("Cannot find object variable."); + was_err = true; + break; + } + + int vidx = IDXLIST(v->, clut, obj->class_idx); + if (vidx < 0 || vidx >= vars.GetN()) + { + // for delete condition it's normal situation + if (del) + return 0; + mmsl_run_error("Cannot find object variable."); + was_err = true; + break; + } + //v = &IDXLIST(classes[obj->class_idx]., vars, vidx); + v = &vars[vidx]; + } + // get current value of the variable or object's variable + // TODO: l-values are also queried - it's no good... + if (v->type == MMSL_VARIABLE_INTEGER || v->type == MMSL_VARIABLE_STRING || + v->type == MMSL_VARIABLE_OBJECT_INTEGER || v->type == MMSL_VARIABLE_OBJECT_STRING) + { + if (v->get_callback != NULL) + v->get_callback(v->ID, v, v->param, v->obj_id, obj != NULL ? &obj->obj : &v->obj); + } + if (var_stack_idx >= max_vars_in_stack) + { + mmsl_run_error("Too long expression, stack overflow."); + return 0; + } + ++var_stack_idx; + var_stack[var_stack_idx].type = MMSL_VARIABLE_INTEGER; + var_stack[var_stack_idx].ref = v; + var_stack[var_stack_idx].ID = -1; + } + } + } + + // stack vars clean-up + for (i = 0; i <= var_stack_idx; i++) + { + if (var_stack[i].type == MMSL_VARIABLE_STRING || + var_stack[i].type == MMSL_VARIABLE_RET_STRING) + delete_SPString(var_stack[i].sval); + } + +#ifdef MMSL_DEBUG + //curfile = NULL; +#endif + + if (was_err) + return 0; + if (var_stack_idx >= 0) + { + return var_stack[var_stack_idx].ival; + } + return 0; +} + +MmslVariable *Mmsl::Calc(MMSL_OPERATOR_TYPE op, MmslVariable *arg1, MmslVariable *arg2, bool last_op) +{ + static MmslVariable ret; + + // ret is static so we need to clean-up + delete_SPString(ret.sval); + ret.ival = 0; + + if (arg1 == NULL || arg2 == NULL) + { + mmsl_run_error("Wrong arithm. arguments."); + return NULL; + } + + int d; + switch (op) + { + /////////////////////////////////////////////////// + // arithmetic: + + case MMSL_OPERATOR_MINUS: + ret.type = MMSL_VARIABLE_INTEGER; + ret.ival = arg1->GetInteger() - arg2->GetInteger(); + break; + case MMSL_OPERATOR_MUL: + ret.type = MMSL_VARIABLE_INTEGER; + ret.ival = arg1->GetInteger() * arg2->GetInteger(); + break; + case MMSL_OPERATOR_DIV: + ret.type = MMSL_VARIABLE_INTEGER; + d = arg2->GetInteger(); + if (d == 0) + { + mmsl_run_error("Division by zero."); + break; + } + ret.ival = arg1->GetInteger() / d; + break; + case MMSL_OPERATOR_MOD: + ret.type = MMSL_VARIABLE_INTEGER; + d = arg2->GetInteger(); + if (d == 0) + { + mmsl_run_error("Modulus Division by zero."); + break; + } + ret.ival = arg1->GetInteger() % d; + break; + + /////////////////////////////////////////////////// + // comparison: + + case MMSL_OPERATOR_GT: + ret.type = MMSL_VARIABLE_INTEGER; + ret.ival = arg1->GetInteger() > arg2->GetInteger(); + break; + case MMSL_OPERATOR_LT: + ret.type = MMSL_VARIABLE_INTEGER; + ret.ival = arg1->GetInteger() < arg2->GetInteger(); + break; + case MMSL_OPERATOR_GTEQU: + ret.type = MMSL_VARIABLE_INTEGER; + ret.ival = arg1->GetInteger() >= arg2->GetInteger(); + break; + case MMSL_OPERATOR_LTEQU: + ret.type = MMSL_VARIABLE_INTEGER; + ret.ival = arg1->GetInteger() <= arg2->GetInteger(); + break; + case MMSL_OPERATOR_OROR: + ret.type = MMSL_VARIABLE_INTEGER; + ret.ival = arg1->GetInteger() || arg2->GetInteger(); + break; + case MMSL_OPERATOR_ANDAND: + ret.type = MMSL_VARIABLE_INTEGER; + ret.ival = arg1->GetInteger() && arg2->GetInteger(); + break; + + /////////////////////////////////////////////////// + // special: + case MMSL_OPERATOR_PLUS: + if (arg1->type == MMSL_VARIABLE_CONST_INTEGER || arg2->type == MMSL_VARIABLE_CONST_INTEGER) + { + ret.type = MMSL_VARIABLE_INTEGER; + ret.ival = arg1->GetInteger() + arg2->GetInteger(); + } + else if (arg1->type == MMSL_VARIABLE_CONST_STRING || arg2->type == MMSL_VARIABLE_CONST_STRING + || arg1->type == MMSL_VARIABLE_RET_STRING || arg2->type == MMSL_VARIABLE_RET_STRING) + { + ret.type = MMSL_VARIABLE_RET_STRING; + ret.sval = new_SPString(arg1->GetString()); + *ret.sval += arg2->GetString(); + } + else if (arg1->type == MMSL_VARIABLE_INTEGER || arg2->type == MMSL_VARIABLE_INTEGER) + { + ret.type = MMSL_VARIABLE_INTEGER; + ret.ival = arg1->GetInteger() + arg2->GetInteger(); + } + else + { + ret.type = MMSL_VARIABLE_RET_STRING; + ret.sval = new_SPString(arg1->GetString()); + *ret.sval += arg2->GetString(); + } + break; + + case MMSL_OPERATOR_EQUEQU: + ret.type = MMSL_VARIABLE_INTEGER; + if (arg1->type == MMSL_VARIABLE_CONST_INTEGER || arg2->type == MMSL_VARIABLE_CONST_INTEGER) + ret.ival = arg1->GetInteger() == arg2->GetInteger(); + else if (arg1->type == MMSL_VARIABLE_CONST_STRING || arg2->type == MMSL_VARIABLE_CONST_STRING) + ret.ival = arg1->GetString().CompareNoCase(arg2->GetString()) == 0; + else if (arg1->type == MMSL_VARIABLE_INTEGER || arg2->type == MMSL_VARIABLE_INTEGER) + ret.ival = arg1->GetInteger() == arg2->GetInteger(); + else + ret.ival = arg1->GetString().CompareNoCase(arg2->GetString()) == 0; + break; + + case MMSL_OPERATOR_NONEQU: + ret.type = MMSL_VARIABLE_INTEGER; + if (arg1->type == MMSL_VARIABLE_CONST_INTEGER || arg2->type == MMSL_VARIABLE_CONST_INTEGER) + ret.ival = arg1->GetInteger() != arg2->GetInteger(); + else if (arg1->type == MMSL_VARIABLE_CONST_STRING || arg2->type == MMSL_VARIABLE_CONST_STRING) + ret.ival = arg1->GetString().CompareNoCase(arg2->GetString()) != 0; + else if (arg1->type == MMSL_VARIABLE_INTEGER || arg2->type == MMSL_VARIABLE_INTEGER) + ret.ival = arg1->GetInteger() != arg2->GetInteger(); + else + ret.ival = arg1->GetString().CompareNoCase(arg2->GetString()) != 0; + break; + + case MMSL_OPERATOR_EQU: + { + arg1->Set(*arg2); + // we're not in conditional, and not need the result + if (!last_op) + ret.Set(*arg2); + } + break; + default: + break; + } + + return &ret; +} + +BOOL Mmsl::Run() +{ + bool more_events = false; + + cur_counter++; + + if (cur_counter == 1) // startup + { + TriggerEvent(0, false); + } + + if (need_to_run_events) + { + need_to_run_events = false; + for (int i = 1; i < events.GetN(); i++) + { + if (events[i].counter >= cur_counter) + { + events[i].counter = 0; + if (IDXLIST_NUM(events[i]., execute) > 0) + { + TriggerEvent(i, false); + more_events = true; + } + } + } + } + return more_events; +} + +////////////////////////////////////////////////// + +BOOL Mmsl::Load(BYTE *buf, int buflen) +{ + /// \WARNING: This code is unprotected from corrupted data! + + int i; + BYTE *buf0 = buf; + MmslSerHeader *hdr = (MmslSerHeader *)buf; + buf += sizeof(MmslSerHeader); + vars.SetN(hdr->num_var); + classes.SetN(hdr->num_class); + events.SetN(hdr->num_event); + assigned_objs.SetN(hdr->num_assignedobj); + execute.SetN(hdr->num_expr); + lut.SetN(hdr->num_lut); + clut.SetN(hdr->num_clut); + assigned_vars.SetN(hdr->num_assignedvar); + conditions.SetN(hdr->num_cond); + tokens.SetN(hdr->num_token); + + BYTE *serbuf0 = (BYTE *)buf; + buf += hdr->var_buf_size; + MmslSerClass *serclass = (MmslSerClass *)buf; + buf += sizeof(MmslSerClass) * classes.GetN(); + MmslSerEvent *serevent = (MmslSerEvent *)buf; + buf += sizeof(MmslSerEvent) * events.GetN(); + MmslSerAssignedObject *serassignedobj = (MmslSerAssignedObject *)buf; + buf += sizeof(MmslSerAssignedObject) * assigned_objs.GetN(); + MmslSerExpression *serexpr = (MmslSerExpression *)buf; + buf += sizeof(MmslSerExpression) * execute.GetN(); + short *serlut = (short *)buf; + buf += sizeof(short) * lut.GetN(); + short *serclut = (short *)buf; + buf += sizeof(short) * clut.GetN(); + short *serassignedvars = (short *)buf; + buf += sizeof(short) * assigned_vars.GetN(); + short *sercond = (short *)buf; + buf += sizeof(short) * conditions.GetN(); + short *sertoken = (short *)buf; + buf += sizeof(short) * tokens.GetN(); + char *str_buf = (char *)buf; + + if (buflen < (int)(buf - buf0)) + { + msg("Mmsl: It seems that MMSO buffer is damaged!\n"); + } + + BYTE *serbuf = serbuf0; + for (i = 0; i < vars.GetN(); i++) + { + vars[i].type = (MMSL_VARIABLE_TYPE)ReadChar(serbuf); + + if (vars[i].type & MMSL_VARIABLE_HAS_ID) + vars[i].ID = ReadShort(serbuf); + if (vars[i].type & MMSL_VARIABLE_HAS_LUT) + { + vars[i].idx_lut1 = ReadShort(serbuf); + vars[i].idx_lut2 = ReadShort(serbuf); + } + if (vars[i].type & MMSL_VARIABLE_HAS_CLUT) + { + vars[i].idx_clut1 = ReadChar(serbuf); + vars[i].idx_clut2 = ReadChar(serbuf); + } + if (vars[i].type & MMSL_VARIABLE_HAS_OBJID) + vars[i].obj_id = ReadChar(serbuf); + + vars[i].type = (MMSL_VARIABLE_TYPE)(vars[i].type & MMSL_VARIABLE_MASK); + + int val = ReadInt(serbuf); + if (vars[i].type == MMSL_VARIABLE_CONST_STRING) + { + vars[i].ival = 0; + vars[i].sval = (val >= 0) ? new_SPString(str_buf + val) : new_SPString(); + } else + { + vars[i].ival = val; + vars[i].sval = NULL; + } + } + + if (PAD_EVEN(serbuf - serbuf0) != hdr->var_buf_size) + { + msg("Mmsl: It seems that MMSO var.buffer is damaged!\n"); + } + + int max_ID = 0; + for (i = 0; i < vars.GetN(); i++) + { + if (vars[i].ID > max_ID) + max_ID = vars[i].ID; + } + idvars.SetN(max_ID + 1); + for (i = 0; i <= max_ID; i++) + idvars[i] = -1; + + SPList num_idobjvars; + int max_num_idobjvars = 0; + num_idobjvars.SetN(classes.GetN()); + for (i = 0; i < classes.GetN(); i++) + num_idobjvars[i] = 0; + for (i = 0; i < vars.GetN(); i++) + { + if (vars[i].obj_id >= 0 && vars[i].obj_id < num_idobjvars.GetN()) + { + num_idobjvars[vars[i].obj_id]++; + if (max_num_idobjvars < vars[i].ID + 1) + max_num_idobjvars = vars[i].ID + 1; + } + if (vars[i].ID >= 0 && vars[i].obj_id < 0) + idvars[vars[i].ID] = i; + } + + max_ID = 0; + for (i = 0; i < classes.GetN(); i++) + { + if (serclass[i].ID > max_ID) + max_ID = serclass[i].ID; + } + idclasses.SetN(max_ID + 1); + for (i = 0; i <= max_ID; i++) + idclasses[i] = -1; + + idobjvars.SetN(max_num_idobjvars * classes.GetN()); + for (i = 0; i < idobjvars.GetN(); i++) + idobjvars[i] = -1; + + for (i = 0; i < classes.GetN(); i++) + { + classes[i].ID = serclass[i].ID; + classes[i].idx_vars1 = serclass[i].idx_vars1; + classes[i].idx_vars2 = serclass[i].idx_vars2; + + if (classes[i].ID >= 0) + idclasses[classes[i].ID] = i; + + classes[i].idx_idobjvars1 = i * max_num_idobjvars; + classes[i].idx_idobjvars2 = (i + 1) * max_num_idobjvars - 1; + + int local_idx = 0; + for (int j = 0; j < vars.GetN(); j++) + { + if (vars[j].obj_id == classes[i].ID) + { + int k = classes[i].idx_idobjvars1 + vars[j].ID; + if (idobjvars[k] == -1) + idobjvars[k] = local_idx++; + } + } + } + + for (i = 0; i < events.GetN(); i++) + { + events[i].trigger.idx_tokens1 = serevent[i].idx_triggertokens1; + events[i].trigger.idx_tokens2 = serevent[i].idx_triggertokens2; + events[i].idx_conditions1 = serevent[i].idx_conditions1; + events[i].idx_conditions2 = serevent[i].idx_conditions2; + events[i].idx_execute1 = serevent[i].idx_execute1; + events[i].idx_execute2 = serevent[i].idx_execute2; + } + + for (i = 0; i < assigned_objs.GetN(); i++) + { + assigned_objs[i].idx_assigned_vars1 = serassignedobj[i].idx_assigned_vars1; + assigned_objs[i].idx_assigned_vars2 = serassignedobj[i].idx_assigned_vars2; + } + + for (i = 0; i < execute.GetN(); i++) + { + execute[i].idx_tokens1 = serexpr[i].idx_tokens1; + execute[i].idx_tokens2 = serexpr[i].idx_tokens2; + } + + for (i = 0; i < lut.GetN(); i++) + lut[i] = serlut[i]; + for (i = 0; i < clut.GetN(); i++) + clut[i] = serclut[i]; + for (i = 0; i < assigned_vars.GetN(); i++) + assigned_vars[i] = serassignedvars[i]; + for (i = 0; i < conditions.GetN(); i++) + conditions[i] = sercond[i]; + for (i = 0; i < tokens.GetN(); i++) + tokens[i] = sertoken[i]; + + msg("Mmsl Loaded: %d vars, %d cls, %d evnts, %d exec, %d asno, %d asnv\n", + vars.GetN(), classes.GetN(), events.GetN(), execute.GetN(), assigned_objs.GetN(), assigned_vars.GetN()); + msg(" %d conds, %d tokens, %d lut, %d clut\n", + conditions.GetN(), tokens.GetN(), lut.GetN(), clut.GetN()); + msg(" %d idv, %d ido, %d idc\n", + idvars.GetN(), idobjvars.GetN(), idclasses.GetN()); + + return TRUE; +} + +int Mmsl::ReadChar(BYTE* &buf) +{ + return (int)(*buf++); +} + +int Mmsl::ReadShort(BYTE* &buf) +{ + int v = (buf[1] << 8) | buf[0]; + buf += 2; + return v; +} + +int Mmsl::ReadInt(BYTE* &buf) +{ + int v = (buf[3] << 24) | (buf[2] << 16) | (buf[1] << 8) | buf[0]; + buf += 4; + return v; +} + +#ifdef MMSL_DUMP + +void Mmsl::Dump() +{ + int i; + for (i = 0; i < vars.GetN(); i++) + { + msg("V[%d] %d %d, %d \"%s\" %d %d %d %d, %d, %c %c\n", + i, vars[i].type, vars[i].ID, vars[i].ival, (vars[i].sval ? **vars[i].sval : ""), + vars[i].idx_lut1, vars[i].idx_lut2, vars[i].idx_clut1, vars[i].idx_clut2, + vars[i].obj_id, + (vars[i].get_callback ? 'G' : '.'), (vars[i].set_callback ? 'S' : '.')); + } + + msg("\n\n-----------------------------------\n\n"); + + for (i = 0; i < classes.GetN(); i++) + { + msg("C[%d] %d, %d %d %d %d, %c %c\n", + i, classes[i].ID, + classes[i].idx_idobjvars1, classes[i].idx_idobjvars2, classes[i].idx_vars1, classes[i].idx_vars2, + classes[i].add_callback ? 'A' : '.', classes[i].delete_callback ? 'S' : '.'); + + } + + msg("\n\n-----------------------------------\n\n"); + + for (i = 0; i < events.GetN(); i++) + { + msg("E[%d] %d %d, %d %d, %d %d\n", + i, events[i].trigger.idx_tokens1, events[i].trigger.idx_tokens2, + events[i].idx_conditions1, events[i].idx_conditions2, + events[i].idx_execute1, events[i].idx_execute2); + } + + msg("\n\n-----------------------------------\n\n"); + + for (i = 0; i < execute.GetN(); i++) + { + msg("X[%d] %d %d\n", + i, execute[i].idx_tokens1, execute[i].idx_tokens2); + } + + msg("\n\n-----------------------------------\n\n"); + + for (i = 0; i < assigned_objs.GetN(); i++) + { + msg("A[%d] %d %d\n", + i, assigned_objs[i].idx_assigned_vars1, assigned_objs[i].idx_assigned_vars2); + } + + DumpIntList("AV", assigned_vars); + DumpIntList("CO", conditions); + DumpIntList("TO", tokens); + DumpIntList("LU", lut); + DumpIntList("CL", clut); + DumpIntList("IV", idvars); + DumpIntList("IO", idobjvars); + DumpIntList("IC", idclasses); + +} + +template +void Mmsl::DumpIntList(const char *n, const L &list) +{ + int i; + msg("\n\n-----------------------------------\n\n"); + for (i = 0; i < list.GetN() / 16 * 16; i+=16) + { + msg("%s[%d] %4d %4d %4d %4d %4d %4d %4d %4d %4d %4d %4d %4d %4d %4d %4d %4d\n", n, i, + list[i+0], list[i+1], list[i+2], list[i+3], list[i+4], list[i+5], list[i+6], list[i+7], + list[i+8], list[i+9], list[i+10], list[i+11], list[i+12], list[i+13], list[i+14], list[i+15]); + } + for (; i < list.GetN(); i++) + { + msg("%s[%d] %d\n", n, i, list[i]); + } +} + +#endif + +////////////////////////////////////////////////// + +SPString *new_SPString() +{ + return new SPString(); +} + +SPString *new_SPString(const SPString & s) +{ + return new SPString(s); +} + +void delete_SPString(SPString * & s) +{ + SPSafeDelete(s); + s = NULL; +} diff --git a/src/mmsl/mmsl.h b/src/mmsl/mmsl.h new file mode 100644 index 0000000..8429ef7 --- /dev/null +++ b/src/mmsl/mmsl.h @@ -0,0 +1,537 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - MMSL interpreter header file + * \file mmsl/mmsl.h + * \author bombur + * \version 0.1 + * \date 4.10.2006 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#ifndef SP_MMSL_H +#define SP_MMSL_H + + +#define MMSL_VERSION "0.94" + +#ifdef WIN32 +//#define MMSL_DEBUG +#endif + +class MmslVariable; + +typedef void* MMSL_OBJECT; +typedef int MMSL_EXPR_TOKEN; + +typedef void (*MmslObjectCallback)(int obj_id, MMSL_OBJECT *obj, void *param); +typedef void (*MmslVariableCallback)(int var_id, MmslVariable *var, void *param, int obj_id, MMSL_OBJECT *obj); + +SPString *new_SPString(); +SPString *new_SPString(const SPString &); +void delete_SPString(SPString * &); + +const int mmsl_num_hash = 347; + +enum MMSL_VARIABLE_CLASS +{ + MMSL_VARIABLECLASS_INTEGER = 0x4, + MMSL_VARIABLECLASS_STRING = 0x8, +}; + + +enum MMSL_VARIABLE_TYPE +{ + MMSL_VARIABLE_INTEGER = MMSL_VARIABLECLASS_INTEGER, + MMSL_VARIABLE_CONST_INTEGER = MMSL_VARIABLECLASS_INTEGER | 1, + MMSL_VARIABLE_OBJECT_INTEGER = MMSL_VARIABLECLASS_INTEGER | 2, + + MMSL_VARIABLE_STRING = MMSL_VARIABLECLASS_STRING, + MMSL_VARIABLE_CONST_STRING = MMSL_VARIABLECLASS_STRING | 1, + MMSL_VARIABLE_RET_STRING = MMSL_VARIABLECLASS_STRING | 2, + MMSL_VARIABLE_OBJECT_STRING = MMSL_VARIABLECLASS_STRING | 3, + + MMSL_VARIABLE_OBJECTVAR, // '.varname' + + // these used only for serialization + MMSL_VARIABLE_HAS_ID = 0x10, + MMSL_VARIABLE_HAS_LUT = 0x20, + MMSL_VARIABLE_HAS_CLUT = 0x40, + MMSL_VARIABLE_HAS_OBJID = 0x80, + + MMSL_VARIABLE_MASK = ~(MMSL_VARIABLE_HAS_ID | MMSL_VARIABLE_HAS_LUT | MMSL_VARIABLE_HAS_CLUT | MMSL_VARIABLE_HAS_OBJID), +}; + +enum MMSL_OPERATOR_TYPE +{ + MMSL_OPERATOR_UNKNOWN1 = -100, + + MMSL_OPERATOR_LP, // ( + MMSL_OPERATOR_RP, // ) + MMSL_OPERATOR_PLUS, // + + MMSL_OPERATOR_MINUS, // - + MMSL_OPERATOR_MUL, // * + MMSL_OPERATOR_DIV, // / + MMSL_OPERATOR_MOD, // % + MMSL_OPERATOR_NONEQU, // != + MMSL_OPERATOR_GT, // > + MMSL_OPERATOR_LT, // < + MMSL_OPERATOR_GTEQU, // >= + MMSL_OPERATOR_LTEQU, // <= + MMSL_OPERATOR_EQUEQU, // == + MMSL_OPERATOR_OROR, // || + MMSL_OPERATOR_ANDAND, // && + MMSL_OPERATOR_EQU, // = + + MMSL_OPERATOR_UNKNOWN2, + + // 'add object' operator + // arg1 = index into classes + // arg2 = index into 'assigned_objects' + MMSL_OPERATOR_ADD, + + // 'delete objects' operator + // following arguments = deletion condition + MMSL_OPERATOR_DELETE, + + // special operator (no args) - returns true. + // used for 'default' event condition + MMSL_CONST_TRUE, +}; + +// lexemes for tokens are case-insensitive! +enum MMSL_TOKEN_TYPE +{ + MMSL_TOKEN_UNDEFINED = 0, + + MMSL_TOKEN_ID, // [a-z][a-z0-9]+ + MMSL_TOKEN_NUMBER, // [0-9]+ + MMSL_TOKEN_STRING, // ['"][^"']*["'] + + MMSL_TOKEN_ADD, // "add" + MMSL_TOKEN_BREAK, // "break" + MMSL_TOKEN_DELETE, // "delete" + MMSL_TOKEN_INCLUDE, // "include" + MMSL_TOKEN_ON, // "on" + + // operators + MMSL_TOKEN_LP, // ( + MMSL_TOKEN_RP, // ) + MMSL_TOKEN_PLUS, // + + MMSL_TOKEN_MINUS, // - + MMSL_TOKEN_MUL, // * + MMSL_TOKEN_DIV, // / + MMSL_TOKEN_MOD, // % + MMSL_TOKEN_NONEQU, // != + MMSL_TOKEN_GT, // > + MMSL_TOKEN_LT, // < + MMSL_TOKEN_GTEQU, // >= + MMSL_TOKEN_LTEQU, // <= + MMSL_TOKEN_EQUEQU, // == + MMSL_TOKEN_OROR, // || + MMSL_TOKEN_ANDAND, // && + MMSL_TOKEN_EQU, // = + + + MMSL_TOKEN_OPERATOR_MAX, + + // special tokens + + MMSL_TOKEN_INDENT, // indent at the start of a line + MMSL_TOKEN_EOL, // end of line [\r\n] + MMSL_TOKEN_EOF, // end of file + // comments are skipped: + // \/\/[^\n]* c++ comments + // \/\*(.*?)\*\/ c-like comments + + // fake 'unary operator' token definition - for correct priorities + MMSL_TOKEN_UNARY, +}; + + + +//////////////////////////////////////////////////////////////////// + +/// Built-in or user-defined variable (also object var.) +class MmslVariable +{ +public: + /// ctor + MmslVariable(); + + /// dtor + ~MmslVariable() + { + delete_SPString(sval); + //SPSafeDelete(lut); + //SPSafeDelete(clut); + } + + void Set(int value); + void Set(const SPString & value); + void Set(const MmslVariable & from); + + int GetInteger(); + /// not thread-safe + SPString &GetString(); + + void Update(bool immediate_trigger); + + MmslVariable (const MmslVariable & v) + { + type = v.type; + ival = v.ival; + sval = v.sval != NULL ? new_SPString(*v.sval) : NULL; + ID = v.ID; + } + + MmslVariable & operator = (const MmslVariable & v) + { + type = v.type; + ival = v.ival; + sval = v.sval != NULL ? new_SPString(*v.sval) : NULL; + get_callback = v.get_callback; + set_callback = v.set_callback; + param = v.param; + obj = v.obj; + obj_id = v.obj_id; + ID = v.ID; + //idx_lut1 = idx_lut2 = -1; + //idx_clut1 = idx_clut2 = -1; + + return (*this); + } + +public: + /// set for tmp vars - references to the real ones + MmslVariable *ref; + + MMSL_VARIABLE_TYPE type; + + int ID; + + int ival; + SPString *sval; + + /// 1) lut='event_deps' - A list of dependent event indexes. + /// If variable changes, they should be revised. + //SPClassicList lut; + int idx_lut1, idx_lut2; + /// 2) lut='class_var_idx' for MMSL_VARIABLE_OBJECTVAR + /// lut[class_idx] = class_var_idx (MmslClass::vars) + /// or -1 if variable not used in class + //SPClassicList clut; + int idx_clut1, idx_clut2; + + /// object ID (for object vars only) + int obj_id; + /// object pointer + MMSL_OBJECT obj; + + /// For 'system' pre-set variables + MmslVariableCallback get_callback, set_callback; + void *param; +}; + +/// Registered object class (for 'add [object] [name]' command) +class MmslClass +{ +public: + MmslClass() + { + add_callback = 0; + delete_callback = 0; + param = NULL; + ID = -1; + + idx_vars1 = idx_vars2 = -1; + idx_idobjvars1 = idx_idobjvars2 = -1; + } + + /// dtor + ~MmslClass() + { + } + +public: + MmslObjectCallback add_callback, delete_callback; + void *param; + + int ID; + + // idobjvars[classes[i].idx_idobjvars1 + var_ID] = vars_idx + int idx_idobjvars1, idx_idobjvars2; // listed by ID + + /// Abstract Variables (like ".var") + int idx_vars1, idx_vars2; +}; + +/// Saved file position (for error reports) +class MmslFile +{ +public: + /// ctor + MmslFile() + { + row = col = 0; + } + + /// dtor + ~MmslFile() {} + +public: + SPString fname; + int row, col; +}; + +/// Expression storage +class MmslExpression +{ +public: + /// ctor + MmslExpression() + { + idx_tokens1 = idx_tokens2 = -1; + value = -1; + } + + // Tokens are in PPN order. + // Operators ADD and DELETE come first. + // Contains variable/constant indexes or operators (< 0). + + //SPList tokens; + int idx_tokens1, idx_tokens2; + + /// Last calculated value + int value; + +#ifdef MMSL_DEBUG + /// expression location (for error/debug) + MmslFile pos; +#endif +}; + +/// Conditional 'on' block +class MmslEvent +{ +public: + /// ctor + MmslEvent() + { + counter = 0; + indent = 0; + idx_conditions1 = idx_conditions2 = -1; + idx_execute1 = idx_execute2 = -1; + } + +public: + /// Iteration counter + int counter; + + /// event statement indent (used to align sub-statements) + int indent; + + /// This one triggers event + MmslExpression trigger; + + /// These conditions (indexes stored) must be true to trigger this event + /// triggers of the parent events) + /// Pointers to the trigger conditions of other events + //SPClassicList conditions; + int idx_conditions1, idx_conditions2; + + /// We need to execute these if event triggered + //SPClassicList execute; + int idx_execute1, idx_execute2; +}; + +class MmslObject; + +/// An object with assigned name (i.e. used in 'add obj name' operator). +/// Used to re-assign objects (MmslObject) to names. +/// Created each time 'add obj' operator encounted. +class MmslAssignedObject +{ +public: + /// ctor + MmslAssignedObject() + { + obj = NULL; + idx_assigned_vars1 = idx_assigned_vars2 = -1; + } + +public: + /// Link to the object + MmslObject *obj; + + /// Indexes of created object variables + /// Created from MmslClass::vars, the same size! + /// Used to access object variables via 'Update' mechanism + // SPList vars; + int idx_assigned_vars1, idx_assigned_vars2; +}; + +/// Dynamically created object +class MmslObject +{ +public: + MmslObject() + { + class_idx = -1; + obj = NULL; + assigned_obj = NULL; + prev = next = NULL; + } + + /// dtor + ~MmslObject(); + +public: + friend class MmslAssignedObject; + + int class_idx; + MMSL_OBJECT obj; + + /// Set if a name exists + MmslAssignedObject *assigned_obj; + +public: + const MmslObject * const GetItem() const + { + return this; + } + + template + bool operator == (const T & o) + { + return obj == o.obj; + } + + inline operator DWORD () const + { + return (DWORD)obj; + } + + + // linked list support + MmslObject *prev, *next; +}; + +//////////////////////////////////////////////////////////////// + +/// MMSL Interpreter singleton class +class Mmsl +{ +public: + /// ctor + Mmsl(); + /// dtor + ~Mmsl(); + + // user land: + + /// Serialization: Save all created data into the buffer + BOOL Load(BYTE *buf, int buflen); + + /// Bind callbacks to the predefined external variable. + int BindVariable(int ID, MmslVariableCallback Get = NULL, MmslVariableCallback Set = NULL, + void *param = NULL); + + /// Bind callbacks to the dynamic object type - to create/delete it from script + int BindObject(int ID, MmslObjectCallback Add, MmslObjectCallback Delete, void *param); + + /// Bind callbacks to the object variables + int BindObjectVariable(int obj_ID, int var_ID, MmslVariableCallback Get, MmslVariableCallback Set, + void *param); + + /// Run script - call this from your program loop every time + BOOL Run(); + + /// Call this to trigger script events when one of variables changed + BOOL UpdateVariable(int ID); + + /// Call this to trigger script events when one of object variables changed + BOOL UpdateObjectVariable(MMSL_OBJECT obj, int ID); + + MmslVariable *GetVariable(int ID); + MmslClass *GetClass(int ID); + + void Dump(); + +public: + /// Created objects + SPHashListAbstract objects; + + /// registered/created variables + SPClassicList vars; + + /// registered object classes + SPClassicList classes; + + /// Events list (contains ALL events). Also used for triggering events. + SPClassicList events; + + /// name-assigned objects (linked to objects) + SPClassicList assigned_objs; + + /// Expressions + SPClassicList execute; + + // internal lists for events/variables/assigned_objs: + SPList lut; + SPList clut; // clut[var_offs + class_idx] = var_idx + SPList assigned_vars; + SPList conditions; + SPList tokens; + + /// LUT for update-vars mechanism + SPList idvars; + /// LUT for update-objectvars mechanism + SPList idobjvars; + /// LUT for classes + SPList idclasses; + +protected: + friend class MmslVariable; + friend void mmsl_run_error(const char *, ...); + + /// Current execution pass counter + int cur_counter; + bool need_to_run_events; + + void SetVariable(int var_idx, bool immediate_trigger); + + void TriggerEvent(int event_idx, bool immediate_trigger); + + bool Evaluate(MmslExpression &e); + + /// Not thread-safe! + int EvaluateMath(const MmslExpression & e, int start, MmslObject *obj, bool del = false); + /// Not thread-safe! + MmslVariable *Calc(MMSL_OPERATOR_TYPE op, MmslVariable *arg1, MmslVariable *arg2, bool last_op); + +private: + inline int ReadChar(BYTE* &buf); + inline int ReadShort(BYTE* &buf); + inline int ReadInt(BYTE* &buf); + + template + void DumpIntList(const char *n, const L &list); +}; + +extern Mmsl *mmsl; + +#endif // of SP_MMSL_H diff --git a/src/module-dvd.cpp b/src/module-dvd.cpp new file mode 100644 index 0000000..6f22c18 --- /dev/null +++ b/src/module-dvd.cpp @@ -0,0 +1,130 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - DVD module interface impl. + * \file module-dvd.cpp + * \author bombur + * \version 0.1 + * \date 10.12.2008 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#ifdef COMPILE_MODULE + #define MODULE_SERVER +#endif + +#ifdef MODULE_SERVER + #include + #include + #include + + #include +#endif + +#include +#include + +extern "C" { + +MODULE_DECL_START() + + MODULE_DECL(dvd_open), + MODULE_DECL(dvd_get_next_block), + MODULE_DECL(dvd_free_block), + MODULE_DECL(dvd_close), + MODULE_DECL(dvd_reset), + MODULE_DECL(dvd_error_string), + MODULE_DECL(dvd_do_command), + MODULE_DECL(dvd_stop), + MODULE_DECL(dvd_getnumtitles), + MODULE_DECL(dvd_play), + MODULE_DECL(dvd_setdeflang_spu), + MODULE_DECL(dvd_setdeflang_audio), + MODULE_DECL(dvd_setdeflang_menu), + MODULE_DECL(dvd_button_play), + MODULE_DECL(dvd_getnumchapters), + MODULE_DECL(dvd_get_cur), + MODULE_DECL(dvd_seek_titlepart), + MODULE_DECL(dvd_seek), + MODULE_DECL(dvd_getdebug), + MODULE_DECL(dvd_ismenu), + MODULE_DECL(dvd_getangle), + MODULE_DECL(dvd_getspeed), + MODULE_DECL(dvd_get_saved), + MODULE_DECL(dvd_setdebug), + MODULE_DECL(dvd_get_total_time), + MODULE_DECL(dvd_get_chapter_for_time), + MODULE_DECL(dvd_button_subtitle), + MODULE_DECL(dvd_button_audio), + MODULE_DECL(dvd_button_menu), + MODULE_DECL(dvd_button_angle), + MODULE_DECL(dvd_setspeed), + MODULE_DECL(dvd_player_loop), + +MODULE_DECL_END(); + + +MODULE_FUNC_TABLE dvd_funcs[] = +{ + MODULE_ENTRY(dvd_open), + MODULE_ENTRY(dvd_get_next_block), + MODULE_ENTRY(dvd_free_block), + MODULE_ENTRY(dvd_close), + MODULE_ENTRY(dvd_reset), + MODULE_ENTRY(dvd_error_string), + MODULE_ENTRY(dvd_do_command), + MODULE_ENTRY(dvd_stop), + MODULE_ENTRY(dvd_getnumtitles), + MODULE_ENTRY(dvd_play), + MODULE_ENTRY(dvd_setdeflang_spu), + MODULE_ENTRY(dvd_setdeflang_audio), + MODULE_ENTRY(dvd_setdeflang_menu), + MODULE_ENTRY(dvd_button_play), + MODULE_ENTRY(dvd_getnumchapters), + MODULE_ENTRY(dvd_get_cur), + MODULE_ENTRY(dvd_seek_titlepart), + MODULE_ENTRY(dvd_seek), + MODULE_ENTRY(dvd_getdebug), + MODULE_ENTRY(dvd_ismenu), + MODULE_ENTRY(dvd_getangle), + MODULE_ENTRY(dvd_getspeed), + MODULE_ENTRY(dvd_get_saved), + MODULE_ENTRY(dvd_setdebug), + MODULE_ENTRY(dvd_get_total_time), + MODULE_ENTRY(dvd_get_chapter_for_time), + MODULE_ENTRY(dvd_button_subtitle), + MODULE_ENTRY(dvd_button_audio), + MODULE_ENTRY(dvd_button_menu), + MODULE_ENTRY(dvd_button_angle), + MODULE_ENTRY(dvd_setspeed), + MODULE_ENTRY(dvd_player_loop), + + MODULE_ENTRY_END(), +}; + +MODULE_DESC module_dvd_desc = +{ + "dvd", // name + "dvd", // class + "DVD Player module", // desc. + 100, // version = 1.00 + 140, // min. init ver = 1.40 + + { 0 }, +}; + +// end of extern "C" +} diff --git a/src/module-init.cpp b/src/module-init.cpp new file mode 100644 index 0000000..9b01294 --- /dev/null +++ b/src/module-init.cpp @@ -0,0 +1,377 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - Main program module interface impl. + * \file module-init.cpp + * \author bombur + * \version 0.1 + * \date 10.12.2008 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +// For main program, use "if NOT defined"! +#ifndef COMPILE_MODULE + #define MODULE_SERVER +#endif + +#ifdef MODULE_SERVER + #define __USE_LARGEFILE64 + #define __LARGE64_FILES + #define _LARGEFILE64_SOURCE + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + + #include + #include + #include + #include + #include + #include + #include + + #include + #include + #include + +extern "C" { extern int __sigjmp_save (sigjmp_buf env, int savemask); }; +#endif + +#include +#include + + +extern "C" { + +MODULE_DECL_START() + + MODULE_DECL(script_error_callback), + MODULE_DECL(script_player_saved_callback), + MODULE_DECL(script_video_info_callback), + MODULE_DECL(script_zoom_scroll_reset_callback), + MODULE_DECL(script_speed_callback), + MODULE_DECL(script_time_callback), + MODULE_DECL(script_dvd_chapter_callback), + MODULE_DECL(script_dvd_title_callback), + MODULE_DECL(script_dvd_menu_callback), + MODULE_DECL(script_totaltime_callback), + MODULE_DECL(script_audio_lang_callback), + MODULE_DECL(script_audio_stream_callback), + MODULE_DECL(script_audio_info_callback), + MODULE_DECL(script_spu_lang_callback), + MODULE_DECL(script_spu_stream_callback), + MODULE_DECL(script_framerate_callback), + MODULE_DECL(script_framesize_callback), + MODULE_DECL(script_update_variable), + + MODULE_DECL(mpeg_init), + MODULE_DECL(mpeg_deinit), + MODULE_DECL(mpeg_getaudiostream), + MODULE_DECL(mpeg_getspustream), + MODULE_DECL(mpeg_zoom_hor), + MODULE_DECL(mpeg_zoom_ver), + MODULE_DECL(mpeg_scroll), + MODULE_DECL(mpeg_getbufbase), + MODULE_DECL(mpeg_setaudioparams), + MODULE_DECL(mpeg_getpacketlength), + MODULE_DECL(mpeg_setbuffer), + MODULE_DECL(mpeg_getframesize), + MODULE_DECL(mpeg_setspeed), + MODULE_DECL(mpeg_zoom_reset), + MODULE_DECL(mpeg_is_displayed), + MODULE_DECL(mpeg_parse_ac3_header), + MODULE_DECL(mpeg_getaudioparams), + MODULE_DECL(mpeg_wait), + MODULE_DECL(mpeg_getrate), + MODULE_DECL(mpeg_is_playing), + MODULE_DECL(mpeg_get_video_format), + MODULE_DECL(mpeg_setpts), + MODULE_DECL(mpeg_resetstreams), + MODULE_DECL(mpeg_setaudiostream), + MODULE_DECL(mpeg_start), + MODULE_DECL(mpeg_play), + MODULE_DECL(mpeg_feed), + MODULE_DECL(mpeg_setbufidx), + MODULE_DECL(mpeg_setspustream), + MODULE_DECL(mpeg_get_fps), + MODULE_DECL(mpeg_audio_format_changed), + MODULE_DECL(mpeg_extractpacket), + MODULE_DECL(mpeg_feed_getlast), + MODULE_DECL(mpeg_parse_program_stream_pack_header), + MODULE_DECL(mpeg_findpacket), + + MODULE_DECL(khwl_display_clear), + MODULE_DECL(khwl_stop), + MODULE_DECL(khwl_setvideomode), + MODULE_DECL(khwl_setproperty), + MODULE_DECL(khwl_getproperty), + MODULE_DECL(khwl_transformcoord), + MODULE_DECL(khwl_set_window_zoom), + + MODULE_DECL(fip_write_string), + MODULE_DECL(fip_write_special), + MODULE_DECL(fip_clear), + + MODULE_DECL(msg_error), + MODULE_DECL(msg), + + MODULE_DECL(settings_get), + MODULE_DECL(settings_set), + + MODULE_DECL(media_open), + MODULE_DECL(media_close), + MODULE_DECL(media_free_block), + MODULE_DECL(media_geterror), + MODULE_DECL(media_get_next_block), + MODULE_DECL(media_skip_buffer), + + MODULE_DECL(usleep), + MODULE_DECL(gettimeofday), + MODULE_DECL(closedir), + MODULE_DECL(readdir), + MODULE_DECL(opendir), + +#ifndef WIN32 + MODULE_DECL(__errno_location), + MODULE_DECL(__sigjmp_save), + MODULE_DECL(atoi), + MODULE_DECL(calloc), + MODULE_DECL(chdir), + MODULE_DECL(clock), + MODULE_DECL(close), + MODULE_DECL(fclose), + MODULE_DECL(ferror), + MODULE_DECL(fgets), + MODULE_DECL(fopen64), + MODULE_DECL(fprintf), + MODULE_DECL(fread), + MODULE_DECL(free), + MODULE_DECL(fstat64), + MODULE_DECL(getcwd), + MODULE_DECL(getenv), + MODULE_DECL(getmntent), + MODULE_DECL(getpwuid), + MODULE_DECL(getuid), + MODULE_DECL(ioctl), + MODULE_DECL(localtime), + MODULE_DECL(lseek64), + MODULE_DECL(malloc), + MODULE_DECL(memset), + MODULE_DECL(open64), + MODULE_DECL(rand), + MODULE_DECL(read), + MODULE_DECL(readdir64), + MODULE_DECL(readv), + MODULE_DECL(realloc), + MODULE_DECL(snprintf), + MODULE_DECL(sprintf), + MODULE_DECL(srand), + MODULE_DECL(stat64), + MODULE_DECL(strcasecmp), + MODULE_DECL(strcat), + MODULE_DECL(strdup), + MODULE_DECL(strerror), + MODULE_DECL(strftime), + MODULE_DECL(strncasecmp), + MODULE_DECL(strncat), + MODULE_DECL(strncmp), + MODULE_DECL(strncpy), + MODULE_DECL(strtok), + MODULE_DECL(strtol), + MODULE_DECL(tolower), + MODULE_DECL(toupper), + MODULE_DECL(vsprintf), + + MODULE_DECL(fflush), + MODULE_DECL(sigemptyset), + MODULE_DECL(sigaddset), + MODULE_DECL(sigprocmask), + MODULE_DECL(sigfillset), + MODULE_DECL(sigaction), + MODULE_DECL(raise), + + MODULE_DECL(getpid), + MODULE_DECL(kill), +#endif + +MODULE_DECL_END(); + + +MODULE_FUNC_TABLE init_funcs[] = +{ + MODULE_ENTRY(script_error_callback), + MODULE_ENTRY(script_player_saved_callback), + MODULE_ENTRY(script_video_info_callback), + MODULE_ENTRY(script_zoom_scroll_reset_callback), + MODULE_ENTRY(script_speed_callback), + MODULE_ENTRY(script_time_callback), + MODULE_ENTRY(script_dvd_chapter_callback), + MODULE_ENTRY(script_dvd_title_callback), + MODULE_ENTRY(script_dvd_menu_callback), + MODULE_ENTRY(script_totaltime_callback), + MODULE_ENTRY(script_audio_lang_callback), + MODULE_ENTRY(script_audio_stream_callback), + MODULE_ENTRY(script_audio_info_callback), + MODULE_ENTRY(script_spu_lang_callback), + MODULE_ENTRY(script_spu_stream_callback), + MODULE_ENTRY(script_framerate_callback), + MODULE_ENTRY(script_framesize_callback), + MODULE_ENTRY(script_update_variable), + + MODULE_ENTRY(mpeg_init), + MODULE_ENTRY(mpeg_deinit), + MODULE_ENTRY(mpeg_getaudiostream), + MODULE_ENTRY(mpeg_getspustream), + MODULE_ENTRY(mpeg_zoom_hor), + MODULE_ENTRY(mpeg_zoom_ver), + MODULE_ENTRY(mpeg_scroll), + MODULE_ENTRY(mpeg_getbufbase), + MODULE_ENTRY(mpeg_setaudioparams), + MODULE_ENTRY(mpeg_getpacketlength), + MODULE_ENTRY(mpeg_setbuffer), + MODULE_ENTRY(mpeg_getframesize), + MODULE_ENTRY(mpeg_setspeed), + MODULE_ENTRY(mpeg_zoom_reset), + MODULE_ENTRY(mpeg_is_displayed), + MODULE_ENTRY(mpeg_parse_ac3_header), + MODULE_ENTRY(mpeg_getaudioparams), + MODULE_ENTRY(mpeg_wait), + MODULE_ENTRY(mpeg_getrate), + MODULE_ENTRY(mpeg_is_playing), + MODULE_ENTRY(mpeg_get_video_format), + MODULE_ENTRY(mpeg_setpts), + MODULE_ENTRY(mpeg_resetstreams), + MODULE_ENTRY(mpeg_setaudiostream), + MODULE_ENTRY(mpeg_start), + MODULE_ENTRY(mpeg_play), + MODULE_ENTRY(mpeg_feed), + MODULE_ENTRY(mpeg_setbufidx), + MODULE_ENTRY(mpeg_setspustream), + MODULE_ENTRY(mpeg_get_fps), + MODULE_ENTRY(mpeg_audio_format_changed), + MODULE_ENTRY(mpeg_extractpacket), + MODULE_ENTRY(mpeg_feed_getlast), + MODULE_ENTRY(mpeg_parse_program_stream_pack_header), + MODULE_ENTRY(mpeg_findpacket), + + MODULE_ENTRY(khwl_display_clear), + MODULE_ENTRY(khwl_stop), + MODULE_ENTRY(khwl_setvideomode), + MODULE_ENTRY(khwl_setproperty), + MODULE_ENTRY(khwl_getproperty), + MODULE_ENTRY(khwl_transformcoord), + MODULE_ENTRY(khwl_set_window_zoom), + + MODULE_ENTRY(fip_write_string), + MODULE_ENTRY(fip_write_special), + MODULE_ENTRY(fip_clear), + + MODULE_ENTRY(msg_error), + MODULE_ENTRY(msg), + + MODULE_ENTRY(settings_get), + MODULE_ENTRY(settings_set), + + MODULE_ENTRY(media_open), + MODULE_ENTRY(media_close), + MODULE_ENTRY(media_free_block), + MODULE_ENTRY(media_geterror), + MODULE_ENTRY(media_get_next_block), + MODULE_ENTRY(media_skip_buffer), + + MODULE_ENTRY(usleep), + MODULE_ENTRY(gettimeofday), + MODULE_ENTRY(closedir), + MODULE_ENTRY(readdir), + MODULE_ENTRY(opendir), + +#ifndef WIN32 + MODULE_ENTRY(__errno_location), + MODULE_ENTRY(__sigjmp_save), + MODULE_ENTRY(atoi), + MODULE_ENTRY(calloc), + MODULE_ENTRY(chdir), + MODULE_ENTRY(clock), + MODULE_ENTRY(close), + MODULE_ENTRY(fclose), + MODULE_ENTRY(ferror), + MODULE_ENTRY(fgets), + MODULE_ENTRY(fopen64), + MODULE_ENTRY(fprintf), + MODULE_ENTRY(fread), + MODULE_ENTRY(free), + MODULE_ENTRY(fstat64), + MODULE_ENTRY(getcwd), + MODULE_ENTRY(getenv), + MODULE_ENTRY(getmntent), + MODULE_ENTRY(getpwuid), + MODULE_ENTRY(getuid), + MODULE_ENTRY(ioctl), + MODULE_ENTRY(localtime), + MODULE_ENTRY(lseek64), + MODULE_ENTRY(malloc), + MODULE_ENTRY(memset), + MODULE_ENTRY(open64), + MODULE_ENTRY(rand), + MODULE_ENTRY(read), + MODULE_ENTRY(readdir64), + MODULE_ENTRY(readv), + MODULE_ENTRY(realloc), + MODULE_ENTRY(snprintf), + MODULE_ENTRY(sprintf), + MODULE_ENTRY(srand), + MODULE_ENTRY(stat64), + MODULE_ENTRY(strcasecmp), + MODULE_ENTRY(strcat), + MODULE_ENTRY(strdup), + MODULE_ENTRY(strerror), + MODULE_ENTRY(strftime), + MODULE_ENTRY(strncasecmp), + MODULE_ENTRY(strncat), + MODULE_ENTRY(strncmp), + MODULE_ENTRY(strncpy), + MODULE_ENTRY(strtok), + MODULE_ENTRY(strtol), + MODULE_ENTRY(tolower), + MODULE_ENTRY(toupper), + MODULE_ENTRY(vsprintf), + + MODULE_ENTRY(fflush), + MODULE_ENTRY(sigemptyset), + MODULE_ENTRY(sigaddset), + MODULE_ENTRY(sigprocmask), + MODULE_ENTRY(sigfillset), + MODULE_ENTRY(sigaction), + MODULE_ENTRY(raise), + + MODULE_ENTRY(getpid), + MODULE_ENTRY(kill), +#endif + + MODULE_ENTRY_END(), +}; + +} diff --git a/src/module.cpp b/src/module.cpp new file mode 100644 index 0000000..5031904 --- /dev/null +++ b/src/module.cpp @@ -0,0 +1,292 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - module system impl. + * \file module.cpp + * \author bombur + * \version 0.1 + * \date 10.12.2008 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#ifdef COMPILE_MODULE + +#include + +extern "C" { + +typedef int (*MODULE_INIT_PROC)(MODULE_DESC *desc, MODULE_FUNC_TABLE *client, MODULE_FUNC_TABLE *server); + +MODULE_INIT_PROC module_init; + +MODULE_INIT_PROC module_getproc(char *str) +{ + if (str == 0) + return 0; + + // str - decimal pointer + unsigned long proc = 0; + for (char *s = str; *s != '\0'; s++) + { + if (*s < '0' || *s > '9') + return 0; + proc *= 10; + proc += (*s - '0'); + } + + return (MODULE_INIT_PROC)proc; +} + +void module_start(char *interface_ptr_str, MODULE_DESC *desc, MODULE_FUNC_TABLE *client, MODULE_FUNC_TABLE *server) +{ + module_init = module_getproc(interface_ptr_str); + if (module_init != 0) + { + if (module_init(desc, client, server) < 0) + return; + } +} + +// end of extern "C" +}; + +#else // server side + +#include +#include +#include + +#include +#include +#include + +#include +#include + +extern "C" { + +const int MAX_NUM_MODULES = 32; +typedef struct MODULE_STRUCT +{ + volatile BOOL loaded; + SPString name; + DWORD hash; + MODULE_DESC *desc; + int pid; + MODULE_FUNC_TABLE *client_table; +} MODULE_STRUCT; + +MODULE_STRUCT modules[MAX_NUM_MODULES]; +int num_modules = 0; +static volatile bool was_module_error = false; + +///////////////////////////// +extern MODULE_FUNC_TABLE dvd_funcs[]; +extern MODULE_FUNC_TABLE init_funcs[]; +///////////////////////////// + +int module_find(const SPString & name) +{ + // find module + DWORD hash = name.Hash(); + int mod_idx = -1; + for (int i = 0; i < num_modules; i++) + { + if (modules[i].hash == hash) + { + if (modules[i].name.CompareNoCase(name) == 0) + { + mod_idx = i; + break; + } + } + } + return mod_idx; +} + +int module_copy_table(MODULE_FUNC_TABLE *from, MODULE_FUNC_TABLE *to) +{ + for (int i = 0; to[i].addr != NULL; i++) + { + int id = to[i].id; + bool found = false; + for (int j = 0; from[j].addr != NULL; j++) + { + if (from[j].id == id) + { + found = true; + module_copy_func(from[j].addr, to[i].addr); + break; + } + } + if (!found) + { + msg("Module: Cannot find function entry, ID=%d!\n", id); + return -1; + } + } + return 0; +} + +/// Called from module! +int module_init(MODULE_DESC *desc, MODULE_FUNC_TABLE *client, MODULE_FUNC_TABLE *server) +{ + was_module_error = false; + if (desc == NULL || client == NULL || server == NULL) + { + was_module_error = true; + return -1; + } + + msg("Module: Starting '%s'...\n", desc->name); + if (desc->init_ver > SP_VERSION) + { + msg("Module: Required version mismatch (%d.%d > %d.%d)!\n", desc->init_ver / 100, desc->init_ver % 100, + SP_VERSION / 100, SP_VERSION % 100); + was_module_error = true; + return -1; + } + int mod_idx = module_find(desc->name); + if (mod_idx < 0) + { + msg("Module: Unknown module loaded - '%s'!\n", desc->name); + was_module_error = true; + return -1; + } + modules[mod_idx].desc = desc; + + // copy dvd tables + MODULE_FUNC_TABLE *client_table = NULL; + + if (strcasecmp(desc->class_name, "dvd") == 0) + client_table = dvd_funcs; + if (client_table == NULL) + { + msg("Module: Cannot find client table for module '%s'.\n", desc->name); + was_module_error = true; + return -1; + } + if (module_copy_table(client, client_table) < 0) + { + was_module_error = true; + return -1; + } + if (module_copy_table(init_funcs, server) < 0) + { + was_module_error = true; + return -1; + } + + modules[mod_idx].client_table = client_table; + modules[mod_idx].loaded = TRUE; + + msg("Module: '%s' Started.\n", desc->name); + + return 0; +} + + +int module_load(const SPString & name) +{ + int mod_idx = module_find(name); + if (mod_idx < 0) // add new module + { + if (num_modules >= MAX_NUM_MODULES) + { + msg("Module: Max. modules number limit exceeded!\n"); + return -1; + } + mod_idx = num_modules++; + modules[mod_idx].name = name; + modules[mod_idx].hash = name.Hash(); + modules[mod_idx].loaded = FALSE; + modules[mod_idx].desc = NULL; + modules[mod_idx].client_table = NULL; + } else + { + if (modules[mod_idx].loaded) // it's already loaded! + return 0; + } + + char fname[128], arg[32]; + sprintf(fname, "/modules/%s.bin", (char *)name); + sprintf(arg, "%lu", (DWORD)module_init); + + msg("Module: Loading '%s'...\n", (char *)name); + + was_module_error = false; + int pid = module_binary_load(fname, arg); + if (pid <= 0) + { + msg("Module: Cannot load module '%s'!\n", (char *)name); + return -1; + } + + modules[mod_idx].pid = pid; + + // now we'll wait for process to settle: + for (int tries = 0; tries < 1000; tries++) + { + if (modules[mod_idx].loaded) + break; + if (was_module_error) + { + msg("Module: Error was detected during module_init()!\n"); + break; + } + usleep(100); + } + if (!modules[mod_idx].loaded) + { + msg("Module: Cannot init module '%s'.\n", (char *)name); + module_unload(name); + return -1; + } + + msg("Module: '%s' Loaded!\n", (char *)name); + return 0; +} + +int module_unload(const SPString & name) +{ + int mod_idx = module_find(name); + if (mod_idx < 0) + return -1; + if (!modules[mod_idx].loaded) + { + msg("Module: Unload - Module '%s' is not loaded!\n", (char *)name); + return -1; + } + if (module_binary_unload(modules[mod_idx].pid) >= 0) + { + if (modules[mod_idx].client_table != NULL) + { + MODULE_FUNC_TABLE *tbl = modules[mod_idx].client_table; + for (int i = 0; tbl[i].addr != NULL; i++) + { + module_clear_func(modules[mod_idx].client_table); + } + } + modules[mod_idx].pid = -1; + modules[mod_idx].loaded = FALSE; + } + return 0; +} + +// end of extern "C" +}; + +#endif diff --git a/src/module.h b/src/module.h new file mode 100644 index 0000000..0f66ad4 --- /dev/null +++ b/src/module.h @@ -0,0 +1,76 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - module system header file + * \file module.h + * \author bombur + * \version 0.1 + * \date 10.12.2008 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#ifndef SP_MODULE_H +#define SP_MODULE_H + +typedef struct MODULE_FUNC_TABLE +{ + int id; + void *addr; +} MODULE_FUNC_TABLE; + +#define MODULE_DECL_START() enum { +#define MODULE_DECL_END() }; + +#ifdef MODULE_SERVER +#define MODULE_DECL(f) modid_##f +#else +#define MODULE_DECL(f) modid_##f }; MODULE_FUNC(f); enum { modid_##f##_continue = modid_##f + +#endif + +#define MODULE_ENTRY(f) { modid_##f, ((void *)&f) } +#define MODULE_ENTRY_END() { -1, 0 } + +typedef struct MODULE_DESC +{ + const char *name; /// module name + const char *class_name; /// module class name + const char *desc; /// module description + int ver; /// module version (100 = 1.00) + int init_ver; /// minimal init (module sys.) version (100 = 1.00) + + int reserved[16]; + +} MODULE_DESC; + +#ifdef COMPILE_MODULE +extern "C" +{ +void module_start(char *interface_ptr_str, MODULE_DESC *desc, MODULE_FUNC_TABLE *client, MODULE_FUNC_TABLE *server); +} +#else + +#include +extern "C" +{ +int module_load(const SPString & name); +int module_unload(const SPString & name); +} +#endif + + + +#endif // of SP_MODULE_H diff --git a/src/mpg.cpp b/src/mpg.cpp new file mode 100644 index 0000000..be8973d --- /dev/null +++ b/src/mpg.cpp @@ -0,0 +1,739 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - MPEG1/MPEG2/VOB files player source file. + * \file mpg.cpp + * \author bombur + * \version 0.2 + * \date 02.07.2010 07.05.2007 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "script.h" +#include "player.h" +#include "media.h" +#include "settings.h" + +#ifdef INTERNAL_VIDEO_PLAYER + +#define VIDEO_INTERNAL + +#include "audio.h" +#include "video.h" +#include "mpg.h" + +#define MSG if (video_msg) msg + +static const int seek_delta_pts = 60 * 90000; // 1 min. + +VideoMpg::VideoMpg() +{ + type = VIDEO_CONTAINER_MPEG; + new_frame_size = true; + cur_rate = 0; + + scan_for_length = true; + seek_start = true; + start_time_pts = -1; + end_time_pts = -1; + totaltime_pts = 0; + pts_base = 0; + wait_for_new_pts = false; + set_pts = true; + + elementary_stream = false; + is_cdxa = false; + + packet_left = 0; + saved_sct = START_CODE_UNKNOWN; + saved_length = -1; + saved_start_counter = 0; + saved_feed = true; + + //tmp_packet_header_buf_active = false; + //tmp_packet_header_buf_size = 0; + packet_header_incomplete = false; + + was_pack = false; + + mpeg_format = -1; + mpeg_aspect = -1; + + old_pts = 0; + video_pts = 0; + + is_totaltime_from_rate = false; + +} + +VideoMpg::~VideoMpg() +{ +} + +//////////////////////////////////////////////// + +BOOL VideoMpg::Parse() +{ + // Read first 4 bytes and check that this is an MPEG file + BYTE data[16]; + if (video_read(data, 12) != 12) + { + msg_error("Mpg: Read error.\n"); + return FALSE; + } + if (strncasecmp((char *)data, "RIFF", 4) == 0 && strncasecmp((char *)data+8, "AVI ", 4) == 0) + { + msg_error("Mpg: Avi file found in MPEG container.\n"); + return FALSE; + } + if (strncasecmp((char *)data, "RIFF", 4) == 0 && strncasecmp((char *)data+8, "CDXA", 4) == 0) + { + msg("Mpg: CDXA format detected.\n"); + is_cdxa = true; + } + video_lseek(0, SEEK_END); + filesize = video_lseek(0L, SEEK_CUR); + seek_filesize = filesize; + +//msg("! seek_filesize = %d\n", (int)seek_filesize); + + video_lseek(0, SEEK_SET); + + video_fmt = RIFF_VIDEO_MPEG12; + video_fmt_str.Printf("MPEG-1/2"); + + //MPEG_PACKET_LENGTH = 2048; + MPEG_PACKET_LENGTH = 32768; + MPEG_NUM_PACKETS = 512; + set_media_type(MEDIA_TYPE_MPEG); + + return TRUE; +} + +BOOL VideoMpg::GetTimeLength() +{ + LONGLONG pos = video_lseek(-32768, SEEK_END); + int ret = mpeg_find_free_blocks(MPEG_BUFFER_1); + // we cannot wait + if (ret < 1) + return FALSE; + BYTE *base = mpeg_getcurbuf(MPEG_BUFFER_1); + if (base == NULL) + return FALSE; + int len = (int)video_read(base, (int)(filesize - pos)); + if (len < 1) + return FALSE; + + // now parse the stream + MPEG_PACKET_LENGTH = len; + start_time_pts = -1; + end_time_pts = -1; + totaltime_pts = 0; + seek_start = false; + scan_for_length = true; + fps = 30; // not correct, but it works + ProcessChunk(base, len); + MPEG_PACKET_LENGTH = 32768; + + video_lseek(0, SEEK_SET); + seek_start = true; + scan_for_length = true; + + return TRUE; +} + +////////////////////////////////////////////////////////////////// + +VIDEO_CHUNK_TYPE VideoMpg::GetNext(BYTE *, int , int *, int *, int *) +{ + return VIDEO_CHUNK_UNKNOWN; +} + +int VideoMpg::ProcessChunk(BYTE *buf, int ) +{ + START_CODE_TYPES sct; + BYTE *base = buf, *lastbuf = base; + + for (; ;) + { + if (packet_left > 0 || (elementary_stream && saved_sct != START_CODE_UNKNOWN)) + sct = saved_sct; + else + { + sct = mpeg_findpacket(buf, base, saved_start_counter); + if (sct == START_CODE_END) + { + return 1; + } + saved_start_counter = 0; + } + bool datapacket = false; + switch (sct) + { + case START_CODE_PACK: + { + int left = MPEG_PACKET_LENGTH - (buf - base); + if (packet_left > 0) + { + memcpy(tmp_packet_header_buf + packet_left, buf, 8 - packet_left); + BYTE *b = tmp_packet_header_buf; + scr = mpeg_parse_program_stream_pack_header(b, tmp_packet_header_buf); + buf += 8; + packet_left = 0; + } + else if (left < 8) + { + memcpy(tmp_packet_header_buf, buf, left); + packet_left = left; + saved_sct = sct; + return 1; + } + else + scr = mpeg_parse_program_stream_pack_header(buf, base); + scr += pts_base; + if (scr < 0) + scr = 0; + if (scr > 0 && scan_for_length) + { + //scan_for_length = false; + if (seek_start) + { + if (start_time_pts < 0) + { + start_time_pts = scr; + UpdateTotalTime(); + } + } + else + { + if (scr > end_time_pts && (end_time_pts < 0 || scr < end_time_pts + 120000)) + end_time_pts = scr; + // early exit + //return 1; + } + } + // this is needed for 'khwl' + scr |= SPTM_SCR_FLAG; + was_pack = true; + lastbuf = buf; + } + break; + case START_CODE_SYSTEM: + case START_CODE_PCI: + case START_CODE_PADDING: + { + int len; + int left = MPEG_PACKET_LENGTH - (buf - base); + if (left < 1) + return 1; + if (packet_left > 0) + { + // if we read only 1 byte of packet length + if (saved_length >= 0) + { + // one byte was already read + len = ((saved_length << 8) | buf[0]) + 1; + } else + len = packet_left; + packet_left = 0; + saved_length = -1; + } + else + { + if (left < 2) // cannot get length + { + saved_length = buf[0]; + packet_left = 1; + saved_sct = sct; + return 1; + } + len = ((buf[0] << 8) | buf[1]) + 2; + } + + if (len > left) // length is greater than packet size + { + packet_left = len - left; + saved_length = -1; + len = left; + saved_sct = sct; + return 1; + } + // it's safe now + buf += len; + lastbuf = buf; + break; + } + case START_CODE_MPEG_VIDEO_ELEMENTARY: + elementary_stream = true; + saved_sct = sct; + case START_CODE_MPEG_VIDEO: + case START_CODE_MPEG_AUDIO1: + case START_CODE_MPEG_AUDIO2: + case START_CODE_PRIVATE1: + datapacket = true; + break; + default:; + } + + if (datapacket) + { + if (scan_for_length) + { + LONGLONG pts = 0; + if (!seek_start) + { + if (sct == START_CODE_MPEG_VIDEO && was_pack) + { + MpegPacket tmppacket; + tmppacket.flags = 0; + tmppacket.pts = 0; + if (mpeg_extractpacket(buf, base, &tmppacket, sct, FALSE) >= 0) + { + if (tmppacket.flags == 2 && tmppacket.pts > 0) + pts = tmppacket.pts; + } + buf += tmppacket.size; + lastbuf = buf; + } + } +#if 0 + if (sct == START_CODE_MPEG_VIDEO || sct == START_CODE_MPEG_VIDEO_ELEMENTARY) + { + BYTE *last = base + MIN(buflen, MPEG_PACKET_LENGTH); + for(BYTE *b = buf; b < last; b++) + { + if (b[0] == 0 && b[1] == 0 && b[2] == 1 && b[3] == 0xb8) // GOP + { + // xhhhhhmm mmmmxsss sssfffff fxxxxxxx + int hours = (b[4] >> 2) & 63; + int mins = ((b[4] & 3) << 4) | (b[5] >> 4); + int secs = ((b[5] & 7) << 3) | (b[6] >> 5); + int frames = ((b[6] << 1) & 63) | (b[7] >> 7); + pts = ((LONGLONG)(hours * 3600) + (LONGLONG)(mins * 60) + (LONGLONG)secs) * + INT64(90000) + (LONGLONG)frames * INT64(90000) / fps; + break; + } + } + // we don't need packets if we scan at the end of file + if (pts == 0 && !seek_start) + return 1; + } +#endif + if (seek_start) + { + if (pts > 0) + { + scan_for_length = false; + start_time_pts = pts; + UpdateTotalTime(); + } + } + else + { + if (pts > 0 && pts > end_time_pts) + { + end_time_pts = pts; + scan_for_length = false; + return 1; + } + if (elementary_stream) + return 1; + continue; + } + } + + // audio or video + MpegPacket *packet = NULL; + packet = mpeg_feed_getlast(); + if (packet == NULL) // well, it won't really help + return 0; + memset((BYTE *)packet + 4, 0, sizeof(MpegPacket) - 4); + packet->pts = pts_base; + + //!!!!!!!!!!!!!!!!!!! + packet->scr = scr; + + bool feed = true; + + if (packet_header_incomplete) // we haven't read the header last time + { + packet_left = 0; + packet_header_incomplete = false; + } + + if (packet_left > 0) + { + packet->type = saved_packet->type; + packet->pData = buf; + packet->size = packet_left; + packet->pts = saved_packet->pts; + packet->nframeheaders = saved_packet->nframeheaders; + packet->firstaccessunitpointer = saved_packet->firstaccessunitpointer; + packet->scr = 0; + feed = saved_feed; + packet_left = 0; + } else + { + BYTE *lb = buf; + int ret = mpeg_extractpacket(buf, base, packet, sct, wait_for_new_pts); + if (ret < 0) // if not enough data in the packet + { + LONGLONG off = -(base + MPEG_PACKET_LENGTH - lb); + video_lseek(off, SEEK_CUR); + saved_sct = sct; + packet_left = 1; // the value doesn't really matter + packet_header_incomplete = true; + return 1; + } + if (ret == 0) + feed = false; + } + + int left = MPEG_PACKET_LENGTH - (buf - base); + bool need_return = false; + if ((int)packet->size > left) // length is greater than packet size + { + packet_left = packet->size - left; + packet->size = left; + saved_sct = sct; + saved_packet = packet; + saved_feed = feed; + need_return = true; + } + + if (new_frame_size) + { + int w, h; + mpeg_getframesize(&w, &h); + if (w != 0 && h != 0) + { + script_framesize_callback(w, h); + new_frame_size = false; + } + + int aspect = mpeg_getaspect(); + if (mpeg_aspect != aspect) + { + mpeg_aspect = aspect; + + int tvtype = settings_get(SETTING_TVTYPE); + KHWL_VIDEOMODE vmode = KHWL_VIDEOMODE_NORMAL; + if (aspect == 2) // 4:3 + { + if (tvtype == 0 || tvtype == 1) + vmode = KHWL_VIDEOMODE_NORMAL; + else + vmode = (tvtype == 2) ? KHWL_VIDEOMODE_HCENTER : KHWL_VIDEOMODE_VCENTER; + } + else if (aspect == 3) // 16:9 + { + if (tvtype == 2 || tvtype == 3) + vmode = KHWL_VIDEOMODE_WIDE; + else + vmode = (tvtype == 0) ? KHWL_VIDEOMODE_LETTERBOX : KHWL_VIDEOMODE_PANSCAN; + } + msg("MPEG: set vmode = %d (%d)\n", vmode, aspect); + + khwl_display_clear(); + khwl_set_window_zoom(KHWL_ZOOMMODE_DVD); + khwl_setvideomode(vmode, TRUE); + } + + int cur_fps = mpeg_get_fps(); + if (cur_fps > 0) + { + fps = cur_fps; + script_framerate_callback(fps); + } + + int cur_fmt = mpeg_get_video_format(); + if (cur_fmt != mpeg_format) + { + if (!elementary_stream) + { + video_fmt_str.Empty(); + video_fmt_str.Printf("MPEG-%d", cur_fmt == MPEG_1 ? 1 : 2); + script_video_info_callback(video_fmt_str); + MSG("Video: * Video format %s detected!\n", *video_fmt_str); + mpeg_format = cur_fmt; + } + } + } + + if (packet->flags == 2 && feed) + { + if (wait_for_new_pts) + { + if (packet->type == 0) + { + if (mpeg_needs_seq_start_header(&packet)) + wait_for_new_pts = false; + else + feed = false; + } + else + feed = false; + } + if (packet->type == 0) + { + pts_base += mpeg_detect_and_fix_pts_wrap(packet); + if (set_pts) + { + mpeg_setpts(packet->pts); + set_pts = false; + } + video_pts = packet->pts; + } else + { + int cur_numaudio_streams = mpeg_getaudiostreamsnum(); + if (cur_numaudio_streams != num_tracks) + { + num_tracks = cur_numaudio_streams; + } + } + + if (displ_pts_base == 0) + { + if (packet->type == 0) + { + displ_pts_base = -MAX(packet->pts - 45000, 0); + /* + pts_base = -MAX(packet->pts - 45000, 0); + packet->pts += pts_base; + scr = packet->scr = ((packet->scr & (~SPTM_SCR_FLAG)) + pts_base) | SPTM_SCR_FLAG; + */ + } + } + int rate = mpeg_getrate(elementary_stream); + if (rate != cur_rate) + { + // calc. total time + if (rate > 0) + { + UpdateTotalTime(); + cur_rate = rate; + } + } + old_pts = packet->pts; + } else + { + if (wait_for_new_pts) + feed = false; + packet->pts = old_pts; + } +#if 0 +{ +static int num_packets = 0; + +static LONGLONG last_pts = 0; +if (feed) +{ +if (packet->type == 1) +msg("[%d] a - %d ---%d: %8d\t\t%d\t\t[%d]\t%d\n", num_packets, packet->size, packet->flags, (int)packet->pts, (int)(packet->scr & 0xfffffff), (int)(packet->pts - last_pts), pts_base); +else if (packet->type == 0) +msg("[%d] v - %d ---%d: %8d\t\t%d\t\t[%d]\t%d\n", num_packets, packet->size, packet->flags, (int)packet->pts, (int)(packet->scr & 0xfffffff), (int)(packet->pts - last_pts), pts_base); +last_pts = packet->pts; +} +/* +extern void player_printpacket(MpegPacket *packet); +player_printpacket(packet); +*/ +num_packets++; +} +//!!!!!!!!!!!!!!!!!!!!! +/* +if (msg_get_output() == MSG_OUTPUT_SHOW && packet->type != 0) +feed = false; +if (msg_get_output() == MSG_OUTPUT_FREEZE && packet->type != 0 && packet->type != 1) +feed = false; +*/ +//!!!!!!!!!!!!!!!!!!!!! +#endif + + //packet->vobu_sptm = (vobu_sptm + pts_base) | SPTM_SCR_FLAG; + // increase bufidx + if (packet->size >= 1 && feed) + { + mpeg_setbufidx(MPEG_BUFFER_1, packet); + mpeg_feed((MPEG_FEED_TYPE)packet->type); + //num_packets++; + } + buf += packet->size; + lastbuf = buf; + + if (need_return) + return 1; + } + } + return 1; +} + +void VideoMpg::UpdateTotalTime() +{ + LONGLONG new_totaltime = 0; + if (start_time_pts >= 0 && end_time_pts >= 0 && end_time_pts > start_time_pts) + { + new_totaltime = (end_time_pts - start_time_pts); + seek_filesize = filesize; + is_totaltime_from_rate = false; +//msg("! new_tt = %d (s=%d e=%d)\n", (int)new_totaltime, (int)start_time_pts, (int)end_time_pts); + } + else + { + LONGLONG rate = (LONGLONG)mpeg_getrate(elementary_stream); + if (rate > 0) + { + new_totaltime = INT64(90000) * filesize / rate; + seek_filesize = filesize; + is_totaltime_from_rate = true; +//msg("! new_tt = %d (r=%d fs=%d)\n", (int)new_totaltime, (int)rate, (int)filesize); + } + } + // if we're unable to deremine total time + if (video_pts + displ_pts_base > new_totaltime) + { + new_totaltime = video_pts + displ_pts_base; + seek_filesize = media_get_filepos(); + is_totaltime_from_rate = false; +//msg("! video_pts (%d) > new_totaltime (%d)\n", (int)video_pts, (int)new_totaltime); +//msg("! seek_filesize = %d\n", (int)seek_filesize); + } + if (new_totaltime > 0) + { + if (new_totaltime > totaltime_pts || (is_totaltime_from_rate && Abs(new_totaltime - totaltime_pts) > 90000)) + { + script_totaltime_callback((int)(new_totaltime / 90000)); + totaltime_pts = new_totaltime; + } + } +} + +int VideoMpg::GetNextIndexes() +{ + return -1; +} + +int VideoMpg::GetNextKeyFrame() +{ + return -1; +} + +int VideoMpg::GetKeyFrame(LONGLONG time) +{ + // undo some Video actions for MPEGs: + skip_fwd = false; + skip_rev = false; + skip_cnt = 0; + searching = false; + packet_left = 0; + + packet_header_incomplete = false; + + old_pts = 0; + wait_for_new_pts = true; + set_pts = true; + was_pack = false; + + saved_sct = START_CODE_UNKNOWN; + saved_length = -1; + saved_start_counter = 0; + saved_feed = true; + + mpeg_stop(); + mpeg_setspeed(MPEG_SPEED_NORMAL); + media_skip_buffer(NULL); + mpeg_setpts(0); + mpeg_start(); + + if (totaltime_pts < 1 || filesize < 1) + { + msg("MPEG: Cannot seek! Total time or size unknown.\n"); + return -1; + } + + LONGLONG curtime = saved_pts; + LONGLONG vpts = video_pts + displ_pts_base; + if (curtime < 0) + curtime = 0; + if (curtime > vpts) + curtime = vpts; + if (curtime < vpts - 10*90000) + curtime = vpts; + LONGLONG pts = 0; + + if (time == VIDEO_KEY_FRAME_NEXT) + { + pts = curtime + seek_delta_pts; + } + else if (time == VIDEO_KEY_FRAME_PREV) + { + pts = curtime - seek_delta_pts; + } + else + { + pts = time * 90000; + } + + LONGLONG filepos = (seek_filesize * pts / totaltime_pts) & (~(/*MPEG_PACKET_LENGTH*/512 - 1)); + if (filepos < 0) + filepos = 0; + // a special correction fix for VBR MPEGs + if (time == VIDEO_KEY_FRAME_NEXT || time == VIDEO_KEY_FRAME_PREV) + { + LONGLONG rate = (LONGLONG)mpeg_getrate(elementary_stream); + if (rate > 0) + { + LONGLONG avg_rate = INT64(90000) * seek_filesize / totaltime_pts; + LONGLONG thr = 100 * Abs(rate - avg_rate) / avg_rate; + if (thr < 10) + { + LONGLONG delta_pos = filepos - media_get_filepos(); + LONGLONG delta_time = INT64(90000) * delta_pos / rate; + LONGLONG d = Abs(delta_time) - seek_delta_pts; + d = seek_delta_pts * rate / INT64(90000); + if (time == VIDEO_KEY_FRAME_PREV) + d = -d; + filepos = media_get_filepos() + d; + msg("MPEG: * Bitrate-correcting (" PRINTF_64d ", r=" PRINTF_64d ")!\n", d, rate); + } + } + } + msg("(saved=%d fsize=%d)\n", (int)saved_pts, (int)seek_filesize); + msg("MPEG: Seek to " PRINTF_64d " (filepos=" PRINTF_64d "/" PRINTF_64d ")\n", pts, filepos, filesize); + LONGLONG ret = video_lseek(filepos, SEEK_SET); + if (ret != filepos) + msg("MPEG: Seek ERROR! Pos = " PRINTF_64d "\n", ret); + if (ret < 0) + { + msg("MPEG: Cannot seek! File error %d.\n", -errno); + return -1; + } + return 1; +} + +#endif diff --git a/src/mpg.h b/src/mpg.h new file mode 100644 index 0000000..577c43d --- /dev/null +++ b/src/mpg.h @@ -0,0 +1,90 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - MPEG1/MPEG2/VOB files player header file + * \file mpg.h + * \author bombur + * \version 0.1 + * \date 07.05.2007 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#ifndef SP_MPG_H +#define SP_MPG_H + +#include "video.h" + +#ifdef VIDEO_INTERNAL + +/// MPEG container player class +class VideoMpg : public Video +{ +public: + /// ctor + VideoMpg(); + + /// dtor + virtual ~VideoMpg(); + +public: + virtual BOOL Parse(); + virtual VIDEO_CHUNK_TYPE GetNext(BYTE *buf, int buflen, int *pos, int *left, int *len); + /// Returns 0 if found, -1 if failed, 1 for EOF. + virtual int GetNextIndexes(); + /// Find next key-frame in raw mode + virtual int GetNextKeyFrame(); + + virtual int GetKeyFrame(LONGLONG time); + virtual void UpdateTotalTime(); + + /// Called from video.cpp + int ProcessChunk(BYTE *buf, int buflen); + + BOOL GetTimeLength(); + +private: + int mpeg_format, mpeg_aspect; + bool new_frame_size; + bool elementary_stream, is_cdxa; + bool wait_for_new_pts, set_pts; + bool is_totaltime_from_rate; + int cur_rate; + LONGLONG filesize, seek_filesize; + LONGLONG video_pts; + + bool scan_for_length, seek_start, was_pack; + LONGLONG start_time_pts, end_time_pts; + LONGLONG totaltime_pts, pts_base; + int fps; + + int packet_left; + START_CODE_TYPES saved_sct; + int saved_length; + int saved_start_counter; + bool saved_feed; + MpegPacket *saved_packet; + + BYTE tmp_packet_header_buf[128]; + bool packet_header_incomplete; + + LONGLONG old_pts; +}; + +#endif + +/////////////////////////////////////////////////////////////// + +#endif // of SP_MPG_H diff --git a/src/player.cpp b/src/player.cpp new file mode 100644 index 0000000..d01579a --- /dev/null +++ b/src/player.cpp @@ -0,0 +1,931 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - File player source file. + * \file player.cpp + * \author bombur + * \version 0.1 + * \date 22.10.2006 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "script.h" +#include "player.h" + +#ifdef WIN32 +#include "info/player_info.h" +#endif + +#ifdef EXTERNAL_PLAYER +#define MSG if (player_msg) msg +static BOOL player_msg = TRUE; +#endif + +#ifdef FILEPLAYER_DEBUG + +//#define FILEPLAYER_DEBUG +//#define DEBUG_MSG msg +#define DEBUG_MSG printf + + +static int player_debug_num_packets = 0; +//extern "C" +//{ +void player_printpacket(MpegPacket *packet) +{ + DEBUG_MSG("[%d] t=%d fl=%ld\n", player_debug_num_packets++, packet->type, packet->flags); + DEBUG_MSG("SIZE=%ld\n", packet->size); + if (packet->pData != NULL) + { + int s = (packet->size + 7) / 8; + for (int i = 0; i < s; i += 8) + { + DEBUG_MSG("DATA: %02x %02x %02x %02x %02x %02x %02x %02x\n", + packet->pData[i+0], packet->pData[i+1], packet->pData[i+2], packet->pData[i+3], + packet->pData[i+4], packet->pData[i+5], packet->pData[i+6], packet->pData[i+7]); + } + } + DEBUG_MSG("pts=%u+%u dts=%u scr=%u+%u vobu=%u+%u\n", + (unsigned)(packet->pts >> 32), (unsigned)packet->pts, + (unsigned)packet->dts, + (unsigned)(packet->scr >> 32), (unsigned)packet->scr, + (unsigned)(packet->vobu_sptm >> 32), (unsigned)packet->vobu_sptm); + DEBUG_MSG("ei=%d nfh=%d faup=%d at=%d r=%d,%d,%d\n", + (int)packet->encryptedinfo, (int)packet->nframeheaders, (int)packet->firstaccessunitpointer, + (int)packet->AudioType, + (int)packet->reserved[0], (int)packet->reserved[1], (int)packet->reserved[2]); + DEBUG_MSG("\n\n"); + fflush(stdout); +} + +/* +void player_feed(MpegPlayStruct *feed) +{ + MpegPlayStruct *from = MPEG_PLAY_STRUCT; + if (from->in != NULL) + player_printpacket(from->in); + mpeg_feed(feed); +} +//} +int player_nextfeedpacket(MpegPlayStruct *feed) +{ + MpegPlayStruct *from = MPEG_PLAY_STRUCT; + MpegPacket *feedin = feed->in; + if (feedin == NULL) + return FALSE; + + player_printpacket(feedin); + + feed->in = feed->in->next; + + feed->num--; + feed->in_cnt++; + from->num++; + from->out_cnt++; + + if (feedin->next == NULL) + feed->out = NULL; + if (from->out == NULL) + from->in = feedin; + else + from->out->next = feedin; + from->out = feedin; + feedin->next = NULL; + + ////// simulate budidx decrement + if (feedin->bufidx) + { + if (*(feedin->bufidx) > 0) + (*feedin->bufidx)--; + } + + return TRUE; +} + +void player_debug_packets() +{ + for (;;) + { + //khwl_blockirq(TRUE); + while (MPEG_VIDEO_STRUCT->in != NULL) + { + player_nextfeedpacket(MPEG_VIDEO_STRUCT); + } + while (MPEG_AUDIO_STRUCT->in != NULL) + { + player_nextfeedpacket(MPEG_AUDIO_STRUCT); + } + while (MPEG_SPU_STRUCT->in != NULL) + { + player_nextfeedpacket(MPEG_SPU_STRUCT); + } + //khwl_blockirq(FALSE); + usleep(1000); + } +} +*/ +#endif + +/////////////////////////////////////////////////// + +class PlayerTerm +{ +public: + /// ctor + PlayerTerm() + { + master_handle = -1; + slave_handle = -1; + old_stdin = -1; + old_stdout = -1; + pts_id = -1; + } + + /// Create and open terminal + int Open(); + /// Close terminal + void Close(); + + /// Return true if terminal is opened + bool IsOpened(); + + /// Set terminal to current stdin/stdout + void Set(); + + /// Restore default stdin/stdout + void Restore(); + + /// Read data from the terminal (non-blocked). + /// Returns number of bytes read. + int Read(char *data, int max_data_size); + + /// Write zero-terminated string to the terminal. + void Write(const char *str); + +public: + int master_handle, slave_handle; + int old_stdin, old_stdout; + int pts_id; +}; + + +int PlayerTerm::Open() +{ + if (slave_handle >= 0) + Restore(); + if (master_handle >= 0) + Close(); + + char pts_str[10]; + pts_id = openpty (&master_handle, &slave_handle, pts_str, NULL, NULL); + if (pts_id == -1) + { + msg_error("Player: Cannot open pty.\n"); + return -1; + } + + struct termios tattr; + if (tcgetattr (master_handle, &tattr) == -1) + { + msg_error("Player: pty getattr failed.\n"); + return -1; + } + tattr.c_oflag &= ~ONLCR; + tattr.c_lflag &= ~ECHO; + tcsetattr(master_handle, TCSANOW, &tattr); + + int flgs = fcntl(master_handle, F_GETFL, 0); + fcntl(master_handle, F_SETFL, flgs | O_NONBLOCK); + + msg("Player: * pty #%d (%s) opened.\n", pts_id, pts_str); + + return 0; +} + +void PlayerTerm::Close() +{ + if (master_handle >= 0) + { + close(master_handle); + master_handle = -1; + + msg("Player: * pty #%d closed.\n", pts_id); + pts_id = -1; + } +} + +void PlayerTerm::Set() +{ + old_stdin = dup(STDIN_FILENO); + old_stdout = dup(STDOUT_FILENO); + close(STDIN_FILENO); + close(STDOUT_FILENO); + + dup2(slave_handle, STDIN_FILENO); + dup2(slave_handle, STDOUT_FILENO); +} + +void PlayerTerm::Restore() +{ + dup2(old_stdin, STDIN_FILENO); + dup2(old_stdout, STDOUT_FILENO); + close(old_stdin); + close(old_stdout); + close(slave_handle); + old_stdin = -1; + old_stdout = -1; + slave_handle = -1; +} + +bool PlayerTerm::IsOpened() +{ + return master_handle >= 0; +} + +void PlayerTerm::Write(const char *str) +{ + if (master_handle >= 0) + write(master_handle, str, strlen(str) + 1); +} + +int PlayerTerm::Read(char *data, int max_data_size) +{ + struct pollfd pfd; + pfd.fd = master_handle; + pfd.events = POLLIN; + pfd.revents = 0; + if (poll(&pfd, 1, 100) <= 0) + return 0; + if (pfd.revents & POLLIN) + { + return read(master_handle, data, max_data_size); + } + return 0; +} + +///////////////////////////////////////////////////// + +static PlayerTerm id3term; +static void player_parseid3(char *data, int data_size); +static void stopid3(); + +enum PLAYER_READID3 +{ + PLAYER_READID3_NONE = 0, + PLAYER_READID3_TITLE, + PLAYER_READID3_ARTIST, + PLAYER_READID3_WIDTH, + PLAYER_READID3_HEIGHT, + PLAYER_READID3_CLRS, +}; + +static PLAYER_READID3 player_reading_id3 = PLAYER_READID3_NONE; +const int id3_max_data_size = 256; +static char id3_data[id3_max_data_size + 1]; +static int id3id = -1; + +inline void to_enter(char* &data, int &data_size) +{ + char *end = strchr(data, '\n'); + if (end != NULL) + { + *end = '\0'; + data_size -= (end - data + 1); + data = end + 1; + } else + data_size = 0; +} + +// called when child player process ends +static void player_sig_handler(int sig, siginfo_t *info, void *) +{ + if (sig != SIGCHLD) + return; + msg("Player: * SIG handler (%d)!\n", info->si_pid); + int pstat; + waitpid(info->si_pid, &pstat, 0); +#ifdef EXTERNAL_PLAYER + if (info->si_pid == playerid) + stopfile(); +#endif + if (info->si_pid == id3id) + stopid3(); +} + +void set_sig(void (*siga) (int, siginfo_t *, void *)) +{ + struct sigaction sig; + memset(&sig, 0, sizeof(sig)); + if (siga != NULL) + sig.sa_flags = SA_SIGINFO; + sig.sa_sigaction = siga; + sigaction(SIGCHLD, &sig, NULL); +} + +///////////////////////////////////////////////////////////////////// + +#ifdef EXTERNAL_PLAYER + +static PlayerTerm pterm; +const int player_max_data_size = 1024; +static char player_data[player_max_data_size + 1]; +static char player_type[10]; +static char vmodebuf[10]; +static bool player_paused = false; + +enum PLAYER_READINFO +{ + PLAYER_READINFO_NONE = 0, + PLAYER_READINFO_TIME = 1, + PLAYER_READINFO_TITLE, + PLAYER_READINFO_ARTIST, + PLAYER_READINFO_VIDEOINFO, + PLAYER_READINFO_AUDIOINFO, +}; +static PLAYER_READINFO player_reading_info = PLAYER_READINFO_NONE; + +static int playerid = -1; +static BOOL playing = FALSE, video = FALSE, mpegplayer = FALSE; +static int mpegplayer_info_cnt = 0; + +static int playfile(const char *filename, const char *type, int vmode); +static BOOL player_command(const char *command); +static void stopfile(); +static void stopid3(); +static void player_parseinfo(char *data, int data_size); +static void set_sig(void (*siga) (int, siginfo_t *, void *)); + +int playfile(const char *filename, const char *type, int vmode) +{ + char *args1[] = { "/bin/fileplayer.bin", (char *)type, (char *)filename, (char *)vmodebuf, NULL }; + char *args2[] = { "/bin/mpegplayer.bin", (char *)filename, (char *)vmodebuf, NULL }; + char **args = (char **)args1; + video = FALSE; + mpegplayer = FALSE; + mpegplayer_info_cnt = 0; + sprintf(vmodebuf, "%d", vmode); + if (type != NULL) + { + if ((type[0] == 'M' && type[1] == 'P' && type[2] == 'G') || + (type[0] == 'M' && type[1] == 'P' && type[2] == 'E' && type[3] == 'G') || + (type[0] == 'V' && type[1] == 'O' && type[2] == 'B') || + (type[0] == 'D' && type[1] == 'A' && type[2] == 'T') + ) + { + args = (char **)args2; + video = TRUE; + mpegplayer = TRUE; + } + else + { + if ((type[0] == 'A' && type[1] == 'V' && type[2] == 'I')) + video = TRUE; + } + } + + if (playerid != -1) + player_stop(); + + pterm.Open(); + set_sig(player_sig_handler); + pterm.Set(); + playerid = exec_file(args[0], (const char **)args); + pterm.Restore(); + + MSG("Player: * Started (%d)...\n", playerid); + playing = TRUE; + + return 0; +} + +void stopfile() +{ + if (playerid < 0) + return; + + fip_clear(); + + // we'll have to wait for the next player_loop() + playerid = -1; + playing = FALSE; + video = FALSE; + mpegplayer = FALSE; + + MSG("Player: * Stopped.\n"); +} + +BOOL player_command(const char *command) +{ + if (playerid < 0 || command == NULL) + return FALSE; + MSG("Player: *COMMAND = %s\n", command); + pterm.Write(command); + return TRUE; +} + +/////////////////////////////////////////////////////////// + +int player_play(char *filepath) +{ +#ifdef FILEPLAYER_DEBUG + player_debug_num_packets = 0; + /* + DWORD *ptr = (DWORD *)0x167FFE0; + *(ptr) = (DWORD)&player_printpacket; + */ +#endif + + if (filepath == NULL) // resume playing + { + player_command("P\n"); + return 0; + } + + // start player + char *ext = strrchr(filepath, '.'); + if (ext != NULL) + { + ext = ext + 1; + strncpy(player_type, ext, 10); + strupr(player_type); + + int vmode = 0; + // TODO: set real vmode + player_paused = false; + return playfile(filepath, player_type, vmode); + } + // unknown file type... + return -1; +} + +int player_info_loop() +{ +#ifdef FILEPLAYER_DEBUG +// player_debug_packets(); +#endif + + if (pterm.IsOpened()) + { + int player_data_size = pterm.Read(player_data, player_max_data_size); + //strcpy(player_data, "Info\n00:00:24\nN/A\nN/A\nN/A\nN/A\n"); + //int player_data_size = strlen(player_data); + + // parse data from player: + if (player_data_size > 0) + { + player_data[player_data_size] = '\0'; + //MSG("data[%d]: %s\n", player_data_size, player_data); + if (!mpegplayer || mpegplayer_info_cnt++ < 10) + player_parseinfo(player_data, player_data_size); + } + } + // playing stopped, so clean-up + if (playerid < 0) + { + if (pterm.IsOpened()) + { + pterm.Close(); + player_reading_info = PLAYER_READINFO_NONE; + } + return 1; + } + return 0; +} + +void player_parseinfo(char *data, int data_size) +{ + while (data_size > 0) + { + if (player_reading_info == PLAYER_READINFO_TIME) + { + if (data_size > 7) + { + int timelen = 0; + if (isdigit(data[0])) + { + timelen = ((data[0] - '0') * 10 + (data[1] - '0')) * 3600; + timelen += ((data[3] - '0') * 10 + (data[4] - '0')) * 60; + timelen += ((data[6] - '0') * 10 + (data[7] - '0')); + } + script_totaltime_callback(timelen); + MSG("* TOTAL TIME: %d secs\n", timelen); + data += 8; + data_size -= 8; + player_reading_info = PLAYER_READINFO_TITLE; + to_enter(data, data_size); + continue; + } + data++; + data_size--; + } + else if (player_reading_info == PLAYER_READINFO_TITLE) + { + char *titl = data; + to_enter(data, data_size); + MSG("* TITLE: %s.\n", titl); + player_reading_info = PLAYER_READINFO_ARTIST; + continue; + } + else if (player_reading_info == PLAYER_READINFO_ARTIST) + { + char *artist = data; + to_enter(data, data_size); + MSG("* ARTIST: %s.\n", artist); + player_reading_info = PLAYER_READINFO_AUDIOINFO; + continue; + } + else if (player_reading_info == PLAYER_READINFO_AUDIOINFO) + { + char *ai = data; + to_enter(data, data_size); + script_audio_info_callback(ai); + MSG("* AUDIO: %s.\n", ai); + player_reading_info = PLAYER_READINFO_VIDEOINFO; + continue; + } + else if (player_reading_info == PLAYER_READINFO_VIDEOINFO) + { + char *vi = data; + to_enter(data, data_size); + script_video_info_callback(vi); + MSG("* VIDEO: %s.\n", vi); + player_reading_info = PLAYER_READINFO_NONE; + continue; + } + else + { + DWORD cmd = SafeGetDword(data); + if (cmd == 0x656d6954 && data_size >= 7) // "Time" + { + if (data[4] <= 99 && data[5] <= 59 && data[6] <= 59) + { + int tim = data[4] * 3600 + data[5] * 60 + data[6]; + script_time_callback(tim); + MSG("PLAYER TIME = %d\n", tim); + } + data += 7; + data_size -= 7; + player_reading_info = PLAYER_READINFO_NONE; + } + else if (cmd == 0x6f666e49 && data_size > 4 + && data[4] == '\n') // "Info" + { + player_reading_info = PLAYER_READINFO_TIME; + data += 5; + data_size -= 5; + } + else if (cmd == 0x79616c50 && data_size >= 4) // "Play" + { + data += 4; + data_size -= 4; + player_reading_info = PLAYER_READINFO_NONE; + player_paused = false; + } + else if (cmd == 0x74696157 && data_size >= 4) // "Wait" + { + data += 4; + data_size -= 4; + script_error_callback(SCRIPT_ERROR_WAIT); + player_reading_info = PLAYER_READINFO_NONE; + } + else if (cmd == 0x41646142 && data_size >= 4) // "BadA" + { + data += 4; + data_size -= 4; + script_error_callback(SCRIPT_ERROR_BAD_AUDIO); + player_reading_info = PLAYER_READINFO_NONE; + } + else if (cmd == 0x43646142 && data_size >= 4) // "BadC" + { + data += 4; + data_size -= 4; + script_error_callback(SCRIPT_ERROR_BAD_CODEC); + player_reading_info = PLAYER_READINFO_NONE; + } + else + { + data++; + data_size--; + player_reading_info = PLAYER_READINFO_NONE; + } + } + } +} + +BOOL player_pause() +{ + player_paused = !player_paused; + player_command("P\n"); + return TRUE; +} + +BOOL player_stop() +{ + int pstat; + if (playerid < 0) + return FALSE; + + MSG("Player: * Stopping...\n"); + + // first, we ask player to stop... + player_command("X\n"); + + // sleep and wait for signals... + sleep(1); + usleep(200000); + + // if it's still alive... + if (waitpid(playerid, &pstat, WNOHANG) == 0) + { + MSG("Player: * Stopping by force (%d)!\n", playerid); +khwl_stop(); + // kill it by force + kill(playerid, SIGKILL); + int pstat; + waitpid(playerid, &pstat, 0); +khwl_stop(); + // clear video garbage + if (video) + khwl_restoreparams(); + } + + stopfile(); + + return TRUE; +} + +BOOL player_seek(int seconds) +{ + if (player_paused) + { + player_command("P\n"); + player_paused = false; + } + + char seekcmd[10]; + sprintf(seekcmd, "s%06d\n", seconds); + player_command(seekcmd); + + return TRUE; +} + +BOOL player_zoom_hor(int /*scale*/) +{ + //player_command("zw15\n"); + //zl15 + //zr15 + //zu15 + //zd15 + //zw15 + //zw-15 + //zh15 + //zh-15 + //zF + //zR reset zoom + //zC center scroll + return FALSE; +} + +BOOL player_zoom_ver(int /*scale*/) +{ + return FALSE; +} + +BOOL player_scroll(int /*offx*/, int /*offy*/) +{ + return FALSE; +} + +int player_forward() +{ + player_command("t1\n"); + if (video && !mpegplayer) + return 1; + return 0; +} + +int player_rewind() +{ + player_command("t2\n"); + if (video && !mpegplayer) + return 1; + return 0; +} + +#endif + +////////////////////////////////////////////////////////////////////// +// INFO + +void stopid3() +{ + if (id3id < 0) + return; + + id3id = -1; + msg("Player: * ID3 Stopped.\n"); +} + + +int player_id3_loop() +{ + if (id3term.IsOpened()) + { + int id3_data_size = id3term.Read(id3_data, id3_max_data_size); + //strcpy(id3_data, "Titl\nTitle\nAuth\nArtist\n"); + //strcpy(id3_data, "Dims\n1000\n500\nClrs\n3\n"); + //int id3_data_size = strlen(id3_data); + + // parse data from fileinfo: + if (id3_data_size > 0) + { + id3_data[id3_data_size] = '\0'; + //MSG("ID3DATA (%d): [%s]\n", id3_data_size, id3_data); + player_parseid3(id3_data, id3_data_size); + + } + } + + // id3 info process stopped, so clean-up + if (id3id < 0) + { + if (id3term.IsOpened()) + { + id3term.Close(); + player_reading_id3 = PLAYER_READID3_NONE; + } + return 1; + } + + return 0; +} + +void player_parseid3(char *data, int data_size) +{ + static int width = 0; + while (data_size > 0) + { + if (player_reading_id3 == PLAYER_READID3_TITLE) + { + char *titl = data; + to_enter(data, data_size); + script_name_callback(titl); + player_reading_id3 = PLAYER_READID3_NONE; + continue; + } + else if (player_reading_id3 == PLAYER_READID3_ARTIST) + { + char *auth = data; + to_enter(data, data_size); + script_artist_callback(auth); + player_reading_id3 = PLAYER_READID3_NONE; + continue; + } + else if (player_reading_id3 == PLAYER_READID3_WIDTH) + { + char *w = data; + to_enter(data, data_size); + width = atoi(w); + player_reading_id3 = PLAYER_READID3_HEIGHT; + continue; + } + else if (player_reading_id3 == PLAYER_READID3_HEIGHT) + { + char *height = data; + to_enter(data, data_size); + script_framesize_callback(width, atoi(height)); + player_reading_id3 = PLAYER_READID3_NONE; + continue; + } + else if (player_reading_id3 == PLAYER_READID3_CLRS) + { + char *clrs = data; + to_enter(data, data_size); + script_colorspace_callback(atoi(clrs)); + player_reading_id3 = PLAYER_READID3_NONE; + continue; + } + else + { + DWORD cmd = SafeGetDword(data); + if (cmd == 0x6c746954 && data_size >= 5) // "Titl" + { + data += 5; + data_size -= 5; + player_reading_id3 = PLAYER_READID3_TITLE; + continue; + } + else if (cmd == 0x74737441 && data_size >= 5) // "Atst" + { + data += 5; + data_size -= 5; + player_reading_id3 = PLAYER_READID3_ARTIST; + continue; + } + else if (cmd == 0x736d6944 && data_size >= 5) // "Dims" + { + data += 5; + data_size -= 5; + width = 0; + player_reading_id3 = PLAYER_READID3_WIDTH; + continue; + } + else if (cmd == 0x73726c43 && data_size >= 5) // "Clrs" + { + data += 5; + data_size -= 5; + player_reading_id3 = PLAYER_READID3_CLRS; + continue; + } + else if (cmd == 0x72727245 && data_size >= 5) // "Errr" + { + data += 5; + data_size -= 5; + script_name_callback(""); + script_artist_callback(""); + //script_error_callback(); + player_reading_id3 = PLAYER_READID3_NONE; + continue; + } + else if (cmd == 0x656e6f4e && data_size >= 5) // "None" + { + data += 5; + data_size -= 5; + script_name_callback(""); + script_artist_callback(""); + player_reading_id3 = PLAYER_READID3_NONE; + continue; + } + else + data_size = 0; + } + } +} + + + +void player_startinfo(const char *fname, const char *charset) +{ +#ifdef PLAYER_INFO_EMBED + player_getinfo(fname, charset); +#else + const char *args[] = { "/bin/fileinfo.bin", fname, charset, NULL }; + + if (id3id >= 0) + { + kill(id3id, SIGKILL); + int pstat; + waitpid(id3id, &pstat, 0); + id3id = -1; + } + + id3term.Open(); + set_sig(player_sig_handler); + id3term.Set(); + id3id = exec_file(args[0], (const char **)args); + id3term.Restore(); +#endif +} + +#ifdef EXTERNAL_PLAYER +void player_setdebug(BOOL ison) +{ + player_msg = ison == TRUE; +} + +BOOL player_getdebug() +{ + return player_msg; +} +#endif diff --git a/src/player.h b/src/player.h new file mode 100644 index 0000000..5f64177 --- /dev/null +++ b/src/player.h @@ -0,0 +1,67 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - file player header file + * \file player.h + * \author bombur + * \version 0.1 + * \date 2.08.2004 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#ifndef SP_PLAYER_H +#define SP_PLAYER_H + +// use this for our audio/video players +#define INTERNAL_VIDEO_PLAYER +#define INTERNAL_VIDEO_MPEG_PLAYER +#define INTERNAL_AUDIO_PLAYER + +//#define EXTERNAL_PLAYER + +int player_mem_init(); +int player_mem_deinit(); + +/// Play file +int player_play(char *filepath = NULL); + +/// Advance playing +int player_info_loop(); +int player_id3_loop(); + +/// Pause playing +BOOL player_pause(); + +/// Stop playing +BOOL player_stop(); + +/// Seek to given time and play +BOOL player_seek(int seconds); + +BOOL player_zoom_hor(int scale); +BOOL player_zoom_ver(int scale); +BOOL player_scroll(int offx, int offy); + +int player_forward(); +int player_rewind(); + +/// Start get_info from media file using external program. +void player_startinfo(const char *fname, const char *charset); + +void player_setdebug(BOOL ison); +BOOL player_getdebug(); + +#endif // of SP_PLAYER_H diff --git a/src/script-dummyobjs.inc.cpp b/src/script-dummyobjs.inc.cpp new file mode 100644 index 0000000..72d7f51 --- /dev/null +++ b/src/script-dummyobjs.inc.cpp @@ -0,0 +1,33 @@ +////////////////////////////////////////////////////// +// SigmaPlayer Project. +// File auto-generated from MMSL language reference. +////////////////////////////////////////////////////// + +#include + +#include + +// dummy declarations (for utilities etc.): +void on_image_create(int obj_id, MMSL_OBJECT *obj, void *param) { } +void on_image_delete(int obj_id, MMSL_OBJECT *obj, void *param) { } + +void on_text_create(int obj_id, MMSL_OBJECT *obj, void *param) { } +void on_text_delete(int obj_id, MMSL_OBJECT *obj, void *param) { } + +void on_rect_create(int obj_id, MMSL_OBJECT *obj, void *param) { } +void on_rect_delete(int obj_id, MMSL_OBJECT *obj, void *param) { } + +#include + +#include + +// dummy declarations (for utilities etc.): +void on_image_get(int var_id, MmslVariable *var, void *param, int obj_id = -1, MMSL_OBJECT *obj = NULL) { } +void on_image_set(int var_id, MmslVariable *var, void *param, int obj_id = -1, MMSL_OBJECT *obj = NULL) { } + +void on_text_get(int var_id, MmslVariable *var, void *param, int obj_id = -1, MMSL_OBJECT *obj = NULL) { } +void on_text_set(int var_id, MmslVariable *var, void *param, int obj_id = -1, MMSL_OBJECT *obj = NULL) { } + +void on_rect_get(int var_id, MmslVariable *var, void *param, int obj_id = -1, MMSL_OBJECT *obj = NULL) { } +void on_rect_set(int var_id, MmslVariable *var, void *param, int obj_id = -1, MMSL_OBJECT *obj = NULL) { } + diff --git a/src/script-dummyvars.inc.cpp b/src/script-dummyvars.inc.cpp new file mode 100644 index 0000000..0ee8805 --- /dev/null +++ b/src/script-dummyvars.inc.cpp @@ -0,0 +1,34 @@ +////////////////////////////////////////////////////// +// SigmaPlayer Project. +// File auto-generated from MMSL language reference. +////////////////////////////////////////////////////// + +#include + +#include + +// dummy declarations (for utilities etc.): +void on_kernel_get(int var_id, MmslVariable *var, void *param, int obj_id = -1, MMSL_OBJECT *obj = NULL) { } +void on_kernel_set(int var_id, MmslVariable *var, void *param, int obj_id = -1, MMSL_OBJECT *obj = NULL) { } + +void on_screen_get(int var_id, MmslVariable *var, void *param, int obj_id = -1, MMSL_OBJECT *obj = NULL) { } +void on_screen_set(int var_id, MmslVariable *var, void *param, int obj_id = -1, MMSL_OBJECT *obj = NULL) { } + +void on_pad_get(int var_id, MmslVariable *var, void *param, int obj_id = -1, MMSL_OBJECT *obj = NULL) { } +void on_pad_set(int var_id, MmslVariable *var, void *param, int obj_id = -1, MMSL_OBJECT *obj = NULL) { } + +void on_drive_get(int var_id, MmslVariable *var, void *param, int obj_id = -1, MMSL_OBJECT *obj = NULL) { } +void on_drive_set(int var_id, MmslVariable *var, void *param, int obj_id = -1, MMSL_OBJECT *obj = NULL) { } + +void on_explorer_get(int var_id, MmslVariable *var, void *param, int obj_id = -1, MMSL_OBJECT *obj = NULL) { } +void on_explorer_set(int var_id, MmslVariable *var, void *param, int obj_id = -1, MMSL_OBJECT *obj = NULL) { } + +void on_player_get(int var_id, MmslVariable *var, void *param, int obj_id = -1, MMSL_OBJECT *obj = NULL) { } +void on_player_set(int var_id, MmslVariable *var, void *param, int obj_id = -1, MMSL_OBJECT *obj = NULL) { } + +void on_settings_get(int var_id, MmslVariable *var, void *param, int obj_id = -1, MMSL_OBJECT *obj = NULL) { } +void on_settings_set(int var_id, MmslVariable *var, void *param, int obj_id = -1, MMSL_OBJECT *obj = NULL) { } + +void on_flash_get(int var_id, MmslVariable *var, void *param, int obj_id = -1, MMSL_OBJECT *obj = NULL) { } +void on_flash_set(int var_id, MmslVariable *var, void *param, int obj_id = -1, MMSL_OBJECT *obj = NULL) { } + diff --git a/src/script-explorer.cpp b/src/script-explorer.cpp new file mode 100644 index 0000000..9b3e83a --- /dev/null +++ b/src/script-explorer.cpp @@ -0,0 +1,1190 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - file explorer wrapper impl. + * \file script-explorer.cpp + * \author bombur + * \version 0.1 + * \date 12.10.2006 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + + +static const StringPair items_sort_pairs[] = +{ + { "none", ITEMS_SORT_NONE }, + { "normal", ITEMS_SORT_NORMAL }, + { "inverse", ITEMS_SORT_INVERSE }, + { "random", ITEMS_SORT_RANDOM }, + { NULL, -1 }, +}; + +static const StringPair item_type_pairs[] = +{ + { "", ITEMS_SORT_NONE }, + { "file", ITEM_TYPE_FILE }, + { "track", ITEM_TYPE_TRACK }, + { "folder", ITEM_TYPE_FOLDER }, + { "up", ITEM_TYPE_UP }, + { "dvd", ITEM_TYPE_DVD }, + { NULL, -1 }, +}; + +// used for items hash +Item *tmpit = NULL; + +static int ItemsListCompareFuncNormal(Item **i1, Item **i2) +{ + return (*i1)->name.CompareNoCase((*i2)->name); +} +static int ItemsListCompareFuncReverse(Item **i1, Item **i2) +{ + return -(*i1)->name.CompareNoCase((*i2)->name); +} + + +//////////////////////////////////////////////////////////////////////// + +void ItemsList::Update() +{ + if (cur >= 0 && cur < items.GetN()) + { + // get extension + int eidx = items[cur]->name.ReverseFind('.'); + if (eidx < 0) + eidx = items[cur]->name.GetLength(); + fname = items[cur]->name.Left(eidx); + if (fname.FindNoCase("/cdrom/") == 0) + fname = fname.Mid(7); + if (fname.FindNoCase("/hdd/") == 0) + fname = fname.Mid(5); + if (fname.FindNoCase("/") == 0) + fname = fname.Mid(1); + ext = items[cur]->name.Mid(eidx); + if (itemhash != NULL) // playlist + path = items[cur]->name; + else + path = folder + items[cur]->name; + type = items[cur]->type; + filetime = items[cur]->datetime; + mask_index = items[cur]->mask_index; + filesize = MAX(items[cur]->size, 0); + + params->lastitem = items[cur]; + + if (params->curtarget != NULL && params->curtarget->itemhash != NULL) + { + tmpit->SetName(path); + copied = (params->curtarget->itemhash->Get(*tmpit) != NULL); + } + } else + { + if (cur < 0) + cur = -1; + if (cur > items.GetN()) + cur = items.GetN(); + fname = ""; + ext = ""; + path = ""; + type = ITEM_TYPE_NONE; + copied = FALSE; + filetime = 0; + filesize = 0; + mask_index = 0; + + params->lastitem = NULL; + } + mmsl->UpdateVariable(SCRIPT_VAR_EXPLORER_POSITION); + + mmsl->UpdateVariable(SCRIPT_VAR_EXPLORER_PATH); + mmsl->UpdateVariable(SCRIPT_VAR_EXPLORER_FILENAME); + mmsl->UpdateVariable(SCRIPT_VAR_EXPLORER_EXTENSION); + mmsl->UpdateVariable(SCRIPT_VAR_EXPLORER_TYPE); + mmsl->UpdateVariable(SCRIPT_VAR_EXPLORER_COPIED); + mmsl->UpdateVariable(SCRIPT_VAR_EXPLORER_FILETIME); + mmsl->UpdateVariable(SCRIPT_VAR_EXPLORER_MASKINDEX); + mmsl->UpdateVariable(SCRIPT_VAR_EXPLORER_FILESIZE); +} + +bool mask_compare(char *str, const SPString & msk) +{ + // empty mask - not allowed! + if (msk[0] == '\0') + return false; + + // process multiple masks + char *comma = strchr(msk, ','); + int mlen = msk.GetLength(); + if (comma != NULL) + { + // if one of other masks match + if (mask_compare(str, SPString(comma + 1))) + return true; + mlen = comma - *msk; + } + + int end = 0, omaski = 0; + int len = strlen(str); + int ml; + for (int i = 0; i < len && omaski <= mlen; omaski++) + { + const char *m = *msk + omaski; + for (int j = omaski; j < mlen && *m != '*'; j++) + m++; + if (*m == '*') + ml = m - msk - omaski; + else + ml = mlen - omaski + 1; + BOOL fl0 = false; + for (; i <= end; i++) + { + const char *tmp1 = str + i; + const char *tmp2 = *msk + omaski; + BOOL fl = true; + for (int j = 0; (j < ml); j++, tmp1++, tmp2++) + { + if (tolower(*tmp1) != tolower(*tmp2 == ',' ? 0 : *tmp2) && *tmp2 != '?') + { + fl = false; + break; + } + } + if (fl) + { + omaski += ml; + i += ml; + end = len; + fl0 = true; + break; + } + } + if (!fl0) + return false; + } + return true; +} + +static void items_sub_sort(int idx1, int idx2) +{ + if (idx1 == idx2) + return; + if (params->sort == ITEMS_SORT_NORMAL) + { + params->curitem->items.Sort(ItemsListCompareFuncNormal, idx1, idx2); + } + else if (params->sort == ITEMS_SORT_INVERSE) + { + params->curitem->items.Sort(ItemsListCompareFuncReverse, idx1, idx2); + } + else if (params->sort == ITEMS_SORT_RANDOM) + { + int n = idx2 - idx1 + 1; + for (int k = idx1; k <= idx2; k++) + { + int j; + // find random swap pos + do + { + j = (rand() % n) + idx1; + } while (j == k); + + // swap + Item *tmp = params->curitem->items[j]; + params->curitem->items[j] = params->curitem->items[k]; + params->curitem->items[k] = tmp; + } + } +} + +static void items_list_sort() +{ + params->lastitem = NULL; + if (params->curitem != NULL && params->sort != ITEMS_SORT_NONE) + { + int k, idx1 = 0, idx2 = 0, maxidx = 0; + // save current index + bool need_new_idx = params->curitem->cur >= 0 && params->curitem->cur < params->curitem->items.GetN(); + if (need_new_idx) + { + params->curitem->items[params->curitem->cur]->oldidx = params->curitem->cur; + } + + for (k = 0; k < ITEM_TYPE_MAX && params->filter[k] != ITEM_TYPE_NONE; k++) + { + //idx1 = params->curitem->add4types[k]; + idx2 = (k < ITEM_TYPE_MAX && params->filter[k] != ITEM_TYPE_NONE) + ? params->curitem->add4types[k]-1 : params->curitem->items.GetN() - 1; + if (idx2 + 1 > maxidx) + maxidx = idx2 + 1; + if (idx2 < idx1) + continue; + items_sub_sort(idx1, idx2); + idx1 = idx2 + 1; + } + if (maxidx < params->curitem->items.GetN()) + items_sub_sort(maxidx, params->curitem->items.GetN() - 1); + + if (need_new_idx) + { + int cur = params->curitem->cur; + params->curitem->cur = 0; + for (k = 0; k < params->curitem->items.GetN(); k++) + { + if (params->curitem->items[k]->oldidx == cur) + { + params->curitem->items[k]->oldidx = -1; + params->curitem->cur = k; + break; + } + } + params->curitem->Update(); + } + } +} + +void explorer_get_folder_name(SPString &folder) +{ + // skip the 'up' folder + if (folder == ".." || folder.GetLength() < 1) + return; + folder.Replace('\\', '/'); + if (folder.Find("/") != 0) + folder = "/" + folder; + if (folder.ReverseFind('/') != folder.GetLength() - 1) + folder = folder + "/"; +} + +void script_explorer_reset() +{ + if (params != NULL) + { + params->lists.DeleteObjects(); + params->playlists.DeleteObjects(); + params->curtarget = NULL; + params->curitem = NULL; + params->lastitem = NULL; + params->folder = ""; + + mmsl->UpdateVariable(SCRIPT_VAR_EXPLORER_COUNT); + mmsl->UpdateVariable(SCRIPT_VAR_EXPLORER_FOLDER); + mmsl->UpdateVariable(SCRIPT_VAR_EXPLORER_DRIVE_LETTER); + } +} + +void on_explorer_get(int var_id, MmslVariable *var, void *, int , MMSL_OBJECT *) +{ + if (var == NULL) + return; + switch (var_id) + { + case SCRIPT_VAR_EXPLORER_CHARSET: + var->Set(params->iso_lang); + break; + case SCRIPT_VAR_EXPLORER_FOLDER: + { + SPString f = params->folder; + if (f.ReverseFind('/') == f.GetLength() - 1) + f = f.Left(f.GetLength() - 1); + var->Set(f); + } + break; + case SCRIPT_VAR_EXPLORER_DRIVE_LETTER: + { + SPString f; + if (params->folder.FindNoCase("/hdd/") == 0) + { + f = params->folder.Mid(5); + if (f != "") + { + static const char led_drive_table[] = { 'A', 'b', 'C', 'd', 'E', 'F', 'G', 'H', + 'I', 'J', 'k', 'L', 'm', 'N', 'O', 'P', 'q', 'r', 'S', 't', 'U', 'v' }; + char ch = (char)toupper(f[0]); + if (ch > 'A' && ch < sizeof(led_drive_table) + 'A') + ch = led_drive_table[ch - 'A']; + f = SPString(ch, 1); + var->Set(f); + break; + } + } + var->Set(f); + } + break; + case SCRIPT_VAR_EXPLORER_TARGET: + { + SPString f = params->target; + if (f.ReverseFind('/') == f.GetLength() - 1) + f = f.Left(f.GetLength() - 1); + var->Set(f); + } + break; + case SCRIPT_VAR_EXPLORER_FILTER: + { + const char *flt[] = { "", "file", "track", "folder", "up", "dvd" }; + SPString filter; + for (int i = 0; i < ITEM_TYPE_MAX; i++) + { + if (params->filter[i] != ITEM_TYPE_NONE) + { + if (filter != "") + filter += ","; + filter += flt[params->filter[i]]; + } + } + + var->Set(filter); + } + break; + case SCRIPT_VAR_EXPLORER_MASK1: + case SCRIPT_VAR_EXPLORER_MASK2: + case SCRIPT_VAR_EXPLORER_MASK3: + case SCRIPT_VAR_EXPLORER_MASK4: + case SCRIPT_VAR_EXPLORER_MASK5: + var->Set(params->mask[var_id - SCRIPT_VAR_EXPLORER_MASK1]); + break; + case SCRIPT_VAR_EXPLORER_PATH: + var->Set(params->curitem != NULL ? params->curitem->path : SPString()); + break; + case SCRIPT_VAR_EXPLORER_FILENAME: + var->Set(params->curitem != NULL ? params->curitem->fname : SPString()); + break; + case SCRIPT_VAR_EXPLORER_EXTENSION: + var->Set(params->curitem != NULL ? params->curitem->ext : SPString()); + break; + case SCRIPT_VAR_EXPLORER_TYPE: + { + int typ = params->curitem != NULL ? params->curitem->type : -1; + StringPair::Set(var, item_type_pairs, typ); + } + break; + case SCRIPT_VAR_EXPLORER_FILESIZE: + { + if (params->curitem == NULL) + var->Set(0); + else + { + int fsize = (params->curitem->filesize < 0x7fffffff) ? + (int)params->curitem->filesize : -(int)(params->curitem->filesize/1024); + var->Set(fsize); + } + } + break; + case SCRIPT_VAR_EXPLORER_FILETIME: + { + static SPString ft; + ft = ""; + if (params->curitem->filetime != 0) + ft.Strftime("%d.%m.%Y %H:%M", params->curitem->filetime); + var->Set(ft); + } + break; + case SCRIPT_VAR_EXPLORER_MASKINDEX: + var->Set(params->curitem != NULL ? params->curitem->mask_index : 0); + break; + case SCRIPT_VAR_EXPLORER_COUNT: + var->Set(params->curitem != NULL ? params->curitem->items.GetN() : 0); + break; + case SCRIPT_VAR_EXPLORER_SORT: + StringPair::Set(var, items_sort_pairs, params->sort); + break; + case SCRIPT_VAR_EXPLORER_POSITION: + var->Set(params->curitem != NULL ? params->curitem->cur : -1); + break; + case SCRIPT_VAR_EXPLORER_COMMAND: + case SCRIPT_VAR_EXPLORER_FIND: + var->Set(""); + break; + case SCRIPT_VAR_EXPLORER_COPIED: + var->Set(params->curitem != NULL ? params->curitem->copied : 0); + break; + } +} + +void on_explorer_set(int var_id, MmslVariable *var, void *, int , MMSL_OBJECT *) +{ + if (var == NULL) + return; + switch (var_id) + { + case SCRIPT_VAR_EXPLORER_CHARSET: + params->iso_lang = var->GetString(); + params->iso_lang_changed = true; + params->list_changed = true; + break; + case SCRIPT_VAR_EXPLORER_FOLDER: + { + params->folder = var->GetString(); + if (params->folder.FindNoCase("/cdrom/") == 0 || params->folder.FindNoCase("/hdd/") == 0 || + params->folder.FindNoCase("/") == 0) + params->list_changed = true; + explorer_get_folder_name(params->folder); + } + break; + case SCRIPT_VAR_EXPLORER_TARGET: + { + params->target = var->GetString(); + explorer_get_folder_name(params->target); + params->curtarget = NULL; + for (int i = 0; i < params->playlists.GetN(); i++) + { + if (params->target.FindNoCase(params->playlists[i]->folder) == 0) + { + params->curtarget = params->playlists[i]; + break; + } + } + } + break; + case SCRIPT_VAR_EXPLORER_FILTER: + { + SPString filter = var->GetString(); + int i; + for (i = 0; i < ITEM_TYPE_MAX; i++) + { + if (filter.FindNoCase("up") == 0) + params->filter[i] = ITEM_TYPE_UP; + else if (filter.FindNoCase("folder") == 0) + params->filter[i] = ITEM_TYPE_FOLDER; + else if (filter.FindNoCase("file") == 0) + params->filter[i] = ITEM_TYPE_FILE; + else if (filter.FindNoCase("track") == 0) + params->filter[i] = ITEM_TYPE_TRACK; + else if (filter.FindNoCase("dvd") == 0) + params->filter[i] = ITEM_TYPE_DVD; + else + break; + int nxt = filter.Find(','); + if (nxt < 0) + filter = ""; + filter = filter.Mid(nxt + 1); + } + for (; i < ITEM_TYPE_MAX; i++) + params->filter[i] = ITEM_TYPE_NONE; + params->list_changed = true; + } + break; + case SCRIPT_VAR_EXPLORER_MASK1: + case SCRIPT_VAR_EXPLORER_MASK2: + case SCRIPT_VAR_EXPLORER_MASK3: + case SCRIPT_VAR_EXPLORER_MASK4: + case SCRIPT_VAR_EXPLORER_MASK5: + { + SPString m = var->GetString(); + + // for easy mask concatenation + m.TrimLeft(); + m.TrimRight(); + if (m.GetLength() > 0 && m[(int)m.GetLength() - 1] != ',') + m += SPString(","); + params->mask[var_id - SCRIPT_VAR_EXPLORER_MASK1] = m; + + params->allmask = SPString(); + for (int i = 0; i < num_file_masks; i++) + params->allmask += params->mask[i]; + + params->list_changed = true; + } + break; + case SCRIPT_VAR_EXPLORER_SORT: + params->sort = (ITEMS_SORT)StringPair::Get(var, items_sort_pairs, ITEMS_SORT_NONE); + items_list_sort(); + params->list_changed = true; + break; + case SCRIPT_VAR_EXPLORER_POSITION: + if (params->curitem != NULL && params->curitem->items.GetN() > 0) + { + params->curitem->cur = var->GetInteger(); + params->curitem->Update(); + } + break; + case SCRIPT_VAR_EXPLORER_COMMAND: + { + // our generated random indexes aren't valid any more... + if (params->curitem != NULL) + { + if (params->list_changed) + { + SPSafeDeleteArray(params->curitem->random_idx); + SPSafeDeleteArray(params->curitem->random_ridx); + params->curitem->cur_random_pos = -1; + params->list_changed = false; + } + } + + SPString cmd = var->GetString(); + if (cmd.CompareNoCase("update") == 0) + { + int i; + params->curitem = NULL; + + msg("Explorer: Update!\n"); + + if (params->folder == "..") + { + if (params->lists.GetN() > 1) + { + int idx = params->lists.GetN() - 1; + SPSafeDelete(params->lists[idx]); + params->lists.Remove(idx); + + // if we don't need update + if (params->lists[idx - 1]->allmask.CompareNoCase(params->allmask) != 0) + { + params->folder = params->lists[idx - 1]->folder; + } else + { + params->curitem = params->lists[idx - 1]; + mmsl->UpdateVariable(SCRIPT_VAR_EXPLORER_COUNT); + params->curitem->Update(); + params->folder = params->curitem->folder; + mmsl->UpdateVariable(SCRIPT_VAR_EXPLORER_FOLDER); + mmsl->UpdateVariable(SCRIPT_VAR_EXPLORER_DRIVE_LETTER); + break; + } + } else + { + mmsl->UpdateVariable(SCRIPT_VAR_EXPLORER_COUNT); + params->folder = ""; + mmsl->UpdateVariable(SCRIPT_VAR_EXPLORER_FOLDER); + mmsl->UpdateVariable(SCRIPT_VAR_EXPLORER_DRIVE_LETTER); + break; + } + } + + // translate virtual path + SPString oldfolder = params->folder; + params->folder = cdrom_getrealpath(params->folder); + if (params->folder != oldfolder) + mmsl->UpdateVariable(SCRIPT_VAR_EXPLORER_FOLDER); + mmsl->UpdateVariable(SCRIPT_VAR_EXPLORER_DRIVE_LETTER); + + if (params->folder.FindNoCase("/cdrom/") == 0 || params->folder.FindNoCase("/hdd/") == 0) + { + int from_which = -1; + for (i = params->lists.GetN() - 1; i >= 0; i--) + { + if (params->folder.FindNoCase(params->lists[i]->folder) == 0) + { + // exactly the same! + if (params->folder.CompareNoCase(params->lists[i]->folder) == 0) + { +#if 0 + params->curitem = params->lists[i]; + mmsl->UpdateVariable(SCRIPT_VAR_EXPLORER_COUNT); + params->curitem->Update(); + params->folder = params->curitem->folder; + mmsl->UpdateVariable(SCRIPT_VAR_EXPLORER_FOLDER); + mmsl->UpdateVariable(SCRIPT_VAR_EXPLORER_DRIVE_LETTER); + break; +#endif + from_which = i - 1; + } + else + from_which = i; + break; + } + } + // delete sub-lists + for (i = params->lists.GetN() - 1; i > from_which; i--) + { + SPSafeDelete(params->lists[i]); + params->lists.Remove(i); + } + } + + // detect source type + if (params->folder.FindNoCase("/cdrom/") == 0 || params->folder.FindNoCase("/hdd/") == 0 || + params->folder == "/") + { + params->curitem = new ItemsList(); + + // read items + explorer_get_folder_name(params->folder); + params->curitem->folder = params->folder; + params->curitem->allmask = params->allmask; + for (i = 0; i < num_file_masks; i++) + params->curitem->mask[i] = params->mask[i]; + + srand((unsigned int)time(NULL)); + + // first, add audio tracks (for root folder only!) + char tmp[10]; + if (params->folder.CompareNoCase("/cdrom/") == 0) + { + Cdda *cdda = cdda_open(); + if (cdda != NULL) + { + for (i = 0; i < cdda->tracks.GetN(); i++) + { + Item *it = new Item(); + it->parent = params->curitem; + sprintf(tmp, "%02d.cda", i + 1); + // we don't need hash for '/cdrom' lists + if (params->curitem->itemhash == NULL) + { + it->name = tmp; + it->namehash = 0; + } else + it->SetName(tmp); + it->datetime = 0; + it->mask_index = 0; + it->size = 0; + it->type = ITEM_TYPE_TRACK; + bool was = false, filterset = false; + for (int ins = 0; ins < ITEM_TYPE_MAX; ins++) + { + if (params->filter[ins] != ITEM_TYPE_NONE) + filterset = true; + if (params->filter[ins] == ITEM_TYPE_TRACK) + { + params->curitem->items.Insert(it, params->curitem->add4types[ins]); + was = true; + } + if (was) + params->curitem->add4types[ins]++; + } + if (!filterset) + params->curitem->items.Add(it); + else if (!was) + delete it; + } + } + } + + //if (has_data) + { + if (!cdrom_ismounted() || params->iso_lang_changed) + { + cdrom_mount(params->iso_lang, FALSE); + params->iso_lang_changed = false; + } + + // determine DVD folder + bool is_dvd = false; + int p = params->folder.FindNoCase("VIDEO_TS/"); + if (p >= 0 && p == params->folder.GetLength() - 9) + { + is_dvd = true; + } + + bool root_folder = (params->folder == "/"); + + DIR *dir = cdrom_opendir(params->folder); + static char path[4097]; + strcpy(path, params->folder); + int path_add = strlen(path); + if (path_add < 4000) + while (dir != NULL) + { + struct dirent *d = cdrom_readdir(dir); + if (d == NULL) + break; + if (d->d_name[0] == '.' && d->d_name[1] == '\0') + continue; + struct stat64 statbuf; + + strcpy(path + path_add, d->d_name); + int mask_index = 0; + if (cdrom_stat(path, &statbuf) < 0) + { + msg("Cannot stat %s\n", path); + statbuf.st_mode = 0; + } + ITEM_TYPE ittype; + if (!root_folder && d->d_name[0] == '.' && d->d_name[1] == '.' && d->d_name[2] == '\0') + { + if (params->lists.GetN() == 0) + continue; + ittype = ITEM_TYPE_UP; + } + else if (S_ISDIR(statbuf.st_mode)) + { + if (d->d_name[0] == '.') // hidden + continue; + + // don't show other root folders except mounted ones. + if (root_folder) + { + if (strcasecmp(d->d_name, "cdrom") != 0 && + strcasecmp(d->d_name, "hdd") != 0) + continue; + } + ittype = ITEM_TYPE_FOLDER; + } + else + { + if (is_dvd && (strcasecmp(d->d_name, "VIDEO_TS.IFO") == 0 || + strcasecmp(d->d_name, "VIDEO_TS.BUP") == 0)) + { + ittype = ITEM_TYPE_DVD; + is_dvd = false; + } else + { + ittype = ITEM_TYPE_FILE; + BOOL found = FALSE; + for (int mi = 0; mi < num_file_masks; mi++) + { + if (mask_compare(d->d_name, params->mask[mi])) + { + found = TRUE; + mask_index = mi + 1; + break; + } + } + if (!found) + continue; + } + } + Item *it = new Item(); + it->parent = params->curitem; + // we don't need hash for '/cdrom' lists + if (params->curitem->itemhash == NULL) + { + it->name = d->d_name; + it->namehash = 0; + } else + it->SetName(d->d_name); + it->datetime = statbuf.st_mtime; + it->size = ittype == ITEM_TYPE_FILE ? statbuf.st_size : 0; + it->type = ittype; + it->mask_index = mask_index; + bool was = false, filterset = false; + for (int ins = 0; ins < ITEM_TYPE_MAX; ins++) + { + if (params->filter[ins] != ITEM_TYPE_NONE) + filterset = true; + if (params->filter[ins] == ittype) + { + params->curitem->items.Insert(it, params->curitem->add4types[ins]); + was = true; + } + if (was) + { + params->curitem->add4types[ins]++; + } + } + if (!filterset) + params->curitem->items.Add(it); + else if (!was) + delete it; + + // script_update(); + } + cdrom_closedir(dir); + } + + if (params->sort != ITEMS_SORT_NONE) + items_list_sort(); + + params->lists.Add(params->curitem); + } + else if (params->folder.FindNoCase("/dvd/") == 0) + { + } + else // other playlists + { + for (i = 0; i < params->playlists.GetN(); i++) + { + if (params->folder.FindNoCase(params->playlists[i]->folder) == 0) + { + params->curitem = params->playlists[i]; + // If we need to apply a new mask to playlist, + // we use actual list in itemhash to create filtered items list. + if (params->curitem->allmask.CompareNoCase(params->allmask) != 0 + && params->curitem->itemhash != NULL) + { + params->curitem->items.Clear(); + Item *cur = params->curitem->itemhash->GetFirst(); + while (cur != NULL) + { + if (mask_compare(cur->name, params->allmask)) + params->curitem->items.Add(cur); + cur = params->curitem->itemhash->GetNext(*cur); + } + params->curitem->allmask = params->allmask; + for (int mi = 0; mi < num_file_masks; mi++) + params->curitem->mask[mi] = params->mask[mi]; + // we must apply sorting because hash list is unordered. + ITEMS_SORT oldsort = params->sort; + if (params->sort == ITEMS_SORT_NONE) + params->sort = ITEMS_SORT_NORMAL; + items_list_sort(); + params->sort = oldsort; + } + break; + } + } + } + + mmsl->UpdateVariable(SCRIPT_VAR_EXPLORER_COUNT); + + if (params->curitem != NULL) + { + Item *lastit = params->lastitem; + params->curitem->cur = 0; + params->curitem->Update(); + if (lastit != NULL) + params->lastitem = lastit; + } + } + else if (cmd.CompareNoCase("first") == 0) + { + if (params->curitem != NULL && params->curitem->items.GetN() > 0) + { + params->curitem->cur = 0; + params->curitem->Update(); + } + } + else if (cmd.CompareNoCase("last") == 0) + { + if (params->curitem != NULL && params->curitem->items.GetN() > 0) + { + params->curitem->cur = params->curitem->items.GetN() - 1; + params->curitem->Update(); + } + } + else if (cmd.CompareNoCase("next") == 0) + { + if (params->curitem != NULL && params->curitem->items.GetN() > 0 && params->curitem->cur < params->curitem->items.GetN()) + { + params->curitem->cur++; + params->curitem->Update(); + } + } + else if (cmd.CompareNoCase("prev") == 0) + { + if (params->curitem != NULL && params->curitem->items.GetN() > 0 && params->curitem->cur >= 0) + { + params->curitem->cur--; + params->curitem->Update(); + } + } + else if (cmd.CompareNoCase("randomize") == 0) + { + if (params->curitem != NULL && params->curitem->items.GetN() > 0) + { + int k, n = params->curitem->items.GetN(); + SPSafeDeleteArray(params->curitem->random_idx); + SPSafeDeleteArray(params->curitem->random_ridx); + params->curitem->random_idx = new int [n]; + params->curitem->random_ridx = new int [n]; + for (k = 0; k < n; k++) + params->curitem->random_idx[k] = k; + if (n > 1) + for (k = 0; k < n; k++) + { + int j; // find random swap pos + do + { + j = (rand() % n); + } while (j == k); + // swap + int tmp = params->curitem->random_idx[j]; + params->curitem->random_idx[j] = params->curitem->random_idx[k]; + params->curitem->random_idx[k] = tmp; + } + for (k = 0; k < n; k++) + params->curitem->random_ridx[params->curitem->random_idx[k]] = k; + params->curitem->cur_random_pos = 0; + params->curitem->cur = params->curitem->random_idx[params->curitem->cur_random_pos]; + params->curitem->Update(); + } + } + else if (cmd.CompareNoCase("nextrandom") == 0 || cmd.CompareNoCase("prevrandom") == 0) + { + if (params->curitem != NULL && params->curitem->items.GetN() > 0) + { + if (params->curitem->random_idx != NULL && params->curitem->random_ridx != NULL) + { + if (params->curitem->cur >= 0 && params->curitem->cur < params->curitem->items.GetN()) + params->curitem->cur_random_pos = params->curitem->random_ridx[params->curitem->cur]; + + if (cmd.CompareNoCase("nextrandom") == 0) + params->curitem->cur_random_pos++; + else + params->curitem->cur_random_pos--; + + if (params->curitem->cur_random_pos < 0 || params->curitem->cur_random_pos >= params->curitem->items.GetN()) + { + params->curitem->cur = -1; + params->curitem->cur_random_pos = -1; + } else + { + params->curitem->cur = params->curitem->random_idx[params->curitem->cur_random_pos]; + } + params->curitem->Update(); + } + } + + } + else if (cmd.CompareNoCase("remove") == 0) + { + if (params->curitem != NULL && params->curitem->items.GetN() > 0 && + params->curitem->cur >= 0 && params->curitem->cur < params->curitem->items.GetN()) + { + if (params->curitem->itemhash != NULL) + params->curitem->itemhash->Remove(*params->curitem->items[params->curitem->cur]); + SPSafeDelete(params->curitem->items[params->curitem->cur]); + params->curitem->items.Remove(params->curitem->cur); + if (params->curitem->cur == params->curitem->items.GetN() - 1) + params->curitem->cur--; + mmsl->UpdateVariable(SCRIPT_VAR_EXPLORER_COUNT); + params->curitem->Update(); + } + } + else if (cmd.CompareNoCase("removeall") == 0) + { + if (params->curitem != NULL) + { + if (params->curitem->itemhash != NULL) + params->curitem->itemhash->Clear(); + params->curitem->items.DeleteObjects(); + params->curitem->cur = -1; + mmsl->UpdateVariable(SCRIPT_VAR_EXPLORER_COUNT); + params->curitem->Update(); + } + } + else if (cmd.CompareNoCase("copy") == 0 || cmd.CompareNoCase("copyall") == 0) + { + // first, find existing play-list + explorer_get_folder_name(params->target); + + int i; + ItemsList *pl = NULL; + for (i = params->playlists.GetN() - 1; i >= 0; i--) + { + // exactly the same! + if (params->target.CompareNoCase(params->playlists[i]->folder) == 0) + { + pl = params->playlists[i]; + break; + } + } + if (pl == NULL) // create a new play-list + { + pl = new ItemsList(); + if (pl == NULL) + break; + pl->allmask = params->allmask; + for (int mi = 0; mi < num_file_masks; mi++) + pl->mask[mi] = params->mask[mi]; + pl->folder = params->target; + + pl->itemhash = new SPHashListAbstract(items_num_hash); + + params->playlists.Add(pl); + } + params->curtarget = pl; + if (params->curitem == NULL || pl == params->curitem) + { + msg_error("Cannot copy items from current source.\n"); + break; + } + bool copyall = cmd.CompareNoCase("copyall") == 0; + + if (!copyall) + { + if (params->curitem->cur < 0 || params->curitem->cur >= params->curitem->items.GetN()) + { + msg_error("Cannot copy current item.\n"); + break; + } + i = params->curitem->cur; + } else + i = 0; + for (; i < params->curitem->items.GetN(); i++) + { + Item *srcit = params->curitem->items[i]; + if (srcit != NULL && (srcit->type == ITEM_TYPE_FILE || srcit->type == ITEM_TYPE_TRACK)) + { + // check if item already exists + Item *item = NULL; + static SPString nam; + nam = params->folder + srcit->name; + if (pl->itemhash != NULL) + { + tmpit->SetName(nam); + item = pl->itemhash->Get(*tmpit); + } + // not in the playlist yet. + if (item == NULL) + { + item = new Item(); + item->SetName(nam); + item->type = srcit->type; + item->datetime = srcit->datetime; + item->mask_index = srcit->mask_index; + item->size = srcit->size; + item->parent = srcit->parent; // inherit parent + pl->itemhash->Add(item); + + srcit->playlist = pl; + srcit->playlist_idx = pl->items.Add(item); + if (i == params->curitem->cur) + params->curitem->Update(); + } + } + if (!copyall) + break; + } + } + else if (cmd.CompareNoCase("targetremove") == 0 || cmd.CompareNoCase("targetremoveall") == 0) + { + // first, find existing play-list + explorer_get_folder_name(params->target); + + ItemsList *pl = NULL; + for (int i = params->playlists.GetN() - 1; i >= 0; i--) + { + // exactly the same! + if (params->target.CompareNoCase(params->playlists[i]->folder) == 0) + { + pl = params->playlists[i]; + break; + } + } + if (pl == NULL) // create a new play-list + { + msg_error("Cannot find target playlist.\n"); + break; + } + params->curtarget = pl; + if (params->curitem == NULL) + { + msg_error("Cannot use items from current source for removing.\n"); + break; + } + + bool removed = false; + if (cmd.CompareNoCase("targetremoveall") == 0) + { + pl->itemhash->Clear(); + pl->items.DeleteObjects(); + removed = true; + } else + { + + if (params->curitem->cur < 0 || params->curitem->cur >= params->curitem->items.GetN()) + { + msg_error("Cannot remove current item from target playlist.\n"); + break; + } + Item *srcit = params->curitem->items[params->curitem->cur]; + if (srcit != NULL) + { + // check if item already exists + Item *item = NULL; + SPString nam = params->folder + srcit->name; + if (pl->itemhash != NULL) + { + tmpit->SetName(nam); + item = pl->itemhash->Get(*tmpit); + } + // not in the playlist yet. + if (item == NULL) + { + msg_error("Cannot find current item in the target playlist.\n"); + break; + } + pl->itemhash->Remove(*item); + // find item in the linear list + for (int i = 0; i < pl->items.GetN(); i++) + { + if (pl->items[i] == item) + { + SPSafeDelete(pl->items[i]); + pl->items.Remove(i); + removed = true; + break; + } + } + } + } + + if (removed) + { + if (params->curitem == pl) + { + if (params->curitem->cur == params->curitem->items.GetN() - 1) + params->curitem->cur--; + mmsl->UpdateVariable(SCRIPT_VAR_EXPLORER_COUNT); + } + params->curitem->Update(); + } + } + } + break; + case SCRIPT_VAR_EXPLORER_FIND: + { + SPString fpath = var->GetString(); + if (params->curitem != NULL && params->curitem->items.GetN() > 0) + { + // first, a little optimization for current items in playlist + if (params->lastitem != NULL && params->lastitem->parent != NULL + && fpath.CompareNoCase(params->lastitem->parent->folder + params->lastitem->name) == 0 + && params->lastitem->playlist == params->curitem + && params->lastitem->playlist_idx >= 0 + && params->lastitem->playlist_idx < params->curitem->items.GetN()) + { + params->curitem->cur = params->lastitem->playlist_idx; + } + else + { + params->curitem->cur = -1; + for (int i = 0; i < params->curitem->items.GetN(); i++) + { + Item *ci = params->curitem->items[i]; + if (fpath.CompareNoCase(params->curitem->folder + ci->name) == 0) + { + params->curitem->cur = i; + break; + } + } + } + params->curitem->Update(); + } + } + break; + } + +} + diff --git a/src/script-internal.h b/src/script-internal.h new file mode 100644 index 0000000..3450658 --- /dev/null +++ b/src/script-internal.h @@ -0,0 +1,510 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - script wrapper header file + * \file script-internal.h + * \author bombur + * \version 0.1 + * \date 2.08.2004 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#ifndef SP_SCRIPT_INTERNAL_H +#define SP_SCRIPT_INTERNAL_H + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "script-vars.h" +#include "script-objs.h" + +enum SCRIPT_ERRORS +{ + SCRIPT_ERRORS_ALL = 0, + SCRIPT_ERRORS_GENERAL, + SCRIPT_ERRORS_CRITICAL, + SCRIPT_ERRORS_NONE, +}; + +enum PLAYER_REPEAT +{ + PLAYER_REPEAT_NONE = 0, + PLAYER_REPEAT_SELECTION, + PLAYER_REPEAT_TRACK, + PLAYER_REPEAT_ALL, + PLAYER_REPEAT_RANDOM, +}; + +enum ITEMS_SORT +{ + ITEMS_SORT_NONE = 0, + ITEMS_SORT_NORMAL, + ITEMS_SORT_INVERSE, + ITEMS_SORT_RANDOM, +}; + +enum ITEM_TYPE +{ + ITEM_TYPE_NONE = 0, + ITEM_TYPE_FILE, + ITEM_TYPE_TRACK, + ITEM_TYPE_FOLDER, + ITEM_TYPE_UP, + ITEM_TYPE_DVD, + + ITEM_TYPE_MAX, +}; + +enum PLAYER_TYPE +{ + PLAYER_TYPE_UNKNOWN = 0, + PLAYER_TYPE_DVD, + PLAYER_TYPE_AUDIOCD, + PLAYER_TYPE_VIDEO, + PLAYER_TYPE_AUDIO, + PLAYER_TYPE_FILE, + PLAYER_TYPE_FOLDER, +}; + +enum PLAYER_COLOR_SPACE +{ + PLAYER_COLOR_SPACE_UNKNOWN = 0, + PLAYER_COLOR_SPACE_GRAYSCALE = 1, + PLAYER_COLOR_SPACE_YCRCB = 3, +}; + +const int num_file_masks = 5; + +/////////////////////////////////////////// + +class StringPair +{ +public: + const char *str; + int value; + + static int Get(MmslVariable *var, const StringPair *arr, int def) + { + SPString str = var->GetString(); + for (int i = 0; arr[i].str != NULL; i++) + { + if (str.CompareNoCase(arr[i].str) == 0) + return arr[i].value; + } + return def; + } + + static bool Set(MmslVariable *var, const StringPair *arr, int value) + { + for (int i = 0; arr[i].str != NULL; i++) + { + if (arr[i].value == value) + { + var->Set(arr[i].str); + return true; + } + } + return false; + } +}; + +const int items_num_hash = 117; + +class ItemsList; + +/// List item +class Item +{ +public: + /// ctor + Item() + { + type = ITEM_TYPE_NONE; + mask_index = 0; + oldidx = -1; + parent = NULL; + + playlist = NULL; + playlist_idx = -1; + + datetime = 0; + size = 0; + + prev = NULL; + next = NULL; + + namehash = 0; + } + +public: + /// filename (file.ext) or item name + SPString name; + /// item type + ITEM_TYPE type; + /// mask index + int mask_index; + + time_t datetime; + LONGLONG size; + + // used to search new index after sorting + int oldidx; + + // parent list index (primary only, not for play-lists) + ItemsList *parent; + + /// last playlist the item was added to (if multiple) + ItemsList *playlist; + int playlist_idx; + +public: + // hash-related stuff... + Item *prev, *next; + DWORD namehash; + + /// compare + template + bool operator == (const T & r) + { + if (name == NULL || r.name == NULL) + return false; + return name.CompareNoCase(r.name) == 0; + } + + inline operator DWORD () const + { + return namehash; + } + + const Item * const GetItem() const + { + return this; + } + + /// Set variable name & calc. hash + void SetName(const SPString & n) + { + name = n; + namehash = SPStringHashFunc(name, items_num_hash); + } + +}; + +class ItemsList +{ +public: + /// ctor + ItemsList() + { + cur = -1; + copied = FALSE; + filesize = 0; + filetime = 0; + mask_index = 0; + + for (int i = 0; i < ITEM_TYPE_MAX; i++) + add4types[i] = 0; + + itemhash = NULL; + + random_idx = NULL; + random_ridx = NULL; + cur_random_pos = -1; + } + + /// dtor + ~ItemsList() + { + // don't delete itemhash items! + if (itemhash != NULL) + { + itemhash->DeleteObjects(); + delete itemhash; + items.Clear(); + } + else + items.DeleteObjects(); + SPSafeDeleteArray(random_idx); + SPSafeDeleteArray(random_ridx); + } + + void Update(); + +public: + SPList items; + // "/folder1/folder2/" + SPString folder; + SPString allmask, mask[num_file_masks]; + int cur; + + // some cached current data: + SPString path, fname, ext; + ITEM_TYPE type; + BOOL copied; + LONGLONG filesize; + time_t filetime; + int mask_index; + + int add4types[ITEM_TYPE_MAX]; + + SPHashListAbstract *itemhash; + + // random indexes + int *random_idx; + // reversed random indexes + int *random_ridx; + // random index + int cur_random_pos; +}; + +class ItemInfo +{ +public: + /// ctor + ItemInfo() + { + cur_time = 0; + length = 0; + + cur_title = cur_chapter = 0; + real_cur_title = real_cur_chapter = 0; + real_cur_time = 0; + cur_changed = false; + cur_time_changed = false; + num_titles = num_chapters = 0; + width = height = 0; + frame_rate = 0; + clrs = PLAYER_COLOR_SPACE_UNKNOWN; + + dvd_menu_lang = "en"; + dvd_audio_lang = "en"; + spu_lang = "en"; + + audio_stream = spu_stream = 0; + + subtitle_charset = SUBTITLE_CHARSET_DEFAULT; + subtitle_wrap = 35; + } + +public: + SPString name; + SPString artist; + SPString audio_info; + SPString video_info; + + SPString subtitle; + SUBTITLE_CHARSET subtitle_charset; + int subtitle_wrap; + + int cur_time; + int length; + + int cur_title, cur_chapter; + int real_cur_title, real_cur_chapter, real_cur_time; + bool cur_changed, cur_time_changed; + int num_titles, num_chapters; + + int width, height; + int frame_rate; + // color space + PLAYER_COLOR_SPACE clrs; + + SPString dvd_menu_lang; + SPString dvd_audio_lang; + SPString spu_lang; // for dvd and video subtitles + int audio_stream; + int spu_stream; // for dvd and video subtitles +}; + +/// Current script parameters +class ScriptParams +{ +public: + /// ctor + ScriptParams() + { + errors = SCRIPT_ERRORS_ALL; + status = CDROM_STATUS_UNKNOWN; + require_next_status = CDROM_STATUS_UNKNOWN; + saved_iso_status = CDROM_STATUS_UNKNOWN; + key = 0; + + dvdplaying = FALSE; + cddaplaying = FALSE; + fileplaying = FALSE; + videoplaying = FALSE; + audioplaying = FALSE; + waitinfo = FALSE; + wasejected = FALSE; + + need_to_toggle_tray = FALSE; + + // screen: + pal_idx = 0; + hscale = 100; vscale = 100; rotate = 0; + hscroll = 0; vscroll = 0; + backleft = backtop = 0; backright = 719; backbottom = 479; + bigbackleft = bigbacktop = 0; bigbackright = 719; bigbackbottom = 479; + bigback_width = 0; bigback_height = 0; + need_setwindow = false; + + // explorer: + curitem = NULL; + curtarget = NULL; + lastitem = NULL; + iso_lang = "iso8859-1"; + iso_lang_changed = true; + filter[0] = ITEM_TYPE_UP; + filter[1] = ITEM_TYPE_FOLDER; + filter[2] = ITEM_TYPE_TRACK; + filter[3] = ITEM_TYPE_DVD; + filter[4] = ITEM_TYPE_FILE; + filter[5] = ITEM_TYPE_NONE; + sort = ITEMS_SORT_NONE; + mask[0] = "*.*"; + for (int i = 1; i < num_file_masks; i++) + mask[i] = ""; + allmask = mask[0]; + list_changed = false; + + // player: + player_type = PLAYER_TYPE_UNKNOWN; + player_do_command = false; + player_debug = false; + player_repeat = PLAYER_REPEAT_NONE; + speed = MPEG_SPEED_STOP; + + // flash: + flash_address = -1; + flash_progress = -1; + flash_p = -1; + + // timer support + curtime = old_curtime = start_sec = 0; + } + + /// dtor + ~ScriptParams() + { + lists.DeleteObjects(); + playlists.DeleteObjects(); + timed_objs.Delete(); + } + +public: + SCRIPT_ERRORS errors; + + SPString back, bigback; + SPString pal; + SPString font; + int pal_idx; + + SPString fw_ver, mmsl_ver; + + CDROM_STATUS status; + CDROM_STATUS require_next_status; + CDROM_STATUS saved_iso_status; + BOOL need_to_toggle_tray; + + int key; + SPString keystr; + + int hscale, vscale, hscroll, vscroll, rotate; + int backleft, backtop, backright, backbottom; + int bigbackleft, bigbacktop, bigbackright, bigbackbottom; + int bigback_width, bigback_height; + bool need_setwindow; + + BOOL dvdplaying, fileplaying, videoplaying, audioplaying, cddaplaying, waitinfo; + bool wasejected; + + // explorer + + SPString iso_lang; + bool iso_lang_changed; + + SPString folder, allmask, mask[num_file_masks]; + SPString target; + ITEM_TYPE filter[ITEM_TYPE_MAX]; + ITEMS_SORT sort; + + SPList lists; + SPList playlists; + ItemsList *curitem; + ItemsList *curtarget; + Item *lastitem; + bool list_changed; + + // player + + SPString player_command; + bool player_do_command; + + PLAYER_TYPE player_type; + SPString player_source, player_folder; + PLAYER_REPEAT player_repeat; + ItemInfo info; + SPString player_error; + bool player_debug; + // used for external players + MPEG_SPEED_TYPE speed; + + // flash: + SPString flash_file; + int flash_address; + int flash_progress, flash_p; + + /// timer support + SPDLinkedListAbstract timed_objs; + int curtime, old_curtime, start_sec; +}; + +extern ScriptParams *params; +extern Item *tmpit; + +void script_explorer_reset(); + +extern bool player_dvd_command(const SPString & command); +extern bool player_file_command(const SPString & command); +extern bool player_video_command(const SPString & command); +extern bool player_audio_command(const SPString & command); +extern bool player_cdda_command(const SPString & command); + +extern void player_do_command(); + +#endif // of SP_SCRIPT_INTERNAL_H diff --git a/src/script-objects.cpp b/src/script-objects.cpp new file mode 100644 index 0000000..3d0629e --- /dev/null +++ b/src/script-objects.cpp @@ -0,0 +1,534 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - script objects wrapper impl. + * \file script-objects.cpp + * \author bombur + * \version 0.1 + * \date 12.10.2006 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include +#include +#include + +#include + +void on_image_create(int obj_id, MMSL_OBJECT *obj, void *) +{ + if (obj_id == SCRIPT_OBJECT_IMAGE) + { + Image *img = new Image(); + img->timerobj = NULL; + gui.AddWindow(img); + *obj = (MMSL_OBJECT)img; + } +} + +void on_image_delete(int obj_id, MMSL_OBJECT *obj, void *) +{ + if (obj_id == SCRIPT_OBJECT_IMAGE) + { + Image *img = (Image *)*obj; + if (img->timerobj != NULL) + { + img->timerobj->obj = NULL; + params->timed_objs.Delete(img->timerobj); + img->timerobj = NULL; + } + if (img->updateobj != NULL) + { + img->updateobj->obj = NULL; + params->timed_objs.Delete(img->updateobj); + img->updateobj = NULL; + } + gui.RemoveWindow(img); + *obj = NULL; + } +} + +//////////////////////////////////////////////////////////////////////// + +void on_text_create(int obj_id, MMSL_OBJECT *obj, void *) +{ + if (obj_id == SCRIPT_OBJECT_TEXT) + { + Text *txt = new Text(); + txt->SetFont(params->font); + gui.AddWindow(txt); + *obj = (MMSL_OBJECT)txt; + } +} + +void on_text_delete(int obj_id, MMSL_OBJECT *obj, void *) +{ + if (obj_id == SCRIPT_OBJECT_TEXT) + { + Text *txt = (Text *)*obj; + if (txt->timerobj != NULL) + { + txt->timerobj->obj = NULL; + if (params != NULL) + params->timed_objs.Delete(txt->timerobj); + txt->timerobj = NULL; + } + gui.RemoveWindow(txt); + *obj = NULL; + } +} + +//////////////////////////////////////////////////////////////////////// + +void on_rect_create(int obj_id, MMSL_OBJECT *obj, void *) +{ + if (obj_id == SCRIPT_OBJECT_RECT) + { + Rectangle *rect = new Rectangle(); + gui.AddWindow(rect); + *obj = (MMSL_OBJECT)rect; + } +} + +void on_rect_delete(int obj_id, MMSL_OBJECT *obj, void *) +{ + if (obj_id == SCRIPT_OBJECT_RECT) + { + Rectangle *rect = (Rectangle *)*obj; + if (rect->timerobj != NULL) + { + rect->timerobj->obj = NULL; + params->timed_objs.Delete(rect->timerobj); + rect->timerobj = NULL; + } + gui.RemoveWindow(rect); + *obj = NULL; + } +} + +//////////////////////////////////////////////////////////////////////// +/// common vars for all dynamic objects +void on_common_get(int var_id, MmslVariable *var, void *, int , MMSL_OBJECT *obj) +{ + if (obj == NULL) + return; + Window *win = (Window *)*obj; + if (win == NULL || var == NULL) + return; + switch (var_id) + { + case SCRIPT_OBJECT_VAR_IMAGE_TYPE: + var->Set("image"); + break; + case SCRIPT_OBJECT_VAR_TEXT_TYPE: + var->Set("text"); + break; + case SCRIPT_OBJECT_VAR_RECT_TYPE: + var->Set("rect"); + break; + case SCRIPT_OBJECT_VAR_IMAGE_GROUP: + case SCRIPT_OBJECT_VAR_TEXT_GROUP: + case SCRIPT_OBJECT_VAR_RECT_GROUP: + var->Set(win->group != NULL ? *win->group : SPString()); + break; + case SCRIPT_OBJECT_VAR_IMAGE_X: + case SCRIPT_OBJECT_VAR_TEXT_X: + case SCRIPT_OBJECT_VAR_RECT_X: + var->Set(win->GetX()); + break; + case SCRIPT_OBJECT_VAR_IMAGE_Y: + case SCRIPT_OBJECT_VAR_TEXT_Y: + case SCRIPT_OBJECT_VAR_RECT_Y: + var->Set(win->GetY()); + break; + case SCRIPT_OBJECT_VAR_IMAGE_VISIBLE: + case SCRIPT_OBJECT_VAR_TEXT_VISIBLE: + case SCRIPT_OBJECT_VAR_RECT_VISIBLE: + var->Set(win->visible); + break; + case SCRIPT_OBJECT_VAR_IMAGE_WIDTH: + case SCRIPT_OBJECT_VAR_TEXT_WIDTH: + case SCRIPT_OBJECT_VAR_RECT_WIDTH: + var->Set(win->GetWidth()); + break; + case SCRIPT_OBJECT_VAR_IMAGE_HEIGHT: + case SCRIPT_OBJECT_VAR_TEXT_HEIGHT: + case SCRIPT_OBJECT_VAR_RECT_HEIGHT: + var->Set(win->GetHeight()); + break; + case SCRIPT_OBJECT_VAR_IMAGE_HALIGN: + case SCRIPT_OBJECT_VAR_TEXT_HALIGN: + case SCRIPT_OBJECT_VAR_RECT_HALIGN: + { + const char *ha[] = { "left", "center", "right" }; + var->Set(ha[win->halign]); + } + break; + case SCRIPT_OBJECT_VAR_IMAGE_VALIGN: + case SCRIPT_OBJECT_VAR_TEXT_VALIGN: + case SCRIPT_OBJECT_VAR_RECT_VALIGN: + { + const char *va[] = { "top", "center", "bottom" }; + var->Set(va[win->valign]); + } + break; + case SCRIPT_OBJECT_VAR_IMAGE_TIMER: + case SCRIPT_OBJECT_VAR_TEXT_TIMER: + case SCRIPT_OBJECT_VAR_RECT_TIMER: + var->Set(win->timer > 0 ? win->timer - params->curtime : win->timer); + break; + } +} + +void on_common_set(int var_id, MmslVariable *var, void *, int , MMSL_OBJECT *obj) +{ + if (obj == NULL) + return; + Window *win = (Window *)*obj; + if (win == NULL || var == NULL) + return; + switch (var_id) + { + case SCRIPT_OBJECT_VAR_IMAGE_GROUP: + case SCRIPT_OBJECT_VAR_TEXT_GROUP: + case SCRIPT_OBJECT_VAR_RECT_GROUP: + SPSafeDelete(win->group); + win->group = new SPString(var->GetString()); + break; + case SCRIPT_OBJECT_VAR_IMAGE_X: + case SCRIPT_OBJECT_VAR_TEXT_X: + case SCRIPT_OBJECT_VAR_RECT_X: + win->SetX(var->GetInteger()); + break; + case SCRIPT_OBJECT_VAR_IMAGE_Y: + case SCRIPT_OBJECT_VAR_TEXT_Y: + case SCRIPT_OBJECT_VAR_RECT_Y: + win->SetY(var->GetInteger()); + break; + case SCRIPT_OBJECT_VAR_IMAGE_VISIBLE: + case SCRIPT_OBJECT_VAR_TEXT_VISIBLE: + case SCRIPT_OBJECT_VAR_RECT_VISIBLE: + win->SetVisible(var->GetInteger() != 0); + break; + case SCRIPT_OBJECT_VAR_IMAGE_WIDTH: + case SCRIPT_OBJECT_VAR_TEXT_WIDTH: + case SCRIPT_OBJECT_VAR_RECT_WIDTH: + win->SetWidth(var->GetInteger()); + break; + case SCRIPT_OBJECT_VAR_IMAGE_HEIGHT: + case SCRIPT_OBJECT_VAR_TEXT_HEIGHT: + case SCRIPT_OBJECT_VAR_RECT_HEIGHT: + win->SetHeight(var->GetInteger()); + break; + case SCRIPT_OBJECT_VAR_IMAGE_HALIGN: + case SCRIPT_OBJECT_VAR_TEXT_HALIGN: + case SCRIPT_OBJECT_VAR_RECT_HALIGN: + { + SPString str = var->GetString(); + if (str.CompareNoCase("center") == 0) + win->SetHAlign(WINDOW_ALIGN_CENTER); + else if (str.CompareNoCase("right") == 0) + win->SetHAlign(WINDOW_ALIGN_RIGHT); + else + win->SetHAlign(WINDOW_ALIGN_LEFT); + } + break; + case SCRIPT_OBJECT_VAR_IMAGE_VALIGN: + case SCRIPT_OBJECT_VAR_TEXT_VALIGN: + case SCRIPT_OBJECT_VAR_RECT_VALIGN: + { + SPString str = var->GetString(); + if (str.CompareNoCase("center") == 0) + win->SetVAlign(WINDOW_ALIGN_CENTER); + else if (str.CompareNoCase("bottom") == 0) + win->SetVAlign(WINDOW_ALIGN_BOTTOM); + else + win->SetVAlign(WINDOW_ALIGN_TOP); + } + break; + case SCRIPT_OBJECT_VAR_IMAGE_TIMER: + case SCRIPT_OBJECT_VAR_TEXT_TIMER: + case SCRIPT_OBJECT_VAR_RECT_TIMER: + { + int delta = var->GetInteger(); + if (delta > 0) + { + ScriptTimerObject *to = new ScriptTimerObject(); + to->type = SCRIPT_OBJECT_TIMER; + to->obj = win; + to->var_ID = var_id; + win->timer = params->curtime + delta; + if (win->timerobj != NULL) + params->timed_objs.Delete(win->timerobj); + win->timerobj = to; + params->timed_objs.Add(to); + } else + win->timer = delta; + } + break; + } +} + +//////////////////////////////////////////////////////////////////////// + +void on_image_get(int var_id, MmslVariable *var, void *param, int obj_id, MMSL_OBJECT *obj) +{ + if (var == NULL || obj_id != SCRIPT_OBJECT_IMAGE || obj == NULL) + return; + + Image *img = (Image *)*obj; + if (img == NULL) + return; + + on_common_get(var_id, var, param, obj_id, obj); + + switch (var_id) + { + case SCRIPT_OBJECT_VAR_IMAGE_SRC: + var->Set(img->img != NULL ? img->img->name : 0); + break; + case SCRIPT_OBJECT_VAR_IMAGE_HFLIP: + var->Set(img->flipx); + break; + case SCRIPT_OBJECT_VAR_IMAGE_VFLIP: + var->Set(img->flipy); + break; + } +} + +void on_image_set(int var_id, MmslVariable *var, void *param, int obj_id, MMSL_OBJECT *obj) +{ + if (var == NULL || obj_id != SCRIPT_OBJECT_IMAGE || obj == NULL) + return; + + Image *img = (Image *)*obj; + if (img == NULL) + return; + + on_common_set(var_id, var, param, obj_id, obj); + + switch (var_id) + { + case SCRIPT_OBJECT_VAR_IMAGE_SRC: + { + img->SetSource(var->GetString()); + // add to update queue + ScriptTimerObject *to = img->updateobj; + if (img->img != NULL && img->img->num_frames > 1) + { + if (to == NULL) + { + to = new ScriptTimerObject(); + to->obj = img; + to->var_ID = var_id; + to->type = SCRIPT_OBJECT_UPDATE; + img->updateobj = to; + params->timed_objs.Add(to); + } + } + else if (to != NULL) + { + params->timed_objs.Delete(to); + img->updateobj = NULL; + } + mmsl->UpdateObjectVariable(img, SCRIPT_OBJECT_VAR_IMAGE_WIDTH); + mmsl->UpdateObjectVariable(img, SCRIPT_OBJECT_VAR_IMAGE_HEIGHT); + } + break; + case SCRIPT_OBJECT_VAR_IMAGE_HFLIP: + img->SetFlipX(var->GetInteger() != 0); + break; + case SCRIPT_OBJECT_VAR_IMAGE_VFLIP: + img->SetFlipY(var->GetInteger() != 0); + break; + } +} + +//////////////////////////////////////////////////////////////////////// + +void on_text_get(int var_id, MmslVariable *var, void *param, int obj_id, MMSL_OBJECT *obj) +{ + if (var == NULL || obj_id != SCRIPT_OBJECT_TEXT || obj == NULL) + return; + + Text *text = (Text *)*obj; + if (text == NULL) + return; + + on_common_get(var_id, var, param, obj_id, obj); + + switch (var_id) + { + case SCRIPT_OBJECT_VAR_TEXT_COLOR: + var->Set(text->color); + break; + case SCRIPT_OBJECT_VAR_TEXT_BACKCOLOR: + var->Set(text->color); + break; + case SCRIPT_OBJECT_VAR_TEXT_FONT: + var->Set(text->font != NULL ? text->font->name : ""); + break; + case SCRIPT_OBJECT_VAR_TEXT_VALUE: + var->Set(text->text); + break; + case SCRIPT_OBJECT_VAR_TEXT_COUNT: + var->Set(text->text.GetLength()); + break; + case SCRIPT_OBJECT_VAR_TEXT_TEXTALIGN: + { + const char *ta[] = { "left", "center", "right" }; + var->Set(ta[text->text_align]); + } + break; + case SCRIPT_OBJECT_VAR_TEXT_DELETE: + var->Set(0); + break; + case SCRIPT_OBJECT_VAR_TEXT_STYLE: + { + const char *sty[] = { "", "underline", "outline" }; + var->Set(sty[text->style]); + } + break; + } +} + +void on_text_set(int var_id, MmslVariable *var, void *param, int obj_id, MMSL_OBJECT *obj) +{ + if (var == NULL || obj_id != SCRIPT_OBJECT_TEXT || obj == NULL) + return; + + Text *text = (Text *)*obj; + if (text == NULL) + return; + + on_common_set(var_id, var, param, obj_id, obj); + + switch (var_id) + { + case SCRIPT_OBJECT_VAR_TEXT_COLOR: + text->SetColor(var->GetInteger()); + break; + case SCRIPT_OBJECT_VAR_TEXT_BACKCOLOR: + text->SetBkColor(var->GetInteger()); + break; + case SCRIPT_OBJECT_VAR_TEXT_FONT: + text->SetFont(var->GetString()); + mmsl->UpdateObjectVariable(text, SCRIPT_OBJECT_VAR_TEXT_WIDTH); + mmsl->UpdateObjectVariable(text, SCRIPT_OBJECT_VAR_TEXT_HEIGHT); + break; + case SCRIPT_OBJECT_VAR_TEXT_VALUE: + text->SetText(var->GetString()); + mmsl->UpdateObjectVariable(text, SCRIPT_OBJECT_VAR_TEXT_WIDTH); + mmsl->UpdateObjectVariable(text, SCRIPT_OBJECT_VAR_TEXT_HEIGHT); + break; + case SCRIPT_OBJECT_VAR_TEXT_COUNT: + text->SetText(text->text.Left(var->GetInteger())); + mmsl->UpdateObjectVariable(text, SCRIPT_OBJECT_VAR_TEXT_WIDTH); + mmsl->UpdateObjectVariable(text, SCRIPT_OBJECT_VAR_TEXT_HEIGHT); + break; + case SCRIPT_OBJECT_VAR_TEXT_DELETE: + text->SetText(text->text.Mid(var->GetInteger())); + mmsl->UpdateObjectVariable(text, SCRIPT_OBJECT_VAR_TEXT_WIDTH); + mmsl->UpdateObjectVariable(text, SCRIPT_OBJECT_VAR_TEXT_HEIGHT); + break; + case SCRIPT_OBJECT_VAR_TEXT_STYLE: + { + SPString str = var->GetString(); + if (str.CompareNoCase("underline") == 0) + text->SetStyle(TEXT_STYLE_UNDERLINE); + else if (str.CompareNoCase("outline") == 0) + text->SetStyle(TEXT_STYLE_OUTLINE); + else + text->SetStyle(TEXT_STYLE_NORMAL); + } + mmsl->UpdateObjectVariable(text, SCRIPT_OBJECT_VAR_TEXT_WIDTH); + mmsl->UpdateObjectVariable(text, SCRIPT_OBJECT_VAR_TEXT_HEIGHT); + break; + case SCRIPT_OBJECT_VAR_TEXT_TEXTALIGN: + { + SPString str = var->GetString(); + if (str.CompareNoCase("center") == 0) + text->SetTextAlign(TEXT_ALIGN_CENTER); + else if (str.CompareNoCase("right") == 0) + text->SetTextAlign(TEXT_ALIGN_RIGHT); + else + text->SetTextAlign(TEXT_ALIGN_LEFT); + } + } +} + +//////////////////////////////////////////////////////////////////////// + +void on_rect_get(int var_id, MmslVariable *var, void *param, int obj_id, MMSL_OBJECT *obj) +{ + if (var == NULL || obj_id != SCRIPT_OBJECT_RECT || obj == NULL) + return; + + Rectangle *rect = (Rectangle *)*obj; + if (rect == NULL) + return; + + on_common_get(var_id, var, param, obj_id, obj); + + switch (var_id) + { + case SCRIPT_OBJECT_VAR_RECT_COLOR: + var->Set(rect->color); + break; + case SCRIPT_OBJECT_VAR_RECT_BACKCOLOR: + var->Set(rect->bkcolor); + break; + case SCRIPT_OBJECT_VAR_RECT_LINEWIDTH: + var->Set(rect->linewidth); + break; + case SCRIPT_OBJECT_VAR_RECT_ROUND: + var->Set(rect->round); + break; + } +} + +void on_rect_set(int var_id, MmslVariable *var, void *param, int obj_id, MMSL_OBJECT *obj) +{ + if (var == NULL || obj_id != SCRIPT_OBJECT_RECT || obj == NULL) + return; + + Rectangle *rect = (Rectangle *)*obj; + if (rect == NULL) + return; + + on_common_set(var_id, var, param, obj_id, obj); + + switch (var_id) + { + case SCRIPT_OBJECT_VAR_RECT_COLOR: + rect->SetColor(var->GetInteger()); + break; + case SCRIPT_OBJECT_VAR_RECT_BACKCOLOR: + rect->SetBkColor(var->GetInteger()); + break; + case SCRIPT_OBJECT_VAR_RECT_LINEWIDTH: + rect->SetLineWidth(var->GetInteger()); + break; + case SCRIPT_OBJECT_VAR_RECT_ROUND: + rect->SetRound(var->GetInteger()); + break; + } +} + diff --git a/src/script-objs.h b/src/script-objs.h new file mode 100644 index 0000000..267ae90 --- /dev/null +++ b/src/script-objs.h @@ -0,0 +1,84 @@ +////////////////////////////////////////////////////// +// SigmaPlayer Project. +// File auto-generated from MMSL language reference. +////////////////////////////////////////////////////// + +// callbacks: +void on_image_create(int obj_id, MMSL_OBJECT *obj, void *param); +void on_image_delete(int obj_id, MMSL_OBJECT *obj, void *param); + +void on_text_create(int obj_id, MMSL_OBJECT *obj, void *param); +void on_text_delete(int obj_id, MMSL_OBJECT *obj, void *param); + +void on_rect_create(int obj_id, MMSL_OBJECT *obj, void *param); +void on_rect_delete(int obj_id, MMSL_OBJECT *obj, void *param); + + +enum +{ + SCRIPT_OBJECT_IMAGE, + SCRIPT_OBJECT_TEXT, + SCRIPT_OBJECT_RECT, +}; + +void on_image_get(int var_id, MmslVariable *var, void *param, int obj_id = -1, MMSL_OBJECT *obj = NULL); +void on_image_set(int var_id, MmslVariable *var, void *param, int obj_id = -1, MMSL_OBJECT *obj = NULL); + +void on_text_get(int var_id, MmslVariable *var, void *param, int obj_id = -1, MMSL_OBJECT *obj = NULL); +void on_text_set(int var_id, MmslVariable *var, void *param, int obj_id = -1, MMSL_OBJECT *obj = NULL); + +void on_rect_get(int var_id, MmslVariable *var, void *param, int obj_id = -1, MMSL_OBJECT *obj = NULL); +void on_rect_set(int var_id, MmslVariable *var, void *param, int obj_id = -1, MMSL_OBJECT *obj = NULL); + + +enum SCRIPT_OBJECT_VARS +{ + SCRIPT_OBJECT_VAR_IMAGE_TYPE, + SCRIPT_OBJECT_VAR_IMAGE_GROUP, + SCRIPT_OBJECT_VAR_IMAGE_SRC, + SCRIPT_OBJECT_VAR_IMAGE_X, + SCRIPT_OBJECT_VAR_IMAGE_Y, + SCRIPT_OBJECT_VAR_IMAGE_VISIBLE, + SCRIPT_OBJECT_VAR_IMAGE_WIDTH, + SCRIPT_OBJECT_VAR_IMAGE_HEIGHT, + SCRIPT_OBJECT_VAR_IMAGE_HFLIP, + SCRIPT_OBJECT_VAR_IMAGE_VFLIP, + SCRIPT_OBJECT_VAR_IMAGE_HALIGN, + SCRIPT_OBJECT_VAR_IMAGE_VALIGN, + SCRIPT_OBJECT_VAR_IMAGE_TIMER, + + SCRIPT_OBJECT_VAR_TEXT_TYPE, + SCRIPT_OBJECT_VAR_TEXT_GROUP, + SCRIPT_OBJECT_VAR_TEXT_X, + SCRIPT_OBJECT_VAR_TEXT_Y, + SCRIPT_OBJECT_VAR_TEXT_VISIBLE, + SCRIPT_OBJECT_VAR_TEXT_WIDTH, + SCRIPT_OBJECT_VAR_TEXT_HEIGHT, + SCRIPT_OBJECT_VAR_TEXT_HALIGN, + SCRIPT_OBJECT_VAR_TEXT_VALIGN, + SCRIPT_OBJECT_VAR_TEXT_COLOR, + SCRIPT_OBJECT_VAR_TEXT_BACKCOLOR, + SCRIPT_OBJECT_VAR_TEXT_VALUE, + SCRIPT_OBJECT_VAR_TEXT_COUNT, + SCRIPT_OBJECT_VAR_TEXT_DELETE, + SCRIPT_OBJECT_VAR_TEXT_FONT, + SCRIPT_OBJECT_VAR_TEXT_TEXTALIGN, + SCRIPT_OBJECT_VAR_TEXT_STYLE, + SCRIPT_OBJECT_VAR_TEXT_TIMER, + + SCRIPT_OBJECT_VAR_RECT_TYPE, + SCRIPT_OBJECT_VAR_RECT_GROUP, + SCRIPT_OBJECT_VAR_RECT_X, + SCRIPT_OBJECT_VAR_RECT_Y, + SCRIPT_OBJECT_VAR_RECT_VISIBLE, + SCRIPT_OBJECT_VAR_RECT_WIDTH, + SCRIPT_OBJECT_VAR_RECT_HEIGHT, + SCRIPT_OBJECT_VAR_RECT_LINEWIDTH, + SCRIPT_OBJECT_VAR_RECT_COLOR, + SCRIPT_OBJECT_VAR_RECT_BACKCOLOR, + SCRIPT_OBJECT_VAR_RECT_HALIGN, + SCRIPT_OBJECT_VAR_RECT_VALIGN, + SCRIPT_OBJECT_VAR_RECT_ROUND, + SCRIPT_OBJECT_VAR_RECT_TIMER, +}; + diff --git a/src/script-objs.inc.c b/src/script-objs.inc.c new file mode 100644 index 0000000..364d6ec --- /dev/null +++ b/src/script-objs.inc.c @@ -0,0 +1,74 @@ +////////////////////////////////////////////////////// +// SigmaPlayer Project. +// File auto-generated from MMSL language reference. +////////////////////////////////////////////////////// + +static const struct SCRIPT_OBJECT +{ + MmslObjectCallback Create, Delete; + int ID; +} script_objects[] = +{ + { on_image_create, on_image_delete, SCRIPT_OBJECT_IMAGE }, + { on_text_create, on_text_delete, SCRIPT_OBJECT_TEXT }, + { on_rect_create, on_rect_delete, SCRIPT_OBJECT_RECT }, + { NULL, NULL, -1 } +}; + +static const struct SCRIPT_OBJECT_VAR +{ + MmslVariableCallback Get, Set; + int obj_ID; + int ID; +} script_object_vars[] = +{ + { on_image_get, on_image_set, SCRIPT_OBJECT_IMAGE, SCRIPT_OBJECT_VAR_IMAGE_TYPE }, + { on_image_get, on_image_set, SCRIPT_OBJECT_IMAGE, SCRIPT_OBJECT_VAR_IMAGE_GROUP }, + { on_image_get, on_image_set, SCRIPT_OBJECT_IMAGE, SCRIPT_OBJECT_VAR_IMAGE_SRC }, + { on_image_get, on_image_set, SCRIPT_OBJECT_IMAGE, SCRIPT_OBJECT_VAR_IMAGE_X }, + { on_image_get, on_image_set, SCRIPT_OBJECT_IMAGE, SCRIPT_OBJECT_VAR_IMAGE_Y }, + { on_image_get, on_image_set, SCRIPT_OBJECT_IMAGE, SCRIPT_OBJECT_VAR_IMAGE_VISIBLE }, + { on_image_get, on_image_set, SCRIPT_OBJECT_IMAGE, SCRIPT_OBJECT_VAR_IMAGE_WIDTH }, + { on_image_get, on_image_set, SCRIPT_OBJECT_IMAGE, SCRIPT_OBJECT_VAR_IMAGE_HEIGHT }, + { on_image_get, on_image_set, SCRIPT_OBJECT_IMAGE, SCRIPT_OBJECT_VAR_IMAGE_HFLIP }, + { on_image_get, on_image_set, SCRIPT_OBJECT_IMAGE, SCRIPT_OBJECT_VAR_IMAGE_VFLIP }, + { on_image_get, on_image_set, SCRIPT_OBJECT_IMAGE, SCRIPT_OBJECT_VAR_IMAGE_HALIGN }, + { on_image_get, on_image_set, SCRIPT_OBJECT_IMAGE, SCRIPT_OBJECT_VAR_IMAGE_VALIGN }, + { on_image_get, on_image_set, SCRIPT_OBJECT_IMAGE, SCRIPT_OBJECT_VAR_IMAGE_TIMER }, + + { on_text_get, on_text_set, SCRIPT_OBJECT_TEXT, SCRIPT_OBJECT_VAR_TEXT_TYPE }, + { on_text_get, on_text_set, SCRIPT_OBJECT_TEXT, SCRIPT_OBJECT_VAR_TEXT_GROUP }, + { on_text_get, on_text_set, SCRIPT_OBJECT_TEXT, SCRIPT_OBJECT_VAR_TEXT_X }, + { on_text_get, on_text_set, SCRIPT_OBJECT_TEXT, SCRIPT_OBJECT_VAR_TEXT_Y }, + { on_text_get, on_text_set, SCRIPT_OBJECT_TEXT, SCRIPT_OBJECT_VAR_TEXT_VISIBLE }, + { on_text_get, on_text_set, SCRIPT_OBJECT_TEXT, SCRIPT_OBJECT_VAR_TEXT_WIDTH }, + { on_text_get, on_text_set, SCRIPT_OBJECT_TEXT, SCRIPT_OBJECT_VAR_TEXT_HEIGHT }, + { on_text_get, on_text_set, SCRIPT_OBJECT_TEXT, SCRIPT_OBJECT_VAR_TEXT_HALIGN }, + { on_text_get, on_text_set, SCRIPT_OBJECT_TEXT, SCRIPT_OBJECT_VAR_TEXT_VALIGN }, + { on_text_get, on_text_set, SCRIPT_OBJECT_TEXT, SCRIPT_OBJECT_VAR_TEXT_COLOR }, + { on_text_get, on_text_set, SCRIPT_OBJECT_TEXT, SCRIPT_OBJECT_VAR_TEXT_BACKCOLOR }, + { on_text_get, on_text_set, SCRIPT_OBJECT_TEXT, SCRIPT_OBJECT_VAR_TEXT_VALUE }, + { on_text_get, on_text_set, SCRIPT_OBJECT_TEXT, SCRIPT_OBJECT_VAR_TEXT_COUNT }, + { on_text_get, on_text_set, SCRIPT_OBJECT_TEXT, SCRIPT_OBJECT_VAR_TEXT_DELETE }, + { on_text_get, on_text_set, SCRIPT_OBJECT_TEXT, SCRIPT_OBJECT_VAR_TEXT_FONT }, + { on_text_get, on_text_set, SCRIPT_OBJECT_TEXT, SCRIPT_OBJECT_VAR_TEXT_TEXTALIGN }, + { on_text_get, on_text_set, SCRIPT_OBJECT_TEXT, SCRIPT_OBJECT_VAR_TEXT_STYLE }, + { on_text_get, on_text_set, SCRIPT_OBJECT_TEXT, SCRIPT_OBJECT_VAR_TEXT_TIMER }, + + { on_rect_get, on_rect_set, SCRIPT_OBJECT_RECT, SCRIPT_OBJECT_VAR_RECT_TYPE }, + { on_rect_get, on_rect_set, SCRIPT_OBJECT_RECT, SCRIPT_OBJECT_VAR_RECT_GROUP }, + { on_rect_get, on_rect_set, SCRIPT_OBJECT_RECT, SCRIPT_OBJECT_VAR_RECT_X }, + { on_rect_get, on_rect_set, SCRIPT_OBJECT_RECT, SCRIPT_OBJECT_VAR_RECT_Y }, + { on_rect_get, on_rect_set, SCRIPT_OBJECT_RECT, SCRIPT_OBJECT_VAR_RECT_VISIBLE }, + { on_rect_get, on_rect_set, SCRIPT_OBJECT_RECT, SCRIPT_OBJECT_VAR_RECT_WIDTH }, + { on_rect_get, on_rect_set, SCRIPT_OBJECT_RECT, SCRIPT_OBJECT_VAR_RECT_HEIGHT }, + { on_rect_get, on_rect_set, SCRIPT_OBJECT_RECT, SCRIPT_OBJECT_VAR_RECT_LINEWIDTH }, + { on_rect_get, on_rect_set, SCRIPT_OBJECT_RECT, SCRIPT_OBJECT_VAR_RECT_COLOR }, + { on_rect_get, on_rect_set, SCRIPT_OBJECT_RECT, SCRIPT_OBJECT_VAR_RECT_BACKCOLOR }, + { on_rect_get, on_rect_set, SCRIPT_OBJECT_RECT, SCRIPT_OBJECT_VAR_RECT_HALIGN }, + { on_rect_get, on_rect_set, SCRIPT_OBJECT_RECT, SCRIPT_OBJECT_VAR_RECT_VALIGN }, + { on_rect_get, on_rect_set, SCRIPT_OBJECT_RECT, SCRIPT_OBJECT_VAR_RECT_ROUND }, + { on_rect_get, on_rect_set, SCRIPT_OBJECT_RECT, SCRIPT_OBJECT_VAR_RECT_TIMER }, + { NULL, NULL, -1, -1, } +}; + diff --git a/src/script-player.cpp b/src/script-player.cpp new file mode 100644 index 0000000..1a1e110 --- /dev/null +++ b/src/script-player.cpp @@ -0,0 +1,1375 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - media player wrapper impl. + * \file script-player.cpp + * \author bombur + * \version 0.1 + * \date 12.10.2006 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + + +static const StringPair player_repeat_pairs[] = +{ + { "none", PLAYER_REPEAT_NONE }, + { "selection", PLAYER_REPEAT_SELECTION }, + { "track", PLAYER_REPEAT_TRACK }, + { "all", PLAYER_REPEAT_ALL }, + { "random", PLAYER_REPEAT_RANDOM }, + { NULL, -1 }, +}; + +static const StringPair player_menu_pairs[] = +{ + { "0", DVD_MENU_ESCAPE }, + { "1", DVD_MENU_ROOT }, + { "escape", DVD_MENU_ESCAPE }, + { "title", DVD_MENU_TITLE }, + { "root", DVD_MENU_ROOT }, + { "subtitle", DVD_MENU_SUBTITLE }, + { "audio", DVD_MENU_AUDIO }, + { "angle", DVD_MENU_ANGLE }, + { "chapters", DVD_MENU_CHAPTERS }, + { NULL, -1 }, +}; + +static const StringPair player_clrs_pairs[] = +{ + { "", PLAYER_COLOR_SPACE_UNKNOWN }, + { "YCrCb", PLAYER_COLOR_SPACE_YCRCB }, + { "Grayscale", PLAYER_COLOR_SPACE_GRAYSCALE }, + { NULL, -1 }, +}; + +static const StringPair player_charset_pairs[] = +{ + { "", SUBTITLE_CHARSET_DEFAULT }, + { "cp1251", SUBTITLE_CHARSET_CP1251 }, + { "iso8859-1", SUBTITLE_CHARSET_ISO8859_1 }, + { "iso8859-2", SUBTITLE_CHARSET_ISO8859_2 }, + { "koi8-r", SUBTITLE_CHARSET_KOI8R }, + { NULL, -1 }, +}; + +static const StringPair player_speed_pairs[] = +{ + { "0", MPEG_SPEED_PAUSE, }, + { "4", MPEG_SPEED_FWD_4X, }, + { "8", MPEG_SPEED_FWD_8X, }, + { "16", MPEG_SPEED_FWD_16X, }, + { "32", MPEG_SPEED_FWD_32X, }, + { "48", MPEG_SPEED_FWD_48X, }, + { "100", MPEG_SPEED_FWD_MAX, }, + { "-4", MPEG_SPEED_REV_4X, }, + { "-8", MPEG_SPEED_REV_8X, }, + { "-16", MPEG_SPEED_REV_16X, }, + { "-32", MPEG_SPEED_REV_32X, }, + { "-48", MPEG_SPEED_REV_48X, }, + { "-100", MPEG_SPEED_REV_MAX, }, + { "1/2", MPEG_SPEED_SLOW_FWD_2X, }, + { "1/4", MPEG_SPEED_SLOW_FWD_4X, }, + { "1/8", MPEG_SPEED_SLOW_FWD_8X, }, + { "-1/2", MPEG_SPEED_SLOW_REV_2X, }, + { "-1/4", MPEG_SPEED_SLOW_REV_4X, }, + { "-1/8", MPEG_SPEED_SLOW_REV_8X, }, + { NULL, -1 }, +}; + +//////////////////////////////////////////////// + +int get_volume() +{ + DWORD lvol = 0, rvol = 0; + khwl_getproperty(KHWL_AUDIO_SET, eaVolumeLeft, sizeof(lvol), &lvol); + khwl_getproperty(KHWL_AUDIO_SET, eaVolumeRight, sizeof(rvol), &rvol); + return MAX(lvol, rvol); +} + +int get_balance() +{ + DWORD lvol = 0, rvol = 0; + khwl_getproperty(KHWL_AUDIO_SET, eaVolumeLeft, sizeof(lvol), &lvol); + khwl_getproperty(KHWL_AUDIO_SET, eaVolumeRight, sizeof(rvol), &rvol); + int volume = MAX(lvol, rvol); + if (volume == 0) + return 0; + int balance = 100 * MIN(lvol, rvol) / volume - 100; + return (lvol < rvol) ? balance : -balance; +} + +int get_audio_offset() +{ + int audio_offs; + //khwl_getproperty(KHWL_COMMON_SET, eDoAudioLater, sizeof(audio_offs), &audio_offs); + audio_offs = (int)(video_get_audio_offset() / INT64(90)); + return audio_offs; +} + + +void set_volume(int vol) +{ + if (vol < 0) + vol = 0; + if (vol > 100) + vol = 100; + int balance = get_balance(); + int lvol = balance <= 0 ? vol : (100-vol) * balance / 100; + int rvol = balance >= 0 ? vol : (vol-100) * balance / 100; + khwl_setproperty(KHWL_AUDIO_SET, eaVolumeLeft, sizeof(lvol), &lvol); + khwl_setproperty(KHWL_AUDIO_SET, eaVolumeRight, sizeof(rvol), &rvol); + msg("Volume set to (%d,%d).\n", lvol, rvol); +} + +void set_balance(int balance) +{ + if (balance < -100) + balance = -100; + if (balance > 100) + balance = 100; + int vol = get_volume(); + int lvol = balance <= 0 ? vol : (100-vol) * balance / 100; + int rvol = balance >= 0 ? vol : (vol-100) * balance / 100; + khwl_setproperty(KHWL_AUDIO_SET, eaVolumeLeft, sizeof(lvol), &lvol); + khwl_setproperty(KHWL_AUDIO_SET, eaVolumeRight, sizeof(rvol), &rvol); + msg("Volume set to (%d,%d).\n", lvol, rvol); +} + +void set_audio_offset(int audio_offs) +{ + if (audio_offs < -5000) + audio_offs = -5000; + if (audio_offs > 5000) + audio_offs = 5000; + audio_offs *= 90; + //khwl_setproperty(KHWL_COMMON_SET, eDoAudioLater, sizeof(audio_offs), &audio_offs); + video_set_audio_offset(audio_offs); + msg("Audio offset set to %d PTS.\n", audio_offs); +} + +/////////////////////////////////////////////////////////// + +bool player_dvd_command(const SPString & command) +{ + if (command.CompareNoCase("play") == 0) + { + params->player_error = ""; + + if (module_load("dvd") < 0) + return false; + + mmsl->UpdateVariable(SCRIPT_VAR_PLAYER_ERROR); + + gui_update(); + + if (params->player_source.FindNoCase("/dvd") == 0 && + params->status != CDROM_STATUS_HAS_DVD) + { + module_unload("dvd"); + return false; + } + + if (params->dvdplaying) + { + // if user changed time + if (params->info.cur_time_changed) + { + params->info.cur_time_changed = false; + dvd_seek(params->info.cur_time); + return true; + } + // if user changed title or chapter: + if (params->info.cur_changed) + { + params->info.cur_changed = false; + if (!dvd_seek_titlepart(params->info.cur_title, params->info.cur_chapter)) + { + dvd_get_cur(¶ms->info.cur_title, ¶ms->info.cur_chapter); + params->info.num_chapters = dvd_getnumchapters(params->info.cur_title); + } + return true; + } + + // restore normal play + dvd_button_play(); + mmsl->UpdateVariable(SCRIPT_VAR_PLAYER_SPEED); + return true; + } + // first time - start DVD... + dvd_setdeflang_menu(params->info.dvd_menu_lang); + dvd_setdeflang_audio(params->info.dvd_audio_lang); + dvd_setdeflang_spu(params->info.spu_lang); + + const char *source; + bool play_from_drive = true; + if (params->player_source.FindNoCase("/dvd") != 0 && + params->player_source.FindNoCase("/cdrom/video_ts/") != 0) + { + source = cdrom_getdevicepath(*params->player_folder); + play_from_drive = false; + } else + source = cdrom_getdevicepath(NULL); + int ret = dvd_play(source, play_from_drive); + if (ret < 0) + { + // Failure - try DVD disc as ISO... + params->require_next_status = CDROM_STATUS_HAS_ISO; + return true; + } + + // all went OK! + params->dvdplaying = TRUE; + params->info.num_titles = dvd_getnumtitles(); + mmsl->UpdateVariable(SCRIPT_VAR_PLAYER_PLAYING); + mmsl->UpdateVariable(SCRIPT_VAR_PLAYER_SPEED); + return true; + } + else if (command.CompareNoCase("stop") == 0) + { + if (params->dvdplaying) + { + dvd_stop(); + params->dvdplaying = FALSE; + if (mmsl != NULL) + { + mmsl->UpdateVariable(SCRIPT_VAR_PLAYER_SPEED); + mmsl->UpdateVariable(SCRIPT_VAR_PLAYER_PLAYING); + } + module_unload("dvd"); + return true; + } + } + else if (command.CompareNoCase("cancel") == 0) + { + if (params->dvdplaying) + { + params->info.cur_chapter = params->info.real_cur_chapter; + params->info.cur_title = params->info.real_cur_title; + params->info.cur_time = params->info.real_cur_time; + params->info.cur_changed = false; + params->info.cur_time_changed = false; + return true; + } + } + else if (params->dvdplaying) + { + return dvd_do_command(command); + } + return false; +} + +#ifdef EXTERNAL_PLAYER +bool player_file_command(const SPString & command) +{ + if (command.CompareNoCase("play") == 0) + { + params->player_error = ""; + mmsl->UpdateVariable(SCRIPT_VAR_PLAYER_ERROR); + gui_update(); + + if (params->status != CDROM_STATUS_HAS_ISO && params->status != CDROM_STATUS_HAS_MIXED) + return false; + if (params->fileplaying) + { + // if user changed time + if (params->info.cur_time_changed) + { + params->info.cur_time_changed = false; + player_seek(params->info.cur_time); + params->speed = MPEG_SPEED_NORMAL; + mmsl->UpdateVariable(SCRIPT_VAR_PLAYER_SPEED); + return true; + } + + // restore normal play + params->speed = MPEG_SPEED_NORMAL; + player_play(); + mmsl->UpdateVariable(SCRIPT_VAR_PLAYER_SPEED); + return true; + } + params->fileplaying = FALSE; + if (player_play(params->player_source) >= 0) + { + // all went OK! + params->fileplaying = TRUE; + params->speed = MPEG_SPEED_NORMAL; + } + mmsl->UpdateVariable(SCRIPT_VAR_PLAYER_PLAYING); + mmsl->UpdateVariable(SCRIPT_VAR_PLAYER_SPEED); + return true; + } + else if (command.CompareNoCase("info") == 0) + { + params->waitinfo = TRUE; + player_startinfo(params->player_source, params->iso_lang); + } + else if (command.CompareNoCase("stop") == 0) + { + if (params->fileplaying) + { + player_stop(); + params->fileplaying = FALSE; + params->speed = MPEG_SPEED_STOP; + if (mmsl != NULL) + { + mmsl->UpdateVariable(SCRIPT_VAR_PLAYER_SPEED); + mmsl->UpdateVariable(SCRIPT_VAR_PLAYER_PLAYING); + } + return true; + } + } + else if (command.CompareNoCase("pause") == 0) + { + if (params->fileplaying) + { + player_pause(); + params->speed = MPEG_SPEED_PAUSE; + mmsl->UpdateVariable(SCRIPT_VAR_PLAYER_SPEED); + return true; + } + } + else if (command.CompareNoCase("forward") == 0) + { + if (params->fileplaying) + { + if (player_forward() == 1) + { + params->speed = MPEG_SPEED_FWD_4X; + } else + params->speed = MPEG_SPEED_NORMAL; + mmsl->UpdateVariable(SCRIPT_VAR_PLAYER_SPEED); + return true; + } + } + else if (command.CompareNoCase("rewind") == 0) + { + if (params->fileplaying) + { + if (player_rewind() == 1) + { + params->speed = MPEG_SPEED_REV_4X; + } else + params->speed = MPEG_SPEED_NORMAL; + mmsl->UpdateVariable(SCRIPT_VAR_PLAYER_SPEED); + return true; + } + } + return false; +} +#endif + +#ifdef INTERNAL_VIDEO_PLAYER +bool player_video_command(const SPString & command) +{ + if (command.CompareNoCase("play") == 0) + { + params->player_error = ""; + mmsl->UpdateVariable(SCRIPT_VAR_PLAYER_ERROR); + gui_update(); + + if (params->status != CDROM_STATUS_HAS_ISO && params->status != CDROM_STATUS_HAS_MIXED) + return false; + if (params->videoplaying) + { + // if user changed time + if (params->info.cur_time_changed) + { + params->info.cur_time_changed = false; + video_seek(params->info.cur_time); + //return true; + } + + // restore normal play + params->speed = MPEG_SPEED_NORMAL; + video_play(); + mmsl->UpdateVariable(SCRIPT_VAR_PLAYER_SPEED); + return true; + } + params->videoplaying = FALSE; + if (video_play(params->player_source) >= 0) + { + // all went OK! + params->videoplaying = TRUE; + params->speed = MPEG_SPEED_NORMAL; + } + mmsl->UpdateVariable(SCRIPT_VAR_PLAYER_PLAYING); + mmsl->UpdateVariable(SCRIPT_VAR_PLAYER_SPEED); + return true; + } + else if (command.CompareNoCase("stop") == 0) + { + if (params->videoplaying) + { + if (video_stop()) + { + params->videoplaying = FALSE; + params->speed = MPEG_SPEED_STOP; + if (mmsl != NULL) + { + mmsl->UpdateVariable(SCRIPT_VAR_PLAYER_SPEED); + mmsl->UpdateVariable(SCRIPT_VAR_PLAYER_PLAYING); + } + return true; + } + return false; + } + } + else if (command.CompareNoCase("pause") == 0) + { + if (params->videoplaying) + { + video_pause(); + params->speed = MPEG_SPEED_PAUSE; + mmsl->UpdateVariable(SCRIPT_VAR_PLAYER_SPEED); + return true; + } + } + else if (command.CompareNoCase("forward") == 0 || command.CompareNoCase("rewind") == 0) + { + if (params->videoplaying) + { + bool fwd = command.CompareNoCase("forward") == 0; + if (params->speed == MPEG_SPEED_FWD_4X) + params->speed = fwd ? MPEG_SPEED_FWD_MAX : MPEG_SPEED_REV_4X; + else if (params->speed == MPEG_SPEED_REV_4X) + params->speed = fwd ? MPEG_SPEED_FWD_4X : MPEG_SPEED_REV_MAX; + else if (params->speed == MPEG_SPEED_FWD_MAX) + params->speed = fwd ? MPEG_SPEED_FWD_MAX : MPEG_SPEED_FWD_4X; + else if (params->speed == MPEG_SPEED_REV_MAX) + params->speed = fwd ? MPEG_SPEED_REV_4X : MPEG_SPEED_REV_MAX; + else if (params->speed == MPEG_SPEED_NORMAL) + params->speed = fwd ? MPEG_SPEED_FWD_4X : MPEG_SPEED_REV_4X; + + if (params->speed & MPEG_SPEED_FWD_MASK) + { + if (video_forward(params->speed == MPEG_SPEED_FWD_MAX) != 1) + params->speed = MPEG_SPEED_NORMAL; + } else + { + if (video_rewind(params->speed == MPEG_SPEED_REV_MAX) != 1) + params->speed = MPEG_SPEED_NORMAL; + } + mmsl->UpdateVariable(SCRIPT_VAR_PLAYER_SPEED); + return true; + } + } + else if (command.CompareNoCase("audio") == 0) + { + if (params->videoplaying) + { + video_set_audio_track(-1); + mmsl->UpdateVariable(SCRIPT_VAR_PLAYER_LANGUAGE_AUDIO); + mmsl->UpdateVariable(SCRIPT_VAR_PLAYER_AUDIO_STREAM); + return true; + } + } + else if (command.CompareNoCase("subtitle") == 0) + { + if (params->videoplaying) + { + if (!subtitle_next()) + script_error_callback(SCRIPT_ERROR_INVALID); + mmsl->UpdateVariable(SCRIPT_VAR_PLAYER_LANGUAGE_SUBTITLE); + mmsl->UpdateVariable(SCRIPT_VAR_PLAYER_SUBTITLE_STREAM); + return true; + } else + script_error_callback(SCRIPT_ERROR_INVALID); + return true; + } + else if (command.CompareNoCase("angle") == 0) + { + script_error_callback(SCRIPT_ERROR_INVALID); + return true; + } + return false; +} +#endif + +#ifdef INTERNAL_AUDIO_PLAYER +bool player_audio_command(const SPString & command) +{ + if (command.CompareNoCase("play") == 0) + { + params->player_error = ""; + mmsl->UpdateVariable(SCRIPT_VAR_PLAYER_ERROR); + gui_update(); + + if (params->status != CDROM_STATUS_HAS_ISO && params->status != CDROM_STATUS_HAS_MIXED) + return false; + if (params->audioplaying) + { + // if user changed time + if (params->info.cur_time_changed) + { + params->info.cur_time_changed = false; + audio_seek(params->info.cur_time, SEEK_SET); + params->speed = MPEG_SPEED_NORMAL; + mmsl->UpdateVariable(SCRIPT_VAR_PLAYER_SPEED); + return true; + } + + // restore normal play + params->speed = MPEG_SPEED_NORMAL; + audio_play(); + mmsl->UpdateVariable(SCRIPT_VAR_PLAYER_SPEED); + return true; + } + params->audioplaying = FALSE; + if (audio_play(params->player_source, params->player_type == PLAYER_TYPE_FOLDER) >= 0) + { + // all went OK! + params->audioplaying = TRUE; + params->speed = MPEG_SPEED_NORMAL; + } + params->player_type = PLAYER_TYPE_AUDIO; + mmsl->UpdateVariable(SCRIPT_VAR_PLAYER_PLAYING); + mmsl->UpdateVariable(SCRIPT_VAR_PLAYER_SPEED); + return true; + } + else if (command.CompareNoCase("info") == 0) + { + params->waitinfo = TRUE; + if (params->player_type != PLAYER_TYPE_FOLDER) + player_startinfo(params->player_source, params->iso_lang); + } + else if (command.CompareNoCase("stop") == 0) + { + if (params->audioplaying) + { + audio_delete_filelist(); + audio_stop(); + params->audioplaying = FALSE; + params->speed = MPEG_SPEED_STOP; + if (mmsl != NULL) + { + mmsl->UpdateVariable(SCRIPT_VAR_PLAYER_SPEED); + mmsl->UpdateVariable(SCRIPT_VAR_PLAYER_PLAYING); + } + return true; + } + } + else if (command.CompareNoCase("next") == 0) + { + if (params->audioplaying) + { + if (audio_stop()) + { + audio_delete_filelist(); + + params->audioplaying = FALSE; + params->speed = MPEG_SPEED_STOP; + if (mmsl != NULL) + { + mmsl->UpdateVariable(SCRIPT_VAR_PLAYER_SPEED); + mmsl->UpdateVariable(SCRIPT_VAR_PLAYER_PLAYING); + } + } + return true; + } + } + else if (command.CompareNoCase("prev") == 0) + { + if (params->audioplaying) + { + if (audio_stop(FALSE)) // 'prev' + { + audio_delete_filelist(); + + params->audioplaying = FALSE; + params->speed = MPEG_SPEED_STOP; + if (mmsl != NULL) + { + mmsl->UpdateVariable(SCRIPT_VAR_PLAYER_SPEED); + mmsl->UpdateVariable(SCRIPT_VAR_PLAYER_PLAYING); + } + } + return true; + } + } + else if (command.CompareNoCase("pause") == 0) + { + if (params->audioplaying) + { + audio_pause(); + params->speed = MPEG_SPEED_PAUSE; + mmsl->UpdateVariable(SCRIPT_VAR_PLAYER_SPEED); + return true; + } + } + else if (command.CompareNoCase("forward") == 0) + { + if (params->audioplaying) + { + if (audio_forward() == 1) + { + params->speed = MPEG_SPEED_FWD_4X; + } else + params->speed = MPEG_SPEED_NORMAL; + mmsl->UpdateVariable(SCRIPT_VAR_PLAYER_SPEED); + return true; + } + } + else if (command.CompareNoCase("rewind") == 0) + { + if (params->audioplaying) + { + if (audio_rewind() == 1) + { + params->speed = MPEG_SPEED_REV_4X; + } else + params->speed = MPEG_SPEED_NORMAL; + mmsl->UpdateVariable(SCRIPT_VAR_PLAYER_SPEED); + return true; + } + } + return false; +} +#endif + +bool player_cdda_command(const SPString & command) +{ + if (command.CompareNoCase("play") == 0) + { + params->player_error = ""; + mmsl->UpdateVariable(SCRIPT_VAR_PLAYER_ERROR); + gui_update(); + + if (params->status != CDROM_STATUS_HAS_AUDIO && params->status != CDROM_STATUS_HAS_MIXED) + return false; + if (params->cddaplaying) + { + // if user changed time + if (params->info.cur_time_changed) + { + params->info.cur_time_changed = false; + cdda_seek(params->info.cur_time); + //return true; + } + + // restore normal play + params->speed = MPEG_SPEED_NORMAL; + cdda_play(NULL); + mmsl->UpdateVariable(SCRIPT_VAR_PLAYER_SPEED); + return true; + } + params->cddaplaying = FALSE; + if (cdda_play(params->player_source) >= 0) + { + // all went OK! + params->cddaplaying = TRUE; + params->speed = MPEG_SPEED_NORMAL; + } + mmsl->UpdateVariable(SCRIPT_VAR_PLAYER_PLAYING); + mmsl->UpdateVariable(SCRIPT_VAR_PLAYER_SPEED); + return true; + } + else if (command.CompareNoCase("info") == 0) + { + params->info.length = cdda_get_length(params->player_source); + params->info.num_titles = cdda_get_numtracks(); + mmsl->UpdateVariable(SCRIPT_VAR_PLAYER_LENGTH); + mmsl->UpdateVariable(SCRIPT_VAR_PLAYER_NUM_TITLES); + } + else if (command.CompareNoCase("stop") == 0) + { + if (params->cddaplaying) + { + cdda_stop(); + params->cddaplaying = FALSE; + params->speed = MPEG_SPEED_STOP; + if (mmsl != NULL) + { + mmsl->UpdateVariable(SCRIPT_VAR_PLAYER_SPEED); + mmsl->UpdateVariable(SCRIPT_VAR_PLAYER_PLAYING); + } + return true; + } + } + else if (command.CompareNoCase("pause") == 0) + { + if (params->cddaplaying) + { + cdda_pause(); + params->speed = MPEG_SPEED_PAUSE; + mmsl->UpdateVariable(SCRIPT_VAR_PLAYER_SPEED); + return true; + } + } + else if (command.CompareNoCase("forward") == 0) + { + if (params->cddaplaying) + { + cdda_forward(); + params->speed = MPEG_SPEED_NORMAL; + mmsl->UpdateVariable(SCRIPT_VAR_PLAYER_SPEED); + return true; + } + } + else if (command.CompareNoCase("rewind") == 0) + { + if (params->cddaplaying) + { + cdda_rewind(); + params->speed = MPEG_SPEED_NORMAL; + mmsl->UpdateVariable(SCRIPT_VAR_PLAYER_SPEED); + return true; + } + } + return false; +} + + +bool is_internal_playing() +{ + return params->dvdplaying == TRUE || params->cddaplaying == TRUE + || params->videoplaying == TRUE || params->audioplaying == TRUE; +} + +bool is_playing() +{ + return params->dvdplaying == TRUE || params->fileplaying == TRUE + || params->videoplaying == TRUE || params->audioplaying == TRUE + || params->cddaplaying == TRUE; +} + +void player_update_source(char *filepath) +{ + params->player_source = filepath; + mmsl->UpdateVariable(SCRIPT_VAR_PLAYER_SOURCE); +} + +void player_do_command() +{ + SPString & cmd = params->player_command; + + if (params->player_type == PLAYER_TYPE_DVD) + { + player_dvd_command(cmd); + } +#ifdef EXTERNAL_PLAYER + else if (params->player_type == PLAYER_TYPE_FILE) + { + if (!cdrom_ismounted()) + cdrom_mount(params->iso_lang, FALSE); + player_file_command(cmd); + } +#else + else if (params->player_type == PLAYER_TYPE_FILE) + { + if (cmd.CompareNoCase("info") == 0) + { + params->waitinfo = TRUE; + player_startinfo(params->player_source, params->iso_lang); + } + } +#endif +#ifdef INTERNAL_VIDEO_PLAYER + else if (params->player_type == PLAYER_TYPE_VIDEO) + { + if (!cdrom_ismounted()) + cdrom_mount(params->iso_lang, FALSE); + player_video_command(cmd); + } +#endif +#ifdef INTERNAL_AUDIO_PLAYER + else if (params->player_type == PLAYER_TYPE_AUDIO || + params->player_type == PLAYER_TYPE_FOLDER) + { + if (!cdrom_ismounted()) + cdrom_mount(params->iso_lang, FALSE); + player_audio_command(cmd); + } +#endif + else if (params->player_type == PLAYER_TYPE_AUDIOCD) + { + player_cdda_command(cmd); + } + else + { + // undo playing + if (cmd == "play") + { + mmsl->UpdateVariable(SCRIPT_VAR_PLAYER_PLAYING); + } + } +} + + +//////////////////////////////////////////////////////////////////////// + +void on_player_get(int var_id, MmslVariable *var, void *, int , MMSL_OBJECT *) +{ + if (var == NULL) + return; + switch (var_id) + { + case SCRIPT_VAR_PLAYER_SOURCE: + var->Set(params->player_source); + break; + case SCRIPT_VAR_PLAYER_COMMAND: + case SCRIPT_VAR_PLAYER_SELECT: + var->Set(""); + break; + case SCRIPT_VAR_PLAYER_PLAYING: + { + if (params->player_type == PLAYER_TYPE_DVD) + var->Set(params->dvdplaying); + else if (params->player_type == PLAYER_TYPE_FILE) + var->Set(params->fileplaying); + else if (params->player_type == PLAYER_TYPE_VIDEO) + var->Set(params->videoplaying); + else if (params->player_type == PLAYER_TYPE_AUDIO) + var->Set(params->audioplaying); + else if (params->player_type == PLAYER_TYPE_AUDIOCD) + var->Set(params->cddaplaying); + else + var->Set(0); + } + break; + case SCRIPT_VAR_PLAYER_SAVED: + if (params->player_type == PLAYER_TYPE_DVD) + { + if (module_load("dvd") < 0) + { + var->Set(0); + return; + } + var->Set(dvd_get_saved() ? 1 : 0); + } + else + var->Set(0); + break; + case SCRIPT_VAR_PLAYER_REPEAT: + StringPair::Set(var, player_repeat_pairs, params->player_repeat); + break; + case SCRIPT_VAR_PLAYER_SPEED: + { + MPEG_SPEED_TYPE speed = MPEG_SPEED_STOP; + if (params->player_type == PLAYER_TYPE_DVD) + { + if (!params->dvdplaying) + speed = MPEG_SPEED_STOP; + else + speed = dvd_getspeed(); + } + else if (params->player_type == PLAYER_TYPE_FILE) + speed = params->speed; + else if (params->player_type == PLAYER_TYPE_VIDEO) + speed = params->speed; + else if (params->player_type == PLAYER_TYPE_AUDIO) + speed = params->speed; + else if (params->player_type == PLAYER_TYPE_AUDIOCD) + speed = params->speed; + switch (speed) + { + case MPEG_SPEED_NORMAL: + var->Set(1); break; + case MPEG_SPEED_PAUSE: + case MPEG_SPEED_STEP: + case MPEG_SPEED_STOP: + var->Set(0); break; + case MPEG_SPEED_FWD_4X: + var->Set(4); break; + case MPEG_SPEED_FWD_8X: + var->Set(8); break; + case MPEG_SPEED_FWD_16X: + var->Set(16); break; + case MPEG_SPEED_FWD_32X: + var->Set(32); break; + case MPEG_SPEED_FWD_48X: + var->Set(48); break; + case MPEG_SPEED_FWD_MAX: + var->Set(100); break; + case MPEG_SPEED_REV_4X: + var->Set(-4); break; + case MPEG_SPEED_REV_8X: + var->Set(-8); break; + case MPEG_SPEED_REV_16X: + var->Set(-16); break; + case MPEG_SPEED_REV_32X: + var->Set(-32); break; + case MPEG_SPEED_REV_48X: + var->Set(-48); break; + case MPEG_SPEED_REV_MAX: + var->Set(-100); break; + case MPEG_SPEED_SLOW_FWD_2X: + var->Set("1/2"); break; + case MPEG_SPEED_SLOW_FWD_4X: + var->Set("1/4"); break; + case MPEG_SPEED_SLOW_FWD_8X: + var->Set("1/8"); break; + case MPEG_SPEED_SLOW_REV_2X: + var->Set("-1/2"); break; + case MPEG_SPEED_SLOW_REV_4X: + var->Set("-1/4"); break; + case MPEG_SPEED_SLOW_REV_8X: + var->Set("-1/8"); break; + default: + var->Set(0); break; + } + } + break; + case SCRIPT_VAR_PLAYER_ANGLE: + if (params->player_type == PLAYER_TYPE_DVD) + var->Set(dvd_getangle()); + else + var->Set(0); + break; + case SCRIPT_VAR_PLAYER_MENU: + if (params->player_type == PLAYER_TYPE_DVD) + { + if (module_load("dvd") < 0) + { + var->Set(0); + return; + } + var->Set(dvd_ismenu() ? 1 : 0); + } + else + var->Set(0); + break; + case SCRIPT_VAR_PLAYER_LANGUAGE_MENU: + if (params->player_type == PLAYER_TYPE_DVD) + var->Set(params->info.dvd_menu_lang); + else + var->Set(""); + break; + case SCRIPT_VAR_PLAYER_LANGUAGE_AUDIO: + if (params->player_type == PLAYER_TYPE_DVD) + var->Set(params->info.dvd_audio_lang); + else + var->Set(""); + break; + case SCRIPT_VAR_PLAYER_LANGUAGE_SUBTITLE: + if (params->player_type == PLAYER_TYPE_DVD || params->player_type == PLAYER_TYPE_VIDEO) + var->Set(params->info.spu_lang); + else + var->Set(""); + break; + case SCRIPT_VAR_PLAYER_SUBTITLE: + if (params->player_type == PLAYER_TYPE_VIDEO) + var->Set(params->info.subtitle); + else + var->Set(""); + break; + case SCRIPT_VAR_PLAYER_SUBTITLE_CHARSET: + StringPair::Set(var, player_charset_pairs, params->info.subtitle_charset); + break; + case SCRIPT_VAR_PLAYER_SUBTITLE_WRAP: + var->Set(params->info.subtitle_wrap); + break; + case SCRIPT_VAR_PLAYER_VOLUME: + var->Set(get_volume()); + break; + case SCRIPT_VAR_PLAYER_BALANCE: + var->Set(get_balance()); + break; + case SCRIPT_VAR_PLAYER_AUDIO_OFFSET: + var->Set(get_audio_offset()); + break; + case SCRIPT_VAR_PLAYER_TIME: + var->Set(params->info.cur_time); + break; + case SCRIPT_VAR_PLAYER_TITLE: + var->Set(params->info.cur_title); + break; + case SCRIPT_VAR_PLAYER_CHAPTER: + var->Set(params->info.cur_chapter); + break; + case SCRIPT_VAR_PLAYER_NUM_TITLES: + var->Set(params->info.num_titles); + break; + case SCRIPT_VAR_PLAYER_NUM_CHAPTERS: + var->Set(params->info.num_chapters); + break; + case SCRIPT_VAR_PLAYER_NAME: + var->Set(params->info.name); + break; + case SCRIPT_VAR_PLAYER_ARTIST: + var->Set(params->info.artist); + break; + case SCRIPT_VAR_PLAYER_AUDIO_INFO: + var->Set(params->info.audio_info); + break; + case SCRIPT_VAR_PLAYER_VIDEO_INFO: + var->Set(params->info.video_info); + break; + case SCRIPT_VAR_PLAYER_AUDIO_STREAM: + var->Set(params->info.audio_stream); + break; + case SCRIPT_VAR_PLAYER_SUBTITLE_STREAM: + var->Set(params->info.spu_stream); + break; + case SCRIPT_VAR_PLAYER_LENGTH: + var->Set(params->info.length); + break; + case SCRIPT_VAR_PLAYER_WIDTH: + var->Set(params->info.width); + break; + case SCRIPT_VAR_PLAYER_HEIGHT: + var->Set(params->info.height); + break; + case SCRIPT_VAR_PLAYER_FRAME_RATE: + var->Set(params->info.frame_rate); + break; + case SCRIPT_VAR_PLAYER_COLOR_SPACE: + StringPair::Set(var, player_clrs_pairs, params->info.clrs); + break; + case SCRIPT_VAR_PLAYER_DEBUG: + if (params->player_type == PLAYER_TYPE_DVD) + var->Set(dvd_getdebug()); +#ifdef EXTERNAL_PLAYER + else if (params->player_type == PLAYER_TYPE_FILE) + var->Set(player_getdebug()); +#endif +#ifdef INTERNAL_VIDEO_PLAYER + else if (params->player_type == PLAYER_TYPE_VIDEO) + var->Set(video_getdebug()); +#endif +#ifdef INTERNAL_AUDIO_PLAYER + else if (params->player_type == PLAYER_TYPE_AUDIO) + var->Set(audio_getdebug()); +#endif + else if (params->player_type == PLAYER_TYPE_AUDIOCD) + var->Set(cdda_getdebug()); + else + var->Set(0); + break; + case SCRIPT_VAR_PLAYER_ERROR: + var->Set(params->player_error); + break; + } +} + +void on_player_set(int var_id, MmslVariable *var, void *, int , MMSL_OBJECT *) +{ + if (var == NULL) + return; + + params->player_error = ""; + + switch (var_id) + { + case SCRIPT_VAR_PLAYER_SOURCE: + { + params->player_source = var->GetString(); + params->player_source.Replace('\\', '/'); + if (params->player_source.Find('/') != 0) + params->player_source = "/" + params->player_source; + params->player_folder = params->player_source; + int rf = params->player_folder.ReverseFind('/'); + if (rf > 0) + params->player_folder = params->player_folder.Left(rf); + // detect media type + params->player_type = PLAYER_TYPE_UNKNOWN; + if (params->player_source.FindNoCase("/dvd") == 0) + { + params->player_type = PLAYER_TYPE_DVD; + } + else if (params->player_source.FindNoCase("/cdrom") == 0 || + params->player_source.FindNoCase("/hdd") == 0) + { + // test for folder + struct stat64 statbuf; + if (cdrom_stat(params->player_source, &statbuf) >= 0 + && S_ISDIR(statbuf.st_mode)) + { + params->player_type = PLAYER_TYPE_FOLDER; + } + else if (params->player_source.FindNoCase(".cda") > 0) + params->player_type = PLAYER_TYPE_AUDIOCD; + else if (params->player_source.FindNoCase(".ifo") > 0 || + params->player_source.FindNoCase(".bup") > 0) + params->player_type = PLAYER_TYPE_DVD; +#ifdef INTERNAL_VIDEO_PLAYER + else if (params->player_source.FindNoCase(".avi") > 0 + || params->player_source.FindNoCase(".divx") > 0 + || params->player_source.FindNoCase(".mp4") > 0 + || params->player_source.FindNoCase(".3gp") > 0 + || params->player_source.FindNoCase(".mov") > 0 +#ifdef INTERNAL_VIDEO_MPEG_PLAYER + || params->player_source.FindNoCase(".mpg") > 0 + || params->player_source.FindNoCase(".m1v") > 0 + || params->player_source.FindNoCase(".m2v") > 0 + || params->player_source.FindNoCase(".mpeg") > 0 + || params->player_source.FindNoCase(".vob") > 0 + || params->player_source.FindNoCase(".dat") > 0 +#endif + ) + params->player_type = PLAYER_TYPE_VIDEO; +#endif +#ifdef INTERNAL_AUDIO_PLAYER + else if (params->player_source.FindNoCase(".mp3") > 0 + || params->player_source.FindNoCase(".mp2") > 0 + || params->player_source.FindNoCase(".mp1") > 0 + || params->player_source.FindNoCase(".mpa") > 0 + || params->player_source.FindNoCase(".ac3") > 0 + || params->player_source.FindNoCase(".wav") > 0 + || params->player_source.FindNoCase(".ogg") > 0) + params->player_type = PLAYER_TYPE_AUDIO; +#endif + else + params->player_type = PLAYER_TYPE_FILE; + } + + params->info.name = ""; + params->info.artist = ""; + params->info.width = 0; + params->info.height = 0; + params->info.frame_rate = 0; + params->info.clrs = PLAYER_COLOR_SPACE_UNKNOWN; + params->info.length = 0; + + mmsl->UpdateVariable(SCRIPT_VAR_PLAYER_NAME); + mmsl->UpdateVariable(SCRIPT_VAR_PLAYER_ARTIST); + mmsl->UpdateVariable(SCRIPT_VAR_PLAYER_WIDTH); + mmsl->UpdateVariable(SCRIPT_VAR_PLAYER_HEIGHT); + mmsl->UpdateVariable(SCRIPT_VAR_PLAYER_FRAME_RATE); + mmsl->UpdateVariable(SCRIPT_VAR_PLAYER_COLOR_SPACE); + mmsl->UpdateVariable(SCRIPT_VAR_PLAYER_LENGTH); + + } + break; + case SCRIPT_VAR_PLAYER_COMMAND: + params->player_command = var->GetString(); + //if (params->player_type == PLAYER_TYPE_DVD || params->player_command.CompareNoCase("stop") == 0) + player_do_command(); + /*else + params->player_do_command = true; + */ + break; + case SCRIPT_VAR_PLAYER_PLAYING: + { + switch (var->GetInteger()) + { + case 0: + if (params->player_type == PLAYER_TYPE_DVD) + player_dvd_command("stop"); +#ifdef EXTERNAL_PLAYER + else if (params->player_type == PLAYER_TYPE_FILE) + player_file_command("stop"); +#endif +#ifdef INTERNAL_VIDEO_PLAYER + else if (params->player_type == PLAYER_TYPE_VIDEO) + player_video_command("stop"); +#endif +#ifdef INTERNAL_AUDIO_PLAYER + else if (params->player_type == PLAYER_TYPE_AUDIO) + player_audio_command("stop"); +#endif + else if (params->player_type == PLAYER_TYPE_AUDIOCD) + player_cdda_command("stop"); + break; + case 1: + if (params->player_type == PLAYER_TYPE_DVD) + player_dvd_command("play"); +#ifdef EXTERNAL_PLAYER + else if (params->player_type == PLAYER_TYPE_FILE) + player_file_command("play"); +#endif +#ifdef INTERNAL_VIDEO_PLAYER + else if (params->player_type == PLAYER_TYPE_VIDEO) + player_video_command("play"); +#endif +#ifdef INTERNAL_AUDIO_PLAYER + else if (params->player_type == PLAYER_TYPE_AUDIO) + player_audio_command("play"); +#endif + else if (params->player_type == PLAYER_TYPE_AUDIOCD) + player_cdda_command("play"); + break; + default: + ; + } + } + break; + case SCRIPT_VAR_PLAYER_SELECT: + break; + case SCRIPT_VAR_PLAYER_REPEAT: + params->player_repeat = (PLAYER_REPEAT)StringPair::Get(var, player_repeat_pairs, PLAYER_REPEAT_NONE); + break; + case SCRIPT_VAR_PLAYER_SPEED: + if (params->player_type == PLAYER_TYPE_DVD) + { + MPEG_SPEED_TYPE speed = (MPEG_SPEED_TYPE)StringPair::Get(var, player_speed_pairs, MPEG_SPEED_NORMAL); + dvd_setspeed(speed); + } + break; + case SCRIPT_VAR_PLAYER_ANGLE: + if (params->player_type == PLAYER_TYPE_DVD) + { + dvd_button_angle(var->GetInteger()); + } + break; + case SCRIPT_VAR_PLAYER_MENU: + if (params->player_type == PLAYER_TYPE_DVD) + { + DVD_MENU_TYPE menu = (DVD_MENU_TYPE)StringPair::Get(var, player_menu_pairs, DVD_MENU_DEFAULT); + dvd_button_menu(menu); + } + break; + case SCRIPT_VAR_PLAYER_LANGUAGE_MENU: + params->info.dvd_menu_lang = var->GetString(); + break; + case SCRIPT_VAR_PLAYER_LANGUAGE_AUDIO: + if (params->player_type == PLAYER_TYPE_DVD) + { + params->info.dvd_audio_lang = var->GetString(); + if (params->dvdplaying) + dvd_button_audio(params->info.dvd_audio_lang); + } + break; + case SCRIPT_VAR_PLAYER_LANGUAGE_SUBTITLE: + if (params->player_type == PLAYER_TYPE_DVD) // only for DVDs + { + params->info.spu_lang = var->GetString(); + if (params->dvdplaying) + dvd_button_subtitle(params->info.spu_lang); + } + break; + case SCRIPT_VAR_PLAYER_SUBTITLE_CHARSET: + params->info.subtitle_charset = (SUBTITLE_CHARSET)StringPair::Get(var, player_charset_pairs, SUBTITLE_CHARSET_DEFAULT); + break; + case SCRIPT_VAR_PLAYER_SUBTITLE_WRAP: + params->info.subtitle_wrap = MAX(0, var->GetInteger()); + break; + case SCRIPT_VAR_PLAYER_VOLUME: + set_volume(var->GetInteger()); + break; + case SCRIPT_VAR_PLAYER_BALANCE: + set_balance(var->GetInteger()); + break; + case SCRIPT_VAR_PLAYER_AUDIO_OFFSET: + set_audio_offset(var->GetInteger()); + break; + case SCRIPT_VAR_PLAYER_TIME: + if (params->player_type == PLAYER_TYPE_DVD) + { + params->info.cur_time = var->GetInteger(); + params->info.cur_time_changed = true; + dvd_get_chapter_for_time(params->info.cur_title, params->info.cur_time, + ¶ms->info.cur_chapter); + mmsl->UpdateVariable(SCRIPT_VAR_PLAYER_CHAPTER); + } + else if (params->player_type == PLAYER_TYPE_FILE) + { + params->info.cur_time = var->GetInteger(); + params->info.cur_time_changed = true; + } + else if (params->player_type == PLAYER_TYPE_VIDEO) + { + params->info.cur_time = var->GetInteger(); + params->info.cur_time_changed = true; + } + else if (params->player_type == PLAYER_TYPE_AUDIO) + { + params->info.cur_time = var->GetInteger(); + params->info.cur_time_changed = true; + } + else if (params->player_type == PLAYER_TYPE_AUDIOCD) + { + params->info.cur_time = var->GetInteger(); + params->info.cur_time_changed = true; + } + break; + case SCRIPT_VAR_PLAYER_CHAPTER: + if (params->player_type == PLAYER_TYPE_DVD) + { + int ch = var->GetInteger(); + if (ch > 0) + { + msg("Player chapter set to %d.\n", ch); + params->info.cur_chapter = ch; + params->info.cur_changed = true; + dvd_get_total_time(params->info.cur_title, params->info.cur_chapter, + ¶ms->info.cur_time); + mmsl->UpdateVariable(SCRIPT_VAR_PLAYER_TIME); + } + } + break; + case SCRIPT_VAR_PLAYER_TITLE: + if (params->player_type == PLAYER_TYPE_DVD) + { + int titl = var->GetInteger(); + if (titl > 0) + { + msg("Player title set to %d.\n", titl); + params->info.cur_title = titl; + params->info.cur_chapter = 1; + params->info.cur_changed = true; + params->info.num_chapters = dvd_getnumchapters(params->info.cur_title); + mmsl->UpdateVariable(SCRIPT_VAR_PLAYER_NUM_CHAPTERS); + mmsl->UpdateVariable(SCRIPT_VAR_PLAYER_CHAPTER); + dvd_get_total_time(params->info.cur_title, params->info.cur_chapter, + ¶ms->info.cur_time); + mmsl->UpdateVariable(SCRIPT_VAR_PLAYER_TIME); + } + } + break; + case SCRIPT_VAR_PLAYER_DEBUG: + if (params->player_type == PLAYER_TYPE_DVD) + { + dvd_setdebug(var->GetInteger()); + } +#ifdef EXTERNAL_PLAYER + else if (params->player_type == PLAYER_TYPE_FILE) + { + player_setdebug(var->GetInteger()); + } +#endif + else if (params->player_type == PLAYER_TYPE_VIDEO) + { +#ifdef INTERNAL_VIDEO_PLAYER + video_setdebug(var->GetInteger()); +#endif + } + else if (params->player_type == PLAYER_TYPE_AUDIO) + { +#ifdef INTERNAL_AUDIO_PLAYER + audio_setdebug(var->GetInteger()); +#endif + } + else if (params->player_type == PLAYER_TYPE_AUDIOCD) + { + cdda_setdebug(var->GetInteger()); + } + break; + } +} diff --git a/src/script-pobjs.inc.c b/src/script-pobjs.inc.c new file mode 100644 index 0000000..f064988 --- /dev/null +++ b/src/script-pobjs.inc.c @@ -0,0 +1,76 @@ +////////////////////////////////////////////////////// +// SigmaPlayer Project. +// File auto-generated from MMSL language reference. +////////////////////////////////////////////////////// + +static const struct SCRIPT_OBJECT +{ + MmslObjectCallback Create, Delete; + int ID; + const char *name; +} script_objects[] = +{ + { on_image_create, on_image_delete, SCRIPT_OBJECT_IMAGE, "image" }, + { on_text_create, on_text_delete, SCRIPT_OBJECT_TEXT, "text" }, + { on_rect_create, on_rect_delete, SCRIPT_OBJECT_RECT, "rect" }, + { NULL, NULL, -1, NULL } +}; + +static const struct SCRIPT_OBJECT_VAR +{ + MmslVariableCallback Get, Set; + int obj_ID; + int ID; + const char *name; +} script_object_vars[] = +{ + { on_image_get, on_image_set, SCRIPT_OBJECT_IMAGE, SCRIPT_OBJECT_VAR_IMAGE_TYPE, ".type" }, + { on_image_get, on_image_set, SCRIPT_OBJECT_IMAGE, SCRIPT_OBJECT_VAR_IMAGE_GROUP, ".group" }, + { on_image_get, on_image_set, SCRIPT_OBJECT_IMAGE, SCRIPT_OBJECT_VAR_IMAGE_SRC, ".src" }, + { on_image_get, on_image_set, SCRIPT_OBJECT_IMAGE, SCRIPT_OBJECT_VAR_IMAGE_X, ".x" }, + { on_image_get, on_image_set, SCRIPT_OBJECT_IMAGE, SCRIPT_OBJECT_VAR_IMAGE_Y, ".y" }, + { on_image_get, on_image_set, SCRIPT_OBJECT_IMAGE, SCRIPT_OBJECT_VAR_IMAGE_VISIBLE, ".visible" }, + { on_image_get, on_image_set, SCRIPT_OBJECT_IMAGE, SCRIPT_OBJECT_VAR_IMAGE_WIDTH, ".width" }, + { on_image_get, on_image_set, SCRIPT_OBJECT_IMAGE, SCRIPT_OBJECT_VAR_IMAGE_HEIGHT, ".height" }, + { on_image_get, on_image_set, SCRIPT_OBJECT_IMAGE, SCRIPT_OBJECT_VAR_IMAGE_HFLIP, ".hflip" }, + { on_image_get, on_image_set, SCRIPT_OBJECT_IMAGE, SCRIPT_OBJECT_VAR_IMAGE_VFLIP, ".vflip" }, + { on_image_get, on_image_set, SCRIPT_OBJECT_IMAGE, SCRIPT_OBJECT_VAR_IMAGE_HALIGN, ".halign" }, + { on_image_get, on_image_set, SCRIPT_OBJECT_IMAGE, SCRIPT_OBJECT_VAR_IMAGE_VALIGN, ".valign" }, + { on_image_get, on_image_set, SCRIPT_OBJECT_IMAGE, SCRIPT_OBJECT_VAR_IMAGE_TIMER, ".timer" }, + + { on_text_get, on_text_set, SCRIPT_OBJECT_TEXT, SCRIPT_OBJECT_VAR_TEXT_TYPE, ".type" }, + { on_text_get, on_text_set, SCRIPT_OBJECT_TEXT, SCRIPT_OBJECT_VAR_TEXT_GROUP, ".group" }, + { on_text_get, on_text_set, SCRIPT_OBJECT_TEXT, SCRIPT_OBJECT_VAR_TEXT_X, ".x" }, + { on_text_get, on_text_set, SCRIPT_OBJECT_TEXT, SCRIPT_OBJECT_VAR_TEXT_Y, ".y" }, + { on_text_get, on_text_set, SCRIPT_OBJECT_TEXT, SCRIPT_OBJECT_VAR_TEXT_VISIBLE, ".visible" }, + { on_text_get, on_text_set, SCRIPT_OBJECT_TEXT, SCRIPT_OBJECT_VAR_TEXT_WIDTH, ".width" }, + { on_text_get, on_text_set, SCRIPT_OBJECT_TEXT, SCRIPT_OBJECT_VAR_TEXT_HEIGHT, ".height" }, + { on_text_get, on_text_set, SCRIPT_OBJECT_TEXT, SCRIPT_OBJECT_VAR_TEXT_HALIGN, ".halign" }, + { on_text_get, on_text_set, SCRIPT_OBJECT_TEXT, SCRIPT_OBJECT_VAR_TEXT_VALIGN, ".valign" }, + { on_text_get, on_text_set, SCRIPT_OBJECT_TEXT, SCRIPT_OBJECT_VAR_TEXT_COLOR, ".color" }, + { on_text_get, on_text_set, SCRIPT_OBJECT_TEXT, SCRIPT_OBJECT_VAR_TEXT_BACKCOLOR, ".backcolor" }, + { on_text_get, on_text_set, SCRIPT_OBJECT_TEXT, SCRIPT_OBJECT_VAR_TEXT_VALUE, ".value" }, + { on_text_get, on_text_set, SCRIPT_OBJECT_TEXT, SCRIPT_OBJECT_VAR_TEXT_COUNT, ".count" }, + { on_text_get, on_text_set, SCRIPT_OBJECT_TEXT, SCRIPT_OBJECT_VAR_TEXT_DELETE, ".delete" }, + { on_text_get, on_text_set, SCRIPT_OBJECT_TEXT, SCRIPT_OBJECT_VAR_TEXT_FONT, ".font" }, + { on_text_get, on_text_set, SCRIPT_OBJECT_TEXT, SCRIPT_OBJECT_VAR_TEXT_TEXTALIGN, ".textalign" }, + { on_text_get, on_text_set, SCRIPT_OBJECT_TEXT, SCRIPT_OBJECT_VAR_TEXT_STYLE, ".style" }, + { on_text_get, on_text_set, SCRIPT_OBJECT_TEXT, SCRIPT_OBJECT_VAR_TEXT_TIMER, ".timer" }, + + { on_rect_get, on_rect_set, SCRIPT_OBJECT_RECT, SCRIPT_OBJECT_VAR_RECT_TYPE, ".type" }, + { on_rect_get, on_rect_set, SCRIPT_OBJECT_RECT, SCRIPT_OBJECT_VAR_RECT_GROUP, ".group" }, + { on_rect_get, on_rect_set, SCRIPT_OBJECT_RECT, SCRIPT_OBJECT_VAR_RECT_X, ".x" }, + { on_rect_get, on_rect_set, SCRIPT_OBJECT_RECT, SCRIPT_OBJECT_VAR_RECT_Y, ".y" }, + { on_rect_get, on_rect_set, SCRIPT_OBJECT_RECT, SCRIPT_OBJECT_VAR_RECT_VISIBLE, ".visible" }, + { on_rect_get, on_rect_set, SCRIPT_OBJECT_RECT, SCRIPT_OBJECT_VAR_RECT_WIDTH, ".width" }, + { on_rect_get, on_rect_set, SCRIPT_OBJECT_RECT, SCRIPT_OBJECT_VAR_RECT_HEIGHT, ".height" }, + { on_rect_get, on_rect_set, SCRIPT_OBJECT_RECT, SCRIPT_OBJECT_VAR_RECT_LINEWIDTH, ".linewidth" }, + { on_rect_get, on_rect_set, SCRIPT_OBJECT_RECT, SCRIPT_OBJECT_VAR_RECT_COLOR, ".color" }, + { on_rect_get, on_rect_set, SCRIPT_OBJECT_RECT, SCRIPT_OBJECT_VAR_RECT_BACKCOLOR, ".backcolor" }, + { on_rect_get, on_rect_set, SCRIPT_OBJECT_RECT, SCRIPT_OBJECT_VAR_RECT_HALIGN, ".halign" }, + { on_rect_get, on_rect_set, SCRIPT_OBJECT_RECT, SCRIPT_OBJECT_VAR_RECT_VALIGN, ".valign" }, + { on_rect_get, on_rect_set, SCRIPT_OBJECT_RECT, SCRIPT_OBJECT_VAR_RECT_ROUND, ".round" }, + { on_rect_get, on_rect_set, SCRIPT_OBJECT_RECT, SCRIPT_OBJECT_VAR_RECT_TIMER, ".timer" }, + { NULL, NULL, -1, -1, NULL } +}; + diff --git a/src/script-pvars.inc.c b/src/script-pvars.inc.c new file mode 100644 index 0000000..cf45a93 --- /dev/null +++ b/src/script-pvars.inc.c @@ -0,0 +1,168 @@ +////////////////////////////////////////////////////// +// SigmaPlayer Project. +// File auto-generated from MMSL language reference. +////////////////////////////////////////////////////// + +static const struct SCRIPT_VAR +{ + MmslVariableCallback Get, Set; + int ID; + const char *name; +} script_vars[] = +{ + { on_kernel_get, on_kernel_set, SCRIPT_VAR_KERNEL_POWER, "kernel.power" }, + { on_kernel_get, on_kernel_set, SCRIPT_VAR_KERNEL_FREQUENCY, "kernel.frequency" }, + { on_kernel_get, on_kernel_set, SCRIPT_VAR_KERNEL_CHIP, "kernel.chip" }, + { on_kernel_get, on_kernel_set, SCRIPT_VAR_KERNEL_FREE_MEMORY, "kernel.free_memory" }, + { on_kernel_get, on_kernel_set, SCRIPT_VAR_KERNEL_FLASH_MEMORY, "kernel.flash_memory" }, + { on_kernel_get, on_kernel_set, SCRIPT_VAR_KERNEL_PRINT, "kernel.print" }, + { on_kernel_get, on_kernel_set, SCRIPT_VAR_KERNEL_FIRMWARE_VERSION, "kernel.firmware_version" }, + { on_kernel_get, on_kernel_set, SCRIPT_VAR_KERNEL_MMSL_VERSION, "kernel.mmsl_version" }, + { on_kernel_get, on_kernel_set, SCRIPT_VAR_KERNEL_MMSL_ERRORS, "kernel.mmsl_errors" }, + { on_kernel_get, on_kernel_set, SCRIPT_VAR_KERNEL_RUN, "kernel.run" }, + { on_kernel_get, on_kernel_set, SCRIPT_VAR_KERNEL_RANDOM, "kernel.random" }, + + { on_screen_get, on_screen_set, SCRIPT_VAR_SCREEN_SWITCH, "screen.switch" }, + { on_screen_get, on_screen_set, SCRIPT_VAR_SCREEN_UPDATE, "screen.update" }, + { on_screen_get, on_screen_set, SCRIPT_VAR_SCREEN_TVSTANDARD, "screen.tvstandard" }, + { on_screen_get, on_screen_set, SCRIPT_VAR_SCREEN_TVOUT, "screen.tvout" }, + { on_screen_get, on_screen_set, SCRIPT_VAR_SCREEN_LEFT, "screen.left" }, + { on_screen_get, on_screen_set, SCRIPT_VAR_SCREEN_TOP, "screen.top" }, + { on_screen_get, on_screen_set, SCRIPT_VAR_SCREEN_RIGHT, "screen.right" }, + { on_screen_get, on_screen_set, SCRIPT_VAR_SCREEN_BOTTOM, "screen.bottom" }, + { on_screen_get, on_screen_set, SCRIPT_VAR_SCREEN_PALETTE, "screen.palette" }, + { on_screen_get, on_screen_set, SCRIPT_VAR_SCREEN_PALIDX, "screen.palidx" }, + { on_screen_get, on_screen_set, SCRIPT_VAR_SCREEN_PALALPHA, "screen.palalpha" }, + { on_screen_get, on_screen_set, SCRIPT_VAR_SCREEN_PALCOLOR, "screen.palcolor" }, + { on_screen_get, on_screen_set, SCRIPT_VAR_SCREEN_FONT, "screen.font" }, + { on_screen_get, on_screen_set, SCRIPT_VAR_SCREEN_COLOR, "screen.color" }, + { on_screen_get, on_screen_set, SCRIPT_VAR_SCREEN_BACKCOLOR, "screen.backcolor" }, + { on_screen_get, on_screen_set, SCRIPT_VAR_SCREEN_TRCOLOR, "screen.trcolor" }, + { on_screen_get, on_screen_set, SCRIPT_VAR_SCREEN_HALIGN, "screen.halign" }, + { on_screen_get, on_screen_set, SCRIPT_VAR_SCREEN_VALIGN, "screen.valign" }, + { on_screen_get, on_screen_set, SCRIPT_VAR_SCREEN_BACK, "screen.back" }, + { on_screen_get, on_screen_set, SCRIPT_VAR_SCREEN_BACK_LEFT, "screen.back_left" }, + { on_screen_get, on_screen_set, SCRIPT_VAR_SCREEN_BACK_TOP, "screen.back_top" }, + { on_screen_get, on_screen_set, SCRIPT_VAR_SCREEN_BACK_RIGHT, "screen.back_right" }, + { on_screen_get, on_screen_set, SCRIPT_VAR_SCREEN_BACK_BOTTOM, "screen.back_bottom" }, + { on_screen_get, on_screen_set, SCRIPT_VAR_SCREEN_PRELOAD, "screen.preload" }, + { on_screen_get, on_screen_set, SCRIPT_VAR_SCREEN_HZOOM, "screen.hzoom" }, + { on_screen_get, on_screen_set, SCRIPT_VAR_SCREEN_VZOOM, "screen.vzoom" }, + { on_screen_get, on_screen_set, SCRIPT_VAR_SCREEN_HSCROLL, "screen.hscroll" }, + { on_screen_get, on_screen_set, SCRIPT_VAR_SCREEN_VSCROLL, "screen.vscroll" }, + { on_screen_get, on_screen_set, SCRIPT_VAR_SCREEN_ROTATE, "screen.rotate" }, + { on_screen_get, on_screen_set, SCRIPT_VAR_SCREEN_BRIGHTNESS, "screen.brightness" }, + { on_screen_get, on_screen_set, SCRIPT_VAR_SCREEN_CONTRAST, "screen.contrast" }, + { on_screen_get, on_screen_set, SCRIPT_VAR_SCREEN_SATURATION, "screen.saturation" }, + { on_screen_get, on_screen_set, SCRIPT_VAR_SCREEN_FULLSCREEN, "screen.fullscreen" }, + + { on_pad_get, on_pad_set, SCRIPT_VAR_PAD_KEY, "pad.key" }, + { on_pad_get, on_pad_set, SCRIPT_VAR_PAD_DISPLAY, "pad.display" }, + { on_pad_get, on_pad_set, SCRIPT_VAR_PAD_SET, "pad.set" }, + { on_pad_get, on_pad_set, SCRIPT_VAR_PAD_CLEAR, "pad.clear" }, + + { on_drive_get, on_drive_set, SCRIPT_VAR_DRIVE_MEDIATYPE, "drive.mediatype" }, + { on_drive_get, on_drive_set, SCRIPT_VAR_DRIVE_TRAY, "drive.tray" }, + + { on_explorer_get, on_explorer_set, SCRIPT_VAR_EXPLORER_CHARSET, "explorer.charset" }, + { on_explorer_get, on_explorer_set, SCRIPT_VAR_EXPLORER_FOLDER, "explorer.folder" }, + { on_explorer_get, on_explorer_set, SCRIPT_VAR_EXPLORER_TARGET, "explorer.target" }, + { on_explorer_get, on_explorer_set, SCRIPT_VAR_EXPLORER_FILTER, "explorer.filter" }, + { on_explorer_get, on_explorer_set, SCRIPT_VAR_EXPLORER_MASK1, "explorer.mask1" }, + { on_explorer_get, on_explorer_set, SCRIPT_VAR_EXPLORER_MASK2, "explorer.mask2" }, + { on_explorer_get, on_explorer_set, SCRIPT_VAR_EXPLORER_MASK3, "explorer.mask3" }, + { on_explorer_get, on_explorer_set, SCRIPT_VAR_EXPLORER_MASK4, "explorer.mask4" }, + { on_explorer_get, on_explorer_set, SCRIPT_VAR_EXPLORER_MASK5, "explorer.mask5" }, + { on_explorer_get, on_explorer_set, SCRIPT_VAR_EXPLORER_PATH, "explorer.path" }, + { on_explorer_get, on_explorer_set, SCRIPT_VAR_EXPLORER_FILENAME, "explorer.filename" }, + { on_explorer_get, on_explorer_set, SCRIPT_VAR_EXPLORER_EXTENSION, "explorer.extension" }, + { on_explorer_get, on_explorer_set, SCRIPT_VAR_EXPLORER_DRIVE_LETTER, "explorer.drive_letter" }, + { on_explorer_get, on_explorer_set, SCRIPT_VAR_EXPLORER_TYPE, "explorer.type" }, + { on_explorer_get, on_explorer_set, SCRIPT_VAR_EXPLORER_MASKINDEX, "explorer.maskindex" }, + { on_explorer_get, on_explorer_set, SCRIPT_VAR_EXPLORER_FILESIZE, "explorer.filesize" }, + { on_explorer_get, on_explorer_set, SCRIPT_VAR_EXPLORER_FILETIME, "explorer.filetime" }, + { on_explorer_get, on_explorer_set, SCRIPT_VAR_EXPLORER_COUNT, "explorer.count" }, + { on_explorer_get, on_explorer_set, SCRIPT_VAR_EXPLORER_SORT, "explorer.sort" }, + { on_explorer_get, on_explorer_set, SCRIPT_VAR_EXPLORER_POSITION, "explorer.position" }, + { on_explorer_get, on_explorer_set, SCRIPT_VAR_EXPLORER_COMMAND, "explorer.command" }, + { on_explorer_get, on_explorer_set, SCRIPT_VAR_EXPLORER_FIND, "explorer.find" }, + { on_explorer_get, on_explorer_set, SCRIPT_VAR_EXPLORER_COPIED, "explorer.copied" }, + + { on_player_get, on_player_set, SCRIPT_VAR_PLAYER_SOURCE, "player.source" }, + { on_player_get, on_player_set, SCRIPT_VAR_PLAYER_PLAYING, "player.playing" }, + { on_player_get, on_player_set, SCRIPT_VAR_PLAYER_SAVED, "player.saved" }, + { on_player_get, on_player_set, SCRIPT_VAR_PLAYER_COMMAND, "player.command" }, + { on_player_get, on_player_set, SCRIPT_VAR_PLAYER_SELECT, "player.select" }, + { on_player_get, on_player_set, SCRIPT_VAR_PLAYER_REPEAT, "player.repeat" }, + { on_player_get, on_player_set, SCRIPT_VAR_PLAYER_SPEED, "player.speed" }, + { on_player_get, on_player_set, SCRIPT_VAR_PLAYER_MENU, "player.menu" }, + { on_player_get, on_player_set, SCRIPT_VAR_PLAYER_LANGUAGE_MENU, "player.language_menu" }, + { on_player_get, on_player_set, SCRIPT_VAR_PLAYER_LANGUAGE_AUDIO, "player.language_audio" }, + { on_player_get, on_player_set, SCRIPT_VAR_PLAYER_LANGUAGE_SUBTITLE, "player.language_subtitle" }, + { on_player_get, on_player_set, SCRIPT_VAR_PLAYER_SUBTITLE_CHARSET, "player.subtitle_charset" }, + { on_player_get, on_player_set, SCRIPT_VAR_PLAYER_SUBTITLE_WRAP, "player.subtitle_wrap" }, + { on_player_get, on_player_set, SCRIPT_VAR_PLAYER_SUBTITLE, "player.subtitle" }, + { on_player_get, on_player_set, SCRIPT_VAR_PLAYER_ANGLE, "player.angle" }, + { on_player_get, on_player_set, SCRIPT_VAR_PLAYER_VOLUME, "player.volume" }, + { on_player_get, on_player_set, SCRIPT_VAR_PLAYER_BALANCE, "player.balance" }, + { on_player_get, on_player_set, SCRIPT_VAR_PLAYER_AUDIO_OFFSET, "player.audio_offset" }, + { on_player_get, on_player_set, SCRIPT_VAR_PLAYER_TIME, "player.time" }, + { on_player_get, on_player_set, SCRIPT_VAR_PLAYER_TITLE, "player.title" }, + { on_player_get, on_player_set, SCRIPT_VAR_PLAYER_CHAPTER, "player.chapter" }, + { on_player_get, on_player_set, SCRIPT_VAR_PLAYER_NAME, "player.name" }, + { on_player_get, on_player_set, SCRIPT_VAR_PLAYER_ARTIST, "player.artist" }, + { on_player_get, on_player_set, SCRIPT_VAR_PLAYER_AUDIO_INFO, "player.audio_info" }, + { on_player_get, on_player_set, SCRIPT_VAR_PLAYER_VIDEO_INFO, "player.video_info" }, + { on_player_get, on_player_set, SCRIPT_VAR_PLAYER_AUDIO_STREAM, "player.audio_stream" }, + { on_player_get, on_player_set, SCRIPT_VAR_PLAYER_SUBTITLE_STREAM, "player.subtitle_stream" }, + { on_player_get, on_player_set, SCRIPT_VAR_PLAYER_LENGTH, "player.length" }, + { on_player_get, on_player_set, SCRIPT_VAR_PLAYER_NUM_TITLES, "player.num_titles" }, + { on_player_get, on_player_set, SCRIPT_VAR_PLAYER_NUM_CHAPTERS, "player.num_chapters" }, + { on_player_get, on_player_set, SCRIPT_VAR_PLAYER_WIDTH, "player.width" }, + { on_player_get, on_player_set, SCRIPT_VAR_PLAYER_HEIGHT, "player.height" }, + { on_player_get, on_player_set, SCRIPT_VAR_PLAYER_FRAME_RATE, "player.frame_rate" }, + { on_player_get, on_player_set, SCRIPT_VAR_PLAYER_COLOR_SPACE, "player.color_space" }, + { on_player_get, on_player_set, SCRIPT_VAR_PLAYER_DEBUG, "player.debug" }, + { on_player_get, on_player_set, SCRIPT_VAR_PLAYER_ERROR, "player.error" }, + + { on_settings_get, on_settings_set, SCRIPT_VAR_SETTINGS_COMMAND, "settings.command" }, + { on_settings_get, on_settings_set, SCRIPT_VAR_SETTINGS_AUDIOOUT, "settings.audioout" }, + { on_settings_get, on_settings_set, SCRIPT_VAR_SETTINGS_TVTYPE, "settings.tvtype" }, + { on_settings_get, on_settings_set, SCRIPT_VAR_SETTINGS_TVSTANDARD, "settings.tvstandard" }, + { on_settings_get, on_settings_set, SCRIPT_VAR_SETTINGS_TVOUT, "settings.tvout" }, + { on_settings_get, on_settings_set, SCRIPT_VAR_SETTINGS_DVI, "settings.dvi" }, + { on_settings_get, on_settings_set, SCRIPT_VAR_SETTINGS_HQ_JPEG, "settings.hq_jpeg" }, + { on_settings_get, on_settings_set, SCRIPT_VAR_SETTINGS_HDD_SPEED, "settings.hdd_speed" }, + { on_settings_get, on_settings_set, SCRIPT_VAR_SETTINGS_DVD_PARENTAL, "settings.dvd_parental" }, + { on_settings_get, on_settings_set, SCRIPT_VAR_SETTINGS_DVD_MV, "settings.dvd_mv" }, + { on_settings_get, on_settings_set, SCRIPT_VAR_SETTINGS_DVD_LANG_MENU, "settings.dvd_lang_menu" }, + { on_settings_get, on_settings_set, SCRIPT_VAR_SETTINGS_DVD_LANG_AUDIO, "settings.dvd_lang_audio" }, + { on_settings_get, on_settings_set, SCRIPT_VAR_SETTINGS_DVD_LANG_SPU, "settings.dvd_lang_spu" }, + { on_settings_get, on_settings_set, SCRIPT_VAR_SETTINGS_VOLUME, "settings.volume" }, + { on_settings_get, on_settings_set, SCRIPT_VAR_SETTINGS_BALANCE, "settings.balance" }, + { on_settings_get, on_settings_set, SCRIPT_VAR_SETTINGS_BRIGHTNESS, "settings.brightness" }, + { on_settings_get, on_settings_set, SCRIPT_VAR_SETTINGS_CONTRAST, "settings.contrast" }, + { on_settings_get, on_settings_set, SCRIPT_VAR_SETTINGS_SATURATION, "settings.saturation" }, + { on_settings_get, on_settings_set, SCRIPT_VAR_SETTINGS_USER1, "settings.user1" }, + { on_settings_get, on_settings_set, SCRIPT_VAR_SETTINGS_USER2, "settings.user2" }, + { on_settings_get, on_settings_set, SCRIPT_VAR_SETTINGS_USER3, "settings.user3" }, + { on_settings_get, on_settings_set, SCRIPT_VAR_SETTINGS_USER4, "settings.user4" }, + { on_settings_get, on_settings_set, SCRIPT_VAR_SETTINGS_USER5, "settings.user5" }, + { on_settings_get, on_settings_set, SCRIPT_VAR_SETTINGS_USER6, "settings.user6" }, + { on_settings_get, on_settings_set, SCRIPT_VAR_SETTINGS_USER7, "settings.user7" }, + { on_settings_get, on_settings_set, SCRIPT_VAR_SETTINGS_USER8, "settings.user8" }, + { on_settings_get, on_settings_set, SCRIPT_VAR_SETTINGS_USER9, "settings.user9" }, + { on_settings_get, on_settings_set, SCRIPT_VAR_SETTINGS_USER10, "settings.user10" }, + { on_settings_get, on_settings_set, SCRIPT_VAR_SETTINGS_USER11, "settings.user11" }, + { on_settings_get, on_settings_set, SCRIPT_VAR_SETTINGS_USER12, "settings.user12" }, + { on_settings_get, on_settings_set, SCRIPT_VAR_SETTINGS_USER13, "settings.user13" }, + { on_settings_get, on_settings_set, SCRIPT_VAR_SETTINGS_USER14, "settings.user14" }, + { on_settings_get, on_settings_set, SCRIPT_VAR_SETTINGS_USER15, "settings.user15" }, + { on_settings_get, on_settings_set, SCRIPT_VAR_SETTINGS_USER16, "settings.user16" }, + + { on_flash_get, on_flash_set, SCRIPT_VAR_FLASH_FILE, "flash.file" }, + { on_flash_get, on_flash_set, SCRIPT_VAR_FLASH_ADDRESS, "flash.address" }, + { on_flash_get, on_flash_set, SCRIPT_VAR_FLASH_PROGRESS, "flash.progress" }, + { NULL, NULL, -1, NULL } +}; + diff --git a/src/script-vars.h b/src/script-vars.h new file mode 100644 index 0000000..b36512e --- /dev/null +++ b/src/script-vars.h @@ -0,0 +1,187 @@ +////////////////////////////////////////////////////// +// SigmaPlayer Project. +// File auto-generated from MMSL language reference. +////////////////////////////////////////////////////// + +void on_kernel_get(int var_id, MmslVariable *var, void *param, int obj_id = -1, MMSL_OBJECT *obj = NULL); +void on_kernel_set(int var_id, MmslVariable *var, void *param, int obj_id = -1, MMSL_OBJECT *obj = NULL); + +void on_screen_get(int var_id, MmslVariable *var, void *param, int obj_id = -1, MMSL_OBJECT *obj = NULL); +void on_screen_set(int var_id, MmslVariable *var, void *param, int obj_id = -1, MMSL_OBJECT *obj = NULL); + +void on_pad_get(int var_id, MmslVariable *var, void *param, int obj_id = -1, MMSL_OBJECT *obj = NULL); +void on_pad_set(int var_id, MmslVariable *var, void *param, int obj_id = -1, MMSL_OBJECT *obj = NULL); + +void on_drive_get(int var_id, MmslVariable *var, void *param, int obj_id = -1, MMSL_OBJECT *obj = NULL); +void on_drive_set(int var_id, MmslVariable *var, void *param, int obj_id = -1, MMSL_OBJECT *obj = NULL); + +void on_explorer_get(int var_id, MmslVariable *var, void *param, int obj_id = -1, MMSL_OBJECT *obj = NULL); +void on_explorer_set(int var_id, MmslVariable *var, void *param, int obj_id = -1, MMSL_OBJECT *obj = NULL); + +void on_player_get(int var_id, MmslVariable *var, void *param, int obj_id = -1, MMSL_OBJECT *obj = NULL); +void on_player_set(int var_id, MmslVariable *var, void *param, int obj_id = -1, MMSL_OBJECT *obj = NULL); + +void on_settings_get(int var_id, MmslVariable *var, void *param, int obj_id = -1, MMSL_OBJECT *obj = NULL); +void on_settings_set(int var_id, MmslVariable *var, void *param, int obj_id = -1, MMSL_OBJECT *obj = NULL); + +void on_flash_get(int var_id, MmslVariable *var, void *param, int obj_id = -1, MMSL_OBJECT *obj = NULL); +void on_flash_set(int var_id, MmslVariable *var, void *param, int obj_id = -1, MMSL_OBJECT *obj = NULL); + + +enum SCRIPT_VARS +{ + SCRIPT_VAR_KERNEL_POWER, + SCRIPT_VAR_KERNEL_FREQUENCY, + SCRIPT_VAR_KERNEL_CHIP, + SCRIPT_VAR_KERNEL_FREE_MEMORY, + SCRIPT_VAR_KERNEL_FLASH_MEMORY, + SCRIPT_VAR_KERNEL_PRINT, + SCRIPT_VAR_KERNEL_FIRMWARE_VERSION, + SCRIPT_VAR_KERNEL_MMSL_VERSION, + SCRIPT_VAR_KERNEL_MMSL_ERRORS, + SCRIPT_VAR_KERNEL_RUN, + SCRIPT_VAR_KERNEL_RANDOM, + + SCRIPT_VAR_SCREEN_SWITCH, + SCRIPT_VAR_SCREEN_UPDATE, + SCRIPT_VAR_SCREEN_TVSTANDARD, + SCRIPT_VAR_SCREEN_TVOUT, + SCRIPT_VAR_SCREEN_LEFT, + SCRIPT_VAR_SCREEN_TOP, + SCRIPT_VAR_SCREEN_RIGHT, + SCRIPT_VAR_SCREEN_BOTTOM, + SCRIPT_VAR_SCREEN_PALETTE, + SCRIPT_VAR_SCREEN_PALIDX, + SCRIPT_VAR_SCREEN_PALALPHA, + SCRIPT_VAR_SCREEN_PALCOLOR, + SCRIPT_VAR_SCREEN_FONT, + SCRIPT_VAR_SCREEN_COLOR, + SCRIPT_VAR_SCREEN_BACKCOLOR, + SCRIPT_VAR_SCREEN_TRCOLOR, + SCRIPT_VAR_SCREEN_HALIGN, + SCRIPT_VAR_SCREEN_VALIGN, + SCRIPT_VAR_SCREEN_BACK, + SCRIPT_VAR_SCREEN_BACK_LEFT, + SCRIPT_VAR_SCREEN_BACK_TOP, + SCRIPT_VAR_SCREEN_BACK_RIGHT, + SCRIPT_VAR_SCREEN_BACK_BOTTOM, + SCRIPT_VAR_SCREEN_PRELOAD, + SCRIPT_VAR_SCREEN_HZOOM, + SCRIPT_VAR_SCREEN_VZOOM, + SCRIPT_VAR_SCREEN_HSCROLL, + SCRIPT_VAR_SCREEN_VSCROLL, + SCRIPT_VAR_SCREEN_ROTATE, + SCRIPT_VAR_SCREEN_BRIGHTNESS, + SCRIPT_VAR_SCREEN_CONTRAST, + SCRIPT_VAR_SCREEN_SATURATION, + SCRIPT_VAR_SCREEN_FULLSCREEN, + + SCRIPT_VAR_PAD_KEY, + SCRIPT_VAR_PAD_DISPLAY, + SCRIPT_VAR_PAD_SET, + SCRIPT_VAR_PAD_CLEAR, + + SCRIPT_VAR_DRIVE_MEDIATYPE, + SCRIPT_VAR_DRIVE_TRAY, + + SCRIPT_VAR_EXPLORER_CHARSET, + SCRIPT_VAR_EXPLORER_FOLDER, + SCRIPT_VAR_EXPLORER_TARGET, + SCRIPT_VAR_EXPLORER_FILTER, + SCRIPT_VAR_EXPLORER_MASK1, + SCRIPT_VAR_EXPLORER_MASK2, + SCRIPT_VAR_EXPLORER_MASK3, + SCRIPT_VAR_EXPLORER_MASK4, + SCRIPT_VAR_EXPLORER_MASK5, + SCRIPT_VAR_EXPLORER_PATH, + SCRIPT_VAR_EXPLORER_FILENAME, + SCRIPT_VAR_EXPLORER_EXTENSION, + SCRIPT_VAR_EXPLORER_DRIVE_LETTER, + SCRIPT_VAR_EXPLORER_TYPE, + SCRIPT_VAR_EXPLORER_MASKINDEX, + SCRIPT_VAR_EXPLORER_FILESIZE, + SCRIPT_VAR_EXPLORER_FILETIME, + SCRIPT_VAR_EXPLORER_COUNT, + SCRIPT_VAR_EXPLORER_SORT, + SCRIPT_VAR_EXPLORER_POSITION, + SCRIPT_VAR_EXPLORER_COMMAND, + SCRIPT_VAR_EXPLORER_FIND, + SCRIPT_VAR_EXPLORER_COPIED, + + SCRIPT_VAR_PLAYER_SOURCE, + SCRIPT_VAR_PLAYER_PLAYING, + SCRIPT_VAR_PLAYER_SAVED, + SCRIPT_VAR_PLAYER_COMMAND, + SCRIPT_VAR_PLAYER_SELECT, + SCRIPT_VAR_PLAYER_REPEAT, + SCRIPT_VAR_PLAYER_SPEED, + SCRIPT_VAR_PLAYER_MENU, + SCRIPT_VAR_PLAYER_LANGUAGE_MENU, + SCRIPT_VAR_PLAYER_LANGUAGE_AUDIO, + SCRIPT_VAR_PLAYER_LANGUAGE_SUBTITLE, + SCRIPT_VAR_PLAYER_SUBTITLE_CHARSET, + SCRIPT_VAR_PLAYER_SUBTITLE_WRAP, + SCRIPT_VAR_PLAYER_SUBTITLE, + SCRIPT_VAR_PLAYER_ANGLE, + SCRIPT_VAR_PLAYER_VOLUME, + SCRIPT_VAR_PLAYER_BALANCE, + SCRIPT_VAR_PLAYER_AUDIO_OFFSET, + SCRIPT_VAR_PLAYER_TIME, + SCRIPT_VAR_PLAYER_TITLE, + SCRIPT_VAR_PLAYER_CHAPTER, + SCRIPT_VAR_PLAYER_NAME, + SCRIPT_VAR_PLAYER_ARTIST, + SCRIPT_VAR_PLAYER_AUDIO_INFO, + SCRIPT_VAR_PLAYER_VIDEO_INFO, + SCRIPT_VAR_PLAYER_AUDIO_STREAM, + SCRIPT_VAR_PLAYER_SUBTITLE_STREAM, + SCRIPT_VAR_PLAYER_LENGTH, + SCRIPT_VAR_PLAYER_NUM_TITLES, + SCRIPT_VAR_PLAYER_NUM_CHAPTERS, + SCRIPT_VAR_PLAYER_WIDTH, + SCRIPT_VAR_PLAYER_HEIGHT, + SCRIPT_VAR_PLAYER_FRAME_RATE, + SCRIPT_VAR_PLAYER_COLOR_SPACE, + SCRIPT_VAR_PLAYER_DEBUG, + SCRIPT_VAR_PLAYER_ERROR, + + SCRIPT_VAR_SETTINGS_COMMAND, + SCRIPT_VAR_SETTINGS_AUDIOOUT, + SCRIPT_VAR_SETTINGS_TVTYPE, + SCRIPT_VAR_SETTINGS_TVSTANDARD, + SCRIPT_VAR_SETTINGS_TVOUT, + SCRIPT_VAR_SETTINGS_DVI, + SCRIPT_VAR_SETTINGS_HQ_JPEG, + SCRIPT_VAR_SETTINGS_HDD_SPEED, + SCRIPT_VAR_SETTINGS_DVD_PARENTAL, + SCRIPT_VAR_SETTINGS_DVD_MV, + SCRIPT_VAR_SETTINGS_DVD_LANG_MENU, + SCRIPT_VAR_SETTINGS_DVD_LANG_AUDIO, + SCRIPT_VAR_SETTINGS_DVD_LANG_SPU, + SCRIPT_VAR_SETTINGS_VOLUME, + SCRIPT_VAR_SETTINGS_BALANCE, + SCRIPT_VAR_SETTINGS_BRIGHTNESS, + SCRIPT_VAR_SETTINGS_CONTRAST, + SCRIPT_VAR_SETTINGS_SATURATION, + SCRIPT_VAR_SETTINGS_USER1, + SCRIPT_VAR_SETTINGS_USER2, + SCRIPT_VAR_SETTINGS_USER3, + SCRIPT_VAR_SETTINGS_USER4, + SCRIPT_VAR_SETTINGS_USER5, + SCRIPT_VAR_SETTINGS_USER6, + SCRIPT_VAR_SETTINGS_USER7, + SCRIPT_VAR_SETTINGS_USER8, + SCRIPT_VAR_SETTINGS_USER9, + SCRIPT_VAR_SETTINGS_USER10, + SCRIPT_VAR_SETTINGS_USER11, + SCRIPT_VAR_SETTINGS_USER12, + SCRIPT_VAR_SETTINGS_USER13, + SCRIPT_VAR_SETTINGS_USER14, + SCRIPT_VAR_SETTINGS_USER15, + SCRIPT_VAR_SETTINGS_USER16, + + SCRIPT_VAR_FLASH_FILE, + SCRIPT_VAR_FLASH_ADDRESS, + SCRIPT_VAR_FLASH_PROGRESS, +}; + diff --git a/src/script-vars.inc.c b/src/script-vars.inc.c new file mode 100644 index 0000000..77b5bce --- /dev/null +++ b/src/script-vars.inc.c @@ -0,0 +1,167 @@ +////////////////////////////////////////////////////// +// SigmaPlayer Project. +// File auto-generated from MMSL language reference. +////////////////////////////////////////////////////// + +static const struct SCRIPT_VAR +{ + MmslVariableCallback Get, Set; + int ID; +} script_vars[] = +{ + { on_kernel_get, on_kernel_set, SCRIPT_VAR_KERNEL_POWER }, + { on_kernel_get, on_kernel_set, SCRIPT_VAR_KERNEL_FREQUENCY }, + { on_kernel_get, on_kernel_set, SCRIPT_VAR_KERNEL_CHIP }, + { on_kernel_get, on_kernel_set, SCRIPT_VAR_KERNEL_FREE_MEMORY }, + { on_kernel_get, on_kernel_set, SCRIPT_VAR_KERNEL_FLASH_MEMORY }, + { on_kernel_get, on_kernel_set, SCRIPT_VAR_KERNEL_PRINT }, + { on_kernel_get, on_kernel_set, SCRIPT_VAR_KERNEL_FIRMWARE_VERSION }, + { on_kernel_get, on_kernel_set, SCRIPT_VAR_KERNEL_MMSL_VERSION }, + { on_kernel_get, on_kernel_set, SCRIPT_VAR_KERNEL_MMSL_ERRORS }, + { on_kernel_get, on_kernel_set, SCRIPT_VAR_KERNEL_RUN }, + { on_kernel_get, on_kernel_set, SCRIPT_VAR_KERNEL_RANDOM }, + + { on_screen_get, on_screen_set, SCRIPT_VAR_SCREEN_SWITCH }, + { on_screen_get, on_screen_set, SCRIPT_VAR_SCREEN_UPDATE }, + { on_screen_get, on_screen_set, SCRIPT_VAR_SCREEN_TVSTANDARD }, + { on_screen_get, on_screen_set, SCRIPT_VAR_SCREEN_TVOUT }, + { on_screen_get, on_screen_set, SCRIPT_VAR_SCREEN_LEFT }, + { on_screen_get, on_screen_set, SCRIPT_VAR_SCREEN_TOP }, + { on_screen_get, on_screen_set, SCRIPT_VAR_SCREEN_RIGHT }, + { on_screen_get, on_screen_set, SCRIPT_VAR_SCREEN_BOTTOM }, + { on_screen_get, on_screen_set, SCRIPT_VAR_SCREEN_PALETTE }, + { on_screen_get, on_screen_set, SCRIPT_VAR_SCREEN_PALIDX }, + { on_screen_get, on_screen_set, SCRIPT_VAR_SCREEN_PALALPHA }, + { on_screen_get, on_screen_set, SCRIPT_VAR_SCREEN_PALCOLOR }, + { on_screen_get, on_screen_set, SCRIPT_VAR_SCREEN_FONT }, + { on_screen_get, on_screen_set, SCRIPT_VAR_SCREEN_COLOR }, + { on_screen_get, on_screen_set, SCRIPT_VAR_SCREEN_BACKCOLOR }, + { on_screen_get, on_screen_set, SCRIPT_VAR_SCREEN_TRCOLOR }, + { on_screen_get, on_screen_set, SCRIPT_VAR_SCREEN_HALIGN }, + { on_screen_get, on_screen_set, SCRIPT_VAR_SCREEN_VALIGN }, + { on_screen_get, on_screen_set, SCRIPT_VAR_SCREEN_BACK }, + { on_screen_get, on_screen_set, SCRIPT_VAR_SCREEN_BACK_LEFT }, + { on_screen_get, on_screen_set, SCRIPT_VAR_SCREEN_BACK_TOP }, + { on_screen_get, on_screen_set, SCRIPT_VAR_SCREEN_BACK_RIGHT }, + { on_screen_get, on_screen_set, SCRIPT_VAR_SCREEN_BACK_BOTTOM }, + { on_screen_get, on_screen_set, SCRIPT_VAR_SCREEN_PRELOAD }, + { on_screen_get, on_screen_set, SCRIPT_VAR_SCREEN_HZOOM }, + { on_screen_get, on_screen_set, SCRIPT_VAR_SCREEN_VZOOM }, + { on_screen_get, on_screen_set, SCRIPT_VAR_SCREEN_HSCROLL }, + { on_screen_get, on_screen_set, SCRIPT_VAR_SCREEN_VSCROLL }, + { on_screen_get, on_screen_set, SCRIPT_VAR_SCREEN_ROTATE }, + { on_screen_get, on_screen_set, SCRIPT_VAR_SCREEN_BRIGHTNESS }, + { on_screen_get, on_screen_set, SCRIPT_VAR_SCREEN_CONTRAST }, + { on_screen_get, on_screen_set, SCRIPT_VAR_SCREEN_SATURATION }, + { on_screen_get, on_screen_set, SCRIPT_VAR_SCREEN_FULLSCREEN }, + + { on_pad_get, on_pad_set, SCRIPT_VAR_PAD_KEY }, + { on_pad_get, on_pad_set, SCRIPT_VAR_PAD_DISPLAY }, + { on_pad_get, on_pad_set, SCRIPT_VAR_PAD_SET }, + { on_pad_get, on_pad_set, SCRIPT_VAR_PAD_CLEAR }, + + { on_drive_get, on_drive_set, SCRIPT_VAR_DRIVE_MEDIATYPE }, + { on_drive_get, on_drive_set, SCRIPT_VAR_DRIVE_TRAY }, + + { on_explorer_get, on_explorer_set, SCRIPT_VAR_EXPLORER_CHARSET }, + { on_explorer_get, on_explorer_set, SCRIPT_VAR_EXPLORER_FOLDER }, + { on_explorer_get, on_explorer_set, SCRIPT_VAR_EXPLORER_TARGET }, + { on_explorer_get, on_explorer_set, SCRIPT_VAR_EXPLORER_FILTER }, + { on_explorer_get, on_explorer_set, SCRIPT_VAR_EXPLORER_MASK1 }, + { on_explorer_get, on_explorer_set, SCRIPT_VAR_EXPLORER_MASK2 }, + { on_explorer_get, on_explorer_set, SCRIPT_VAR_EXPLORER_MASK3 }, + { on_explorer_get, on_explorer_set, SCRIPT_VAR_EXPLORER_MASK4 }, + { on_explorer_get, on_explorer_set, SCRIPT_VAR_EXPLORER_MASK5 }, + { on_explorer_get, on_explorer_set, SCRIPT_VAR_EXPLORER_PATH }, + { on_explorer_get, on_explorer_set, SCRIPT_VAR_EXPLORER_FILENAME }, + { on_explorer_get, on_explorer_set, SCRIPT_VAR_EXPLORER_EXTENSION }, + { on_explorer_get, on_explorer_set, SCRIPT_VAR_EXPLORER_DRIVE_LETTER }, + { on_explorer_get, on_explorer_set, SCRIPT_VAR_EXPLORER_TYPE }, + { on_explorer_get, on_explorer_set, SCRIPT_VAR_EXPLORER_MASKINDEX }, + { on_explorer_get, on_explorer_set, SCRIPT_VAR_EXPLORER_FILESIZE }, + { on_explorer_get, on_explorer_set, SCRIPT_VAR_EXPLORER_FILETIME }, + { on_explorer_get, on_explorer_set, SCRIPT_VAR_EXPLORER_COUNT }, + { on_explorer_get, on_explorer_set, SCRIPT_VAR_EXPLORER_SORT }, + { on_explorer_get, on_explorer_set, SCRIPT_VAR_EXPLORER_POSITION }, + { on_explorer_get, on_explorer_set, SCRIPT_VAR_EXPLORER_COMMAND }, + { on_explorer_get, on_explorer_set, SCRIPT_VAR_EXPLORER_FIND }, + { on_explorer_get, on_explorer_set, SCRIPT_VAR_EXPLORER_COPIED }, + + { on_player_get, on_player_set, SCRIPT_VAR_PLAYER_SOURCE }, + { on_player_get, on_player_set, SCRIPT_VAR_PLAYER_PLAYING }, + { on_player_get, on_player_set, SCRIPT_VAR_PLAYER_SAVED }, + { on_player_get, on_player_set, SCRIPT_VAR_PLAYER_COMMAND }, + { on_player_get, on_player_set, SCRIPT_VAR_PLAYER_SELECT }, + { on_player_get, on_player_set, SCRIPT_VAR_PLAYER_REPEAT }, + { on_player_get, on_player_set, SCRIPT_VAR_PLAYER_SPEED }, + { on_player_get, on_player_set, SCRIPT_VAR_PLAYER_MENU }, + { on_player_get, on_player_set, SCRIPT_VAR_PLAYER_LANGUAGE_MENU }, + { on_player_get, on_player_set, SCRIPT_VAR_PLAYER_LANGUAGE_AUDIO }, + { on_player_get, on_player_set, SCRIPT_VAR_PLAYER_LANGUAGE_SUBTITLE }, + { on_player_get, on_player_set, SCRIPT_VAR_PLAYER_SUBTITLE_CHARSET }, + { on_player_get, on_player_set, SCRIPT_VAR_PLAYER_SUBTITLE_WRAP }, + { on_player_get, on_player_set, SCRIPT_VAR_PLAYER_SUBTITLE }, + { on_player_get, on_player_set, SCRIPT_VAR_PLAYER_ANGLE }, + { on_player_get, on_player_set, SCRIPT_VAR_PLAYER_VOLUME }, + { on_player_get, on_player_set, SCRIPT_VAR_PLAYER_BALANCE }, + { on_player_get, on_player_set, SCRIPT_VAR_PLAYER_AUDIO_OFFSET }, + { on_player_get, on_player_set, SCRIPT_VAR_PLAYER_TIME }, + { on_player_get, on_player_set, SCRIPT_VAR_PLAYER_TITLE }, + { on_player_get, on_player_set, SCRIPT_VAR_PLAYER_CHAPTER }, + { on_player_get, on_player_set, SCRIPT_VAR_PLAYER_NAME }, + { on_player_get, on_player_set, SCRIPT_VAR_PLAYER_ARTIST }, + { on_player_get, on_player_set, SCRIPT_VAR_PLAYER_AUDIO_INFO }, + { on_player_get, on_player_set, SCRIPT_VAR_PLAYER_VIDEO_INFO }, + { on_player_get, on_player_set, SCRIPT_VAR_PLAYER_AUDIO_STREAM }, + { on_player_get, on_player_set, SCRIPT_VAR_PLAYER_SUBTITLE_STREAM }, + { on_player_get, on_player_set, SCRIPT_VAR_PLAYER_LENGTH }, + { on_player_get, on_player_set, SCRIPT_VAR_PLAYER_NUM_TITLES }, + { on_player_get, on_player_set, SCRIPT_VAR_PLAYER_NUM_CHAPTERS }, + { on_player_get, on_player_set, SCRIPT_VAR_PLAYER_WIDTH }, + { on_player_get, on_player_set, SCRIPT_VAR_PLAYER_HEIGHT }, + { on_player_get, on_player_set, SCRIPT_VAR_PLAYER_FRAME_RATE }, + { on_player_get, on_player_set, SCRIPT_VAR_PLAYER_COLOR_SPACE }, + { on_player_get, on_player_set, SCRIPT_VAR_PLAYER_DEBUG }, + { on_player_get, on_player_set, SCRIPT_VAR_PLAYER_ERROR }, + + { on_settings_get, on_settings_set, SCRIPT_VAR_SETTINGS_COMMAND }, + { on_settings_get, on_settings_set, SCRIPT_VAR_SETTINGS_AUDIOOUT }, + { on_settings_get, on_settings_set, SCRIPT_VAR_SETTINGS_TVTYPE }, + { on_settings_get, on_settings_set, SCRIPT_VAR_SETTINGS_TVSTANDARD }, + { on_settings_get, on_settings_set, SCRIPT_VAR_SETTINGS_TVOUT }, + { on_settings_get, on_settings_set, SCRIPT_VAR_SETTINGS_DVI }, + { on_settings_get, on_settings_set, SCRIPT_VAR_SETTINGS_HQ_JPEG }, + { on_settings_get, on_settings_set, SCRIPT_VAR_SETTINGS_HDD_SPEED }, + { on_settings_get, on_settings_set, SCRIPT_VAR_SETTINGS_DVD_PARENTAL }, + { on_settings_get, on_settings_set, SCRIPT_VAR_SETTINGS_DVD_MV }, + { on_settings_get, on_settings_set, SCRIPT_VAR_SETTINGS_DVD_LANG_MENU }, + { on_settings_get, on_settings_set, SCRIPT_VAR_SETTINGS_DVD_LANG_AUDIO }, + { on_settings_get, on_settings_set, SCRIPT_VAR_SETTINGS_DVD_LANG_SPU }, + { on_settings_get, on_settings_set, SCRIPT_VAR_SETTINGS_VOLUME }, + { on_settings_get, on_settings_set, SCRIPT_VAR_SETTINGS_BALANCE }, + { on_settings_get, on_settings_set, SCRIPT_VAR_SETTINGS_BRIGHTNESS }, + { on_settings_get, on_settings_set, SCRIPT_VAR_SETTINGS_CONTRAST }, + { on_settings_get, on_settings_set, SCRIPT_VAR_SETTINGS_SATURATION }, + { on_settings_get, on_settings_set, SCRIPT_VAR_SETTINGS_USER1 }, + { on_settings_get, on_settings_set, SCRIPT_VAR_SETTINGS_USER2 }, + { on_settings_get, on_settings_set, SCRIPT_VAR_SETTINGS_USER3 }, + { on_settings_get, on_settings_set, SCRIPT_VAR_SETTINGS_USER4 }, + { on_settings_get, on_settings_set, SCRIPT_VAR_SETTINGS_USER5 }, + { on_settings_get, on_settings_set, SCRIPT_VAR_SETTINGS_USER6 }, + { on_settings_get, on_settings_set, SCRIPT_VAR_SETTINGS_USER7 }, + { on_settings_get, on_settings_set, SCRIPT_VAR_SETTINGS_USER8 }, + { on_settings_get, on_settings_set, SCRIPT_VAR_SETTINGS_USER9 }, + { on_settings_get, on_settings_set, SCRIPT_VAR_SETTINGS_USER10 }, + { on_settings_get, on_settings_set, SCRIPT_VAR_SETTINGS_USER11 }, + { on_settings_get, on_settings_set, SCRIPT_VAR_SETTINGS_USER12 }, + { on_settings_get, on_settings_set, SCRIPT_VAR_SETTINGS_USER13 }, + { on_settings_get, on_settings_set, SCRIPT_VAR_SETTINGS_USER14 }, + { on_settings_get, on_settings_set, SCRIPT_VAR_SETTINGS_USER15 }, + { on_settings_get, on_settings_set, SCRIPT_VAR_SETTINGS_USER16 }, + + { on_flash_get, on_flash_set, SCRIPT_VAR_FLASH_FILE }, + { on_flash_get, on_flash_set, SCRIPT_VAR_FLASH_ADDRESS }, + { on_flash_get, on_flash_set, SCRIPT_VAR_FLASH_PROGRESS }, + { NULL, NULL, -1, } +}; + diff --git a/src/script.cpp b/src/script.cpp new file mode 100644 index 0000000..615fbbe --- /dev/null +++ b/src/script.cpp @@ -0,0 +1,1998 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - script wrapper impl. + * \file script.cpp + * \author bombur + * \version 0.1 + * \date 12.10.2006 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + + +#ifdef WIN32 +#define USE_MMSL_PARSER +#endif + + +#ifdef USE_MMSL_PARSER +#include +#endif + +#include +#include +#include + +ScriptParams *params = NULL; + +#include + +#ifdef USE_MMSL_PARSER +#include +#include +#else +#include +#include +#endif + +////////////////////////////////////////////////// + +static const StringPair tvtype_pairs[] = +{ + { "letterbox", 0 }, + { "panscan", 1 }, + { "wide", 2 }, + { "vcenter", 3 }, + { NULL, -1 }, +}; + +static const StringPair tvstandard_pairs[] = +{ + { "ntsc", 0 }, + { "pal", 1 }, + { "480p", 2 }, + { "576p", 3 }, + { "720p", 4 }, + { "1080i", 5 }, + { NULL, -1 }, +}; + +static const StringPair tvout_pairs[] = +{ + { "composite", 0 }, + { "ypbpr", 1 }, + { "rgb", 2 }, + { NULL, -1 }, +}; + +static const StringPair audioout_pairs[] = +{ + { "analog", 0 }, + { "digital", 1 }, + { NULL, -1 }, +}; + +static const StringPair hddspeed_pairs[] = +{ + { "fastest", 0 }, + { "limited", 1 }, + { "slow", 2 }, + { NULL, -1 }, +}; + +static const StringPair button_pairs[] = +{ + { "", FIP_KEY_NONE }, + { "power", FIP_KEY_POWER }, + { "eject", FIP_KEY_EJECT }, + { "one", FIP_KEY_ONE }, + { "two",FIP_KEY_TWO }, + { "three",FIP_KEY_THREE }, + { "four",FIP_KEY_FOUR }, + { "five",FIP_KEY_FIVE }, + { "six",FIP_KEY_SIX }, + { "seven",FIP_KEY_SEVEN }, + { "eight",FIP_KEY_EIGHT }, + { "nine",FIP_KEY_NINE }, + { "zero",FIP_KEY_ZERO }, + { "cancel",FIP_KEY_CANCEL }, + { "search",FIP_KEY_SEARCH }, + { "enter",FIP_KEY_ENTER }, + { "osd",FIP_KEY_OSD }, + { "subtitle",FIP_KEY_SUBTITLE }, + { "setup",FIP_KEY_SETUP }, + { "return",FIP_KEY_RETURN }, + { "title",FIP_KEY_TITLE }, + { "pn",FIP_KEY_PN }, + { "menu",FIP_KEY_MENU }, + { "ab",FIP_KEY_AB }, + { "repeat",FIP_KEY_REPEAT }, + { "up",FIP_KEY_UP }, + { "down",FIP_KEY_DOWN }, + { "left",FIP_KEY_LEFT }, + { "right",FIP_KEY_RIGHT }, + { "volume_down",FIP_KEY_VOLUME_DOWN }, + { "volume_up",FIP_KEY_VOLUME_UP }, + { "pause",FIP_KEY_PAUSE }, + { "rewind",FIP_KEY_REWIND }, + { "forward",FIP_KEY_FORWARD }, + { "prev",FIP_KEY_SKIP_PREV }, + { "next",FIP_KEY_SKIP_NEXT }, + { "play",FIP_KEY_PLAY }, + { "stop",FIP_KEY_STOP }, + { "slow",FIP_KEY_SLOW }, + { "audio",FIP_KEY_AUDIO }, + { "vmode",FIP_KEY_VMODE }, + { "mute",FIP_KEY_MUTE }, + { "zoom",FIP_KEY_ZOOM }, + { "program",FIP_KEY_PROGRAM }, + { "pbc",FIP_KEY_PBC }, + { "angle",FIP_KEY_ANGLE }, + + { "eject", FIP_KEY_FRONT_EJECT }, + { "pause",FIP_KEY_FRONT_PAUSE }, + { "rewind",FIP_KEY_FRONT_REWIND }, + { "forward",FIP_KEY_FRONT_FORWARD }, + { "prev",FIP_KEY_FRONT_SKIP_PREV }, + { "next",FIP_KEY_FRONT_SKIP_NEXT }, + { "play",FIP_KEY_FRONT_PLAY }, + { "stop",FIP_KEY_FRONT_STOP }, + { NULL, -1 }, +}; + +static const StringPair front_button_pairs2[] = +{ + { "", FIP_KEY_NONE }, + { "front_eject", FIP_KEY_FRONT_EJECT }, + { "front_pause",FIP_KEY_FRONT_PAUSE }, + { "front_rewind",FIP_KEY_FRONT_REWIND }, + { "front_forward",FIP_KEY_FRONT_FORWARD }, + { "front_prev",FIP_KEY_FRONT_SKIP_PREV }, + { "front_next",FIP_KEY_FRONT_SKIP_NEXT }, + { "front_play",FIP_KEY_FRONT_PLAY }, + { "front_stop",FIP_KEY_FRONT_STOP }, + { NULL, -1 }, +}; + +static const StringPair pad_special_pairs[] = +{ + { "play", FIP_SPECIAL_PLAY, }, + { "pause", FIP_SPECIAL_PAUSE, }, + { "mp3", FIP_SPECIAL_MP3, }, + { "dvd", FIP_SPECIAL_DVD, }, + { "s", FIP_SPECIAL_S, }, + { "v", FIP_SPECIAL_V, }, + { "cd", FIP_SPECIAL_CD, }, + { "dolby", FIP_SPECIAL_DOLBY, }, + { "dts", FIP_SPECIAL_DTS, }, + { "play_all", FIP_SPECIAL_ALL, }, + { "play_repeat", FIP_SPECIAL_REPEAT, }, + { "pbc", FIP_SPECIAL_PBC, }, + { "camera", FIP_SPECIAL_CAMERA, }, + { "colon1", FIP_SPECIAL_COLON1, }, + { "colon2", FIP_SPECIAL_COLON2, }, + { NULL, -1 }, +}; + +static const StringPair mediatype_pairs[] = +{ + { "none", CDROM_STATUS_NODISC }, + { "none", CDROM_STATUS_TRAYOPEN }, + { "dvd", CDROM_STATUS_HAS_DVD }, + { "iso", CDROM_STATUS_HAS_ISO }, + { "audio", CDROM_STATUS_HAS_AUDIO }, + { "mixed", CDROM_STATUS_HAS_MIXED }, + { NULL, -1 }, +}; + +static int GetScreenAdjustmentSetting(int idx) +{ + SETTING_SET which = SETTING_BRIGHTNESS_CONTRAST_SATURATION_PAL; + if (gui.GetTvStandard() == WINDOW_TVSTANDARD_NTSC) + which = SETTING_BRIGHTNESS_CONTRAST_SATURATION_NTSC; + DWORD v = (settings_get(which) >> (idx * 10)) & 1023; + return v; +} + +static void SetScreenAdjustmentSetting(int idx, int v) +{ + if (v < 0) v = 0; + if (v > 1000) v = 1000; + SETTING_SET which = SETTING_BRIGHTNESS_CONTRAST_SATURATION_PAL; + if (gui.GetTvStandard() == WINDOW_TVSTANDARD_NTSC) + which = SETTING_BRIGHTNESS_CONTRAST_SATURATION_NTSC; + DWORD d = settings_get(which) & ~(1023 << (idx * 10)); + settings_set(which, d | ((v & 1023) << (idx * 10))); +} + +////////////////////////////////////////////////// + +int script_init() +{ + int i; + int buflen; + BYTE *buf = NULL; + + params = new ScriptParams(); + tmpit = new Item(); + + const char *mmso_filename = "mmsl/startup.mmso"; + +#ifdef USE_MMSL_PARSER + mmsl_parser = new MmslParser(); + + for (i = 0; script_vars[i].name != NULL; i++) + { + mmsl_parser->RegisterVariable(script_vars[i].name, script_vars[i].ID); + } + + for (i = 0; script_objects[i].name != NULL; i++) + { + mmsl_parser->RegisterObject(script_objects[i].name, script_objects[i].ID); + } + + for (i = 0; script_object_vars[i].name != NULL; i++) + { + mmsl_parser->RegisterObjectVariable(script_object_vars[i].obj_ID, + script_object_vars[i].name, script_object_vars[i].ID); + } + if (!mmsl_parser->ParseFile("startup.mmsl")) + { + msg_error("Cannot open/parse script file!\n"); + } + buf = mmsl_parser->Save(&buflen); + + SPSafeDelete(mmsl_parser); + + if (buf != NULL) + { + mmso_save(mmso_filename, buf, buflen); + SPSafeFree(buf); + } +#endif + + mmsl = new Mmsl(); + msg("Loading MMSO '%s'\n", mmso_filename); + buf = mmso_load(mmso_filename, &buflen); + + if (buf != NULL) + { + if (!mmsl->Load(buf, buflen)) + { + msg("Cannot init main script!\n"); + } + SPSafeFree(buf); + + for (i = 0; script_vars[i].ID >= 0; i++) + { + if (mmsl->BindVariable(script_vars[i].ID, script_vars[i].Get, script_vars[i].Set) < 0) + msg("Script: Cannot bind variable #%d\n", script_vars[i].ID); + } + + for (i = 0; script_objects[i].ID >= 0; i++) + { + if (mmsl->BindObject(script_objects[i].ID, script_objects[i].Create, script_objects[i].Delete, NULL) < 0) + msg("Script: Cannot bind object #%d\n", script_objects[i].ID); + } + + for (i = 0; script_object_vars[i].ID >= 0; i++) + { + if (mmsl->BindObjectVariable(script_object_vars[i].obj_ID, script_object_vars[i].ID, script_object_vars[i].Get, + script_object_vars[i].Set, NULL) < 0) + msg("Script: Cannot bind object's #%d variable #%d\n", + script_object_vars[i].obj_ID, script_object_vars[i].ID); + } + } else + { + msg("Cannot load the main MMSO script!\n"); + } + + msg("Mmsl: Starting...\n"); + + //mmsl->Dump(); + + SPSafeFree(buf); + + struct timeval tv; + gettimeofday(&tv, NULL); + params->start_sec = (int)tv.tv_sec; + params->curtime = params->old_curtime = tv.tv_usec / 1000; + + params->fw_ver.Printf("%s-%d.%d (" __DATE__ ")", SP_NAME, SP_VERSION / 100, SP_VERSION % 100); + params->mmsl_ver = MMSL_VERSION; + + return 0; +} + +int script_deinit() +{ + stop_all(); + cdda_close(); + + SPSafeDelete(params); + SPSafeDelete(tmpit); + + return 0; +} + +int script_update(bool gfx_only) +{ + if (!params) + return 0; + + struct timeval tv; + gettimeofday(&tv, NULL); + + params->curtime = (int)((tv.tv_sec - params->start_sec) * 1000 + tv.tv_usec / 1000); + + // check if we need to call elapsed timers: +restart: + ScriptTimerObject *cur = params->timed_objs.GetFirst(); + while (cur != NULL) + { + // check timer value + Window *win = (Window *)cur->obj; + if (win != NULL) + { + // check if object timer elapsed + if (!gfx_only && cur->type == SCRIPT_OBJECT_TIMER) + { + if (win->timer > 0 && win->timer < params->curtime) + { + win->timer = 0; + mmsl->UpdateObjectVariable(win, cur->var_ID); + if (win->timerobj == NULL || win->timer != 0) + goto restart; + } + } + // some objects may require updates + else if (cur->type == SCRIPT_OBJECT_UPDATE) + { + win->Update(params->curtime); + } + } + cur = cur->next; + } + + if (gfx_only) + return 0; + + // update other stuff + + if (params->need_to_toggle_tray) + { + if (toggle_tray()) + params->need_to_toggle_tray = FALSE; + } + + if (params->flash_progress >= 0 && params->flash_progress < 100) + { + params->flash_p = flash_cycle(); + if (params->flash_p != params->flash_progress || params->flash_p < 0) + { + params->flash_progress = params->flash_p; + + msg("FLASH %d%%\n", params->flash_progress); + if (params->flash_progress == 100) + { + msg("FLASH OK!\n"); + } + mmsl->UpdateVariable(SCRIPT_VAR_FLASH_PROGRESS); + } + + // do nothing more + return 0; + } + + if (params->player_do_command) + { + params->player_do_command = false; + player_do_command(); + } + + if (params->dvdplaying) + { + if (dvd_player_loop() == 1) + { + dvd_stop(); + params->dvdplaying = FALSE; + mmsl->UpdateVariable(SCRIPT_VAR_PLAYER_PLAYING); + module_unload("dvd"); + } + } +#ifdef EXTERNAL_PLAYER + if (params->fileplaying) + { + if (player_info_loop() == 1) + { + player_stop(); + params->fileplaying = FALSE; + mmsl->UpdateVariable(SCRIPT_VAR_PLAYER_PLAYING); + } + } +#endif +#ifdef INTERNAL_VIDEO_PLAYER + if (params->videoplaying) + { + if (video_loop() == 1) + { + video_stop(); + params->videoplaying = FALSE; + params->speed = MPEG_SPEED_STOP; + if (mmsl != NULL) + { + mmsl->UpdateVariable(SCRIPT_VAR_PLAYER_SPEED); + mmsl->UpdateVariable(SCRIPT_VAR_PLAYER_PLAYING); + } + } + } +#endif +#ifdef INTERNAL_AUDIO_PLAYER + if (params->audioplaying) + { + if (audio_loop() == 1) + { + audio_delete_filelist(); + audio_stop(); + params->audioplaying = FALSE; + mmsl->UpdateVariable(SCRIPT_VAR_PLAYER_PLAYING); + } + } +#endif + if (params->cddaplaying) + { + if (cdda_player_loop() == 1) + { + cdda_stop(); + params->cddaplaying = FALSE; + mmsl->UpdateVariable(SCRIPT_VAR_PLAYER_PLAYING); + } + } + + if (params->waitinfo) + { + if (player_id3_loop() == 1) + { + params->waitinfo = FALSE; + } + } + + return 0; +} + +int script_get_time(struct timeval *tv) +{ + if (tv == NULL) + return params->curtime; + else + return (int)((tv->tv_sec - params->start_sec) * 1000 + tv->tv_usec / 1000 - params->curtime); +} + +void script_skiptime() +{ + struct timeval tv; + gettimeofday(&tv, NULL); + int next_time = script_get_time(&tv); + params->start_sec += next_time / 1000; +} + +bool script_update_variable(int ID) +{ + return mmsl->UpdateVariable(ID) != 0; +} + +/////////////////////////////////////////////////////// + +void script_drive_callback(CDROM_STATUS status) +{ + bool status_changed = (status != params->status); + if ((status == CDROM_STATUS_TRAYOPEN && params->status != CDROM_STATUS_TRAYOPEN) || + (params->status == CDROM_STATUS_TRAYOPEN && status != CDROM_STATUS_TRAYOPEN) || + (params->status == CDROM_STATUS_UNKNOWN && status != CDROM_STATUS_UNKNOWN)) + { + params->status = status; +#if 1 + msg("* DRIVE.TRAY = %d\n", params->status); +#endif + mmsl->UpdateVariable(SCRIPT_VAR_DRIVE_TRAY); + } + if (status_changed) + { + params->status = status; +#if 1 + msg("* DRIVE.MEDIATYPE = %d\n", params->status); +#endif + mmsl->UpdateVariable(SCRIPT_VAR_DRIVE_MEDIATYPE); + } +} + +void script_set_key(int key) +{ + params->key = key; + + for (int i = 0; button_pairs[i].str != NULL; i++) + { + if (button_pairs[i].value == key) + { + params->keystr = button_pairs[i].str; + break; + } + } +} + +void script_key_callback(int key) +{ + if (params->flash_progress < 0) + { + msg("****** button %s ******\n", get_button_string(key)); + + script_set_key(key); + mmsl->UpdateVariable(SCRIPT_VAR_PAD_KEY); + } +} + +void script_time_callback(int secs) +{ + params->info.cur_time = secs; + params->info.real_cur_time = secs; + mmsl->UpdateVariable(SCRIPT_VAR_PLAYER_TIME); +} + +void script_totaltime_callback(int secs) +{ + params->info.length = secs; + mmsl->UpdateVariable(SCRIPT_VAR_PLAYER_LENGTH); +} + +void script_dvd_menu_callback(bool ) +{ + mmsl->UpdateVariable(SCRIPT_VAR_PLAYER_MENU); +} + +void script_speed_callback(int speed) +{ + if (speed >= 0) + params->speed = (MPEG_SPEED_TYPE)speed; + mmsl->UpdateVariable(SCRIPT_VAR_PLAYER_SPEED); +} + +void script_dvd_chapter_callback(int chapter) +{ + params->info.real_cur_chapter = params->info.cur_chapter = chapter; + mmsl->UpdateVariable(SCRIPT_VAR_PLAYER_CHAPTER); +} + +void script_dvd_title_callback(int title, int numchapters) +{ + params->info.real_cur_title = params->info.cur_title = title; + params->info.num_chapters = numchapters; + mmsl->UpdateVariable(SCRIPT_VAR_PLAYER_TITLE); + mmsl->UpdateVariable(SCRIPT_VAR_PLAYER_NUM_CHAPTERS); +} + +void script_player_saved_callback() +{ + if (mmsl != NULL) + mmsl->UpdateVariable(SCRIPT_VAR_PLAYER_SAVED); +} + +void script_cdda_track_callback(int track) +{ + params->info.real_cur_title = params->info.cur_title = track; + params->info.num_chapters = 0; + mmsl->UpdateVariable(SCRIPT_VAR_PLAYER_TITLE); + mmsl->UpdateVariable(SCRIPT_VAR_PLAYER_NUM_CHAPTERS); +} + +void script_name_callback(const SPString & n) +{ + params->info.name = n; + msg("Title: %s\n", *params->info.name); + mmsl->UpdateVariable(SCRIPT_VAR_PLAYER_NAME); +} + +void script_artist_callback(const SPString & a) +{ + params->info.artist = a; + msg("Artist: %s\n", *params->info.artist); + mmsl->UpdateVariable(SCRIPT_VAR_PLAYER_ARTIST); +} + +void script_audio_info_callback(const SPString & ai) +{ + params->info.audio_info = ai; + mmsl->UpdateVariable(SCRIPT_VAR_PLAYER_AUDIO_INFO); +} + +void script_video_info_callback(const SPString & vi) +{ + params->info.video_info = vi; + mmsl->UpdateVariable(SCRIPT_VAR_PLAYER_VIDEO_INFO); +} + +void script_player_subtitle_callback(const char *s) +{ + params->info.subtitle = s; + mmsl->UpdateVariable(SCRIPT_VAR_PLAYER_SUBTITLE); +} + +void script_error_callback(SCRIPT_ERROR_CODE err) +{ + switch (err) + { + case SCRIPT_ERROR_INVALID: + params->player_error = "invalid"; + break; + case SCRIPT_ERROR_BAD_AUDIO: + params->player_error = "badaudio"; + break; + case SCRIPT_ERROR_BAD_CODEC: + params->player_error = "badvideo"; + break; + case SCRIPT_ERROR_QPEL: + params->player_error = "qpel"; + break; + case SCRIPT_ERROR_GMC: + params->player_error = "gmc"; + break; + case SCRIPT_ERROR_WAIT: + params->player_error = "wait"; + break; + case SCRIPT_ERROR_CORRUPTED: + params->player_error = "corrupted"; + break; + default: + return; + } + + if (params->player_type == PLAYER_TYPE_DVD) + { + if (dvd_getdebug()) + msg("DVD Warning!: %s!\n", *params->player_error); + } +#ifdef EXTERNAL_PLAYER + else if (params->player_type == PLAYER_TYPE_FILE) + { + if (player_getdebug()) + msg("Player Warning!: %s!\n", *params->player_error); + } +#endif + mmsl->UpdateVariable(SCRIPT_VAR_PLAYER_ERROR); +} + +void script_framesize_callback(int width, int height) +{ + if ((params->player_type == PLAYER_TYPE_DVD && dvd_getdebug()) +#ifdef EXTERNAL_PLAYER + || (params->player_type == PLAYER_TYPE_FILE && player_getdebug()) +#endif + ) + { + msg("* FRAME = %dx%d\n", width, height); + } + params->info.width = width; + params->info.height = height; + mmsl->UpdateVariable(SCRIPT_VAR_PLAYER_WIDTH); + mmsl->UpdateVariable(SCRIPT_VAR_PLAYER_HEIGHT); +} + +void script_framerate_callback(int frame_rate) +{ + if ((params->player_type == PLAYER_TYPE_DVD && dvd_getdebug()) +#ifdef EXTERNAL_PLAYER + || (params->player_type == PLAYER_TYPE_FILE && player_getdebug()) +#endif + ) + { + msg("* FRAME_RATE = %d\n", frame_rate); + } + params->info.frame_rate = frame_rate; + mmsl->UpdateVariable(SCRIPT_VAR_PLAYER_FRAME_RATE); +} + +void script_zoom_scroll_reset_callback() +{ + params->hscale = 0; + params->vscale = 0; + params->hscroll = 0; + params->vscroll = 0; + mmsl->UpdateVariable(SCRIPT_VAR_SCREEN_HZOOM); + mmsl->UpdateVariable(SCRIPT_VAR_SCREEN_VZOOM); + mmsl->UpdateVariable(SCRIPT_VAR_SCREEN_HSCROLL); + mmsl->UpdateVariable(SCRIPT_VAR_SCREEN_VSCROLL); +} + +void script_colorspace_callback(int clrs) +{ + if ((params->player_type == PLAYER_TYPE_DVD && dvd_getdebug()) +#ifdef EXTERNAL_PLAYER + || (params->player_type == PLAYER_TYPE_FILE && player_getdebug()) +#endif + ) + { + msg("* COLORSPACE = %d\n", clrs); + } + params->info.clrs = (PLAYER_COLOR_SPACE)clrs; + mmsl->UpdateVariable(SCRIPT_VAR_PLAYER_COLOR_SPACE); +} + +void script_audio_lang_callback(const char *lang) +{ + params->info.dvd_audio_lang = lang; +} + +void script_spu_lang_callback(const char *lang) +{ + params->info.spu_lang = lang; +} + +void script_audio_stream_callback(int stream) +{ + params->info.audio_stream = stream; +} + +void script_spu_stream_callback(int stream) +{ + params->info.spu_stream = stream; +} + +///////////////////////////////////////////////////// + +bool stop_all() +{ + if (params->dvdplaying) + player_dvd_command("stop"); +#ifdef EXTERNAL_PLAYER + if (params->fileplaying) + player_file_command("stop"); +#endif +#ifdef INTERNAL_VIDEO_PLAYER + if (params->videoplaying) + { + return player_video_command("stop"); + } +#endif +#ifdef INTERNAL_AUDIO_PLAYER + if (params->audioplaying) + player_audio_command("stop"); +#endif + if (params->cddaplaying) + player_cdda_command("stop"); + return true; +} + + +bool toggle_tray() +{ + static int in_toggle = 0; + if (in_toggle) + return false; + + in_toggle = 1; + gui.Update(); + + BOOL force_update = FALSE; + CDROM_STATUS status = cdrom_getstatus(&force_update); + bool eject = (status != CDROM_STATUS_TRAYOPEN); + + if (eject) + { + if (!stop_all()) + { + params->need_to_toggle_tray = TRUE; + return false; + } + + // media is being changed - so close CDDA data + cdda_close(); + + // clear all cached lists and playlists + script_explorer_reset(); + + status = CDROM_STATUS_TRAYOPEN; + params->require_next_status = CDROM_STATUS_UNKNOWN; + } + + if (force_update) + params->status = CDROM_STATUS_UNKNOWN; + + script_drive_callback(status); + + msg("* toggle_tray: eject = %d (%d)!!!\n", eject, params->status); + cdrom_eject(eject); + + params->wasejected = TRUE; // to restore fip + in_toggle = 0; + return true; +} + +void disc_changed(CDROM_STATUS st) +{ + if (st != CDROM_STATUS_CURRENT) + params->status = st; + if (is_playing()) + return; + BOOL force_update = FALSE; + CDROM_STATUS status = cdrom_getstatus(&force_update); + + if ((status & CDROM_STATUS_HASDISC) == CDROM_STATUS_HASDISC + && params->require_next_status != CDROM_STATUS_UNKNOWN) + status = params->require_next_status; + + if (params->status != status || force_update) + { + if (status == CDROM_STATUS_NODISC || status == CDROM_STATUS_TRAYOPEN) + params->require_next_status = CDROM_STATUS_UNKNOWN; + if (force_update) + params->status = CDROM_STATUS_UNKNOWN; + params->wasejected = FALSE; + script_drive_callback(status); + } +} + +///////////////////////////////////////////////////////// + +void player_halt() +{ + for (;;) + { + khwl_osd_update(); + } +} + +bool player_turn_onoff(bool on) +{ + // we cannot combine instructions because of different order + if (!on) // OFF + { + // close CD-ROM tray and prepare for shutdown + BOOL force_status = FALSE; + CDROM_STATUS status = cdrom_getstatus(&force_status); + if (status == CDROM_STATUS_TRAYOPEN || !cdrom_isready()) + { + fip_clear(); + fip_write_string("CLOSE"); + cdrom_eject(false); + for (int tries = 0; tries < 80; tries++) + { + //msg("try=%d [status=%d]\n", tries, status); + //gui_update(); + + usleep(250000); + if (cdrom_isready()) + { + status = cdrom_getstatus(&force_status); + if (status != CDROM_STATUS_TRAYOPEN && status != CDROM_STATUS_UNKNOWN) + break; + } + } + // if we stop CDROM before it detects a disc, we won't start it again... + // I don't know a reliable way to check if we can stop - so wait 3 secs... + //status = cdrom_getstatus(); + //if (status != CDROM_STATUS_TRAYOPEN && status != CDROM_STATUS_NODISC) + // usleep(3500000); + } + + params->status = CDROM_STATUS_NODISC; + mmsl->UpdateVariable(SCRIPT_VAR_DRIVE_TRAY); + mmsl->UpdateVariable(SCRIPT_VAR_DRIVE_MEDIATYPE); + + gui.Clear(); + khwl_display_clear(); + fip_clear(); + + gui.SwitchDisplay(false); + khwl_osd_update(); + + cdrom_switch(FALSE); + khwl_ideswitch(FALSE); + } + else // ON + { + fip_write_string("LoAd"); + khwl_ideswitch(TRUE); + + usleep(1000000); + + gui.SwitchDisplay(true); + + usleep(1000000); + + khwl_set_window_frame(params->bigbackleft, params->bigbacktop, params->bigbackright, params->bigbackbottom); + gui.SetBackground(params->bigback); + + usleep(1000000); + + cdrom_switch(TRUE); + } + return true; +} + +bool script_next_vmode() +{ + int vout_max = settings_getmax(SETTING_TVOUT); + int vstd_max = 1;//settings_getmax(SETTING_TVSTANDARD); + int vout = gui.GetTvOut(); + int vstd = gui.GetTvStandard(); + if (vstd >= WINDOW_TVSTANDARD_480P) + { + vout = 0; + vstd = 0; + } + else if (++vout > vout_max) + { + vout = 0; + if (++vstd > vstd_max) + { + vstd = WINDOW_TVSTANDARD_480P; + vout = WINDOW_TVOUT_YPBPR; + } + } + + gui.SetTv((WINDOW_TVOUT)vout, (WINDOW_TVSTANDARD)vstd); + if (!params->dvdplaying && !params->videoplaying) + { + khwl_setvideomode(KHWL_VIDEOMODE_NONE, TRUE); + khwl_set_window_frame(params->bigbackleft, params->bigbacktop, params->bigbackright, params->bigbackbottom); + gui.SetBackground(params->bigback); + } + const char *vstds[] = { "NTSC", "PAL", "480P", "576P", "720P", "1080I" }; + const char *vouts[] = { "C/S-Video", "C/YPbPr", "C/RGB" }; + msg("VMODE set = %s %s\n", vstds[vstd], vouts[vout]); + + mmsl->UpdateVariable(SCRIPT_VAR_SCREEN_TVSTANDARD); + mmsl->UpdateVariable(SCRIPT_VAR_SCREEN_TVOUT); + return true; +} + +//////////////////////////////////////////////////////////////////////// + +void on_kernel_get(int var_id, MmslVariable *var, void *, int , MMSL_OBJECT *) +{ + switch (var_id) + { + case SCRIPT_VAR_KERNEL_POWER: + var->Set(1); + break; + case SCRIPT_VAR_KERNEL_PRINT: + var->Set(""); + break; + case SCRIPT_VAR_KERNEL_FIRMWARE_VERSION: + var->Set(params->fw_ver); + break; + case SCRIPT_VAR_KERNEL_MMSL_VERSION: + var->Set(params->mmsl_ver); + break; + case SCRIPT_VAR_KERNEL_MMSL_ERRORS: + { + const char *errs[] = { "all", "general", "critical", "none" }; + var->Set(errs[params->errors]); + } + break; + case SCRIPT_VAR_KERNEL_RUN: + var->Set(""); + break; + case SCRIPT_VAR_KERNEL_RANDOM: + var->Set(rand() % 32768); + break; + case SCRIPT_VAR_KERNEL_FREQUENCY: + var->Set(khwl_getfrequency()); + break; + case SCRIPT_VAR_KERNEL_CHIP: + var->Set(khwl_gethw()); + break; + case SCRIPT_VAR_KERNEL_FREE_MEMORY: + { + struct sysinfo si; + sysinfo(&si); + var->Set(si.freeram); + } + break; + case SCRIPT_VAR_KERNEL_FLASH_MEMORY: + { + var->Set(flash_get_memory_size()); + } + break; + } +} + +void on_kernel_set(int var_id, MmslVariable *var, void *, int , MMSL_OBJECT *) +{ + switch (var_id) + { + case SCRIPT_VAR_KERNEL_POWER: + if (var->GetInteger() == 2) + player_halt(); + else + { + extern bool is_sleeping, do_sleep, need_to_stop; + is_sleeping = var->GetInteger() == 0; + do_sleep = 1; + if (is_sleeping) + { + if (!stop_all()) + need_to_stop = true; + } + //player_turn_onoff(var->GetInteger() != 0); + } + break; + case SCRIPT_VAR_KERNEL_PRINT: + msg("User: %s\n", *var->GetString()); + break; + case SCRIPT_VAR_KERNEL_MMSL_ERRORS: + { + SPString str = var->GetString(); + if (str.CompareNoCase("none") == 0) + { + params->errors = SCRIPT_ERRORS_NONE; + msg_set_filter(MSG_FILTER_NONE); + } + else if (str.CompareNoCase("general") == 0) + { + params->errors = SCRIPT_ERRORS_GENERAL; + msg_set_filter(MSG_FILTER_ERROR); + } + else if (str.CompareNoCase("critical") == 0) + { + params->errors = SCRIPT_ERRORS_CRITICAL; + msg_set_filter(MSG_FILTER_CRITICAL); + } + else + { + params->errors = SCRIPT_ERRORS_ALL; + msg_set_filter(MSG_FILTER_ALL); + } + } + break; + case SCRIPT_VAR_KERNEL_RUN: + { + SPString str = var->GetString(); + str.TrimLeft(); + str.TrimRight(); + char *args[20]; + args[0] = &str[0]; + bool in_quotes = false; + int curarg = 0; + for (int i = 0; i < str.GetLength(); i++) + { + if (str[i] == '\"') + { + in_quotes = !in_quotes; + } + if (str[i] == ' ' && !in_quotes) + { + str[i] = '\0'; + args[++curarg] = &str[i + 1]; + if (curarg >= 20) + break; + } + } + args[++curarg] = NULL; + if (vfork() == 0) + { + exec_file(args[0], (const char **)args); + } + } + break; + case SCRIPT_VAR_KERNEL_FREQUENCY: + khwl_setfrequency(var->GetInteger()); + break; + } +} + +//////////////////////////////////////////////////////////////////////// + +void on_screen_get(int var_id, MmslVariable *var, void *, int , MMSL_OBJECT *) +{ + if (var == NULL) + return; + switch (var_id) + { + case SCRIPT_VAR_SCREEN_SWITCH: + var->Set(gui.IsDisplaySwitchedOn()); + break; + case SCRIPT_VAR_SCREEN_UPDATE: + var->Set(gui.IsUpdateEnabled()); + break; + case SCRIPT_VAR_SCREEN_TVSTANDARD: + StringPair::Set(var, tvstandard_pairs, gui.GetTvStandard()); + break; + case SCRIPT_VAR_SCREEN_TVOUT: + StringPair::Set(var, tvout_pairs, gui.GetTvOut()); + break; + case SCRIPT_VAR_SCREEN_FULLSCREEN: + var->Set(gui.IsOsdFullscreen()); + break; + case SCRIPT_VAR_SCREEN_LEFT: + var->Set(gui.left); + break; + case SCRIPT_VAR_SCREEN_TOP: + var->Set(gui.top); + break; + case SCRIPT_VAR_SCREEN_RIGHT: + var->Set(gui.right); + break; + case SCRIPT_VAR_SCREEN_BOTTOM: + var->Set(gui.bottom); + break; + case SCRIPT_VAR_SCREEN_PALETTE: + var->Set(params->pal); + break; + case SCRIPT_VAR_SCREEN_PALIDX: + var->Set(params->pal_idx); + break; + case SCRIPT_VAR_SCREEN_PALALPHA: + { + BYTE *p = gui.GetPaletteEntry(params->pal_idx); + var->Set((p != NULL) ? p[0] : 0); + } + break; + case SCRIPT_VAR_SCREEN_PALCOLOR: + { + BYTE *p = gui.GetPaletteEntry(params->pal_idx); + BYTE r, g, b; + khwl_tvyuvtovgargb(p[1], p[2], p[3], &r, &g, &b); + var->Set((p != NULL) ? ((r << 16) | (g << 8) | b) : 0); + } + break; + case SCRIPT_VAR_SCREEN_FONT: + var->Set(params->font); + break; + case SCRIPT_VAR_SCREEN_COLOR: + var->Set(gui.GetColor()); + break; + case SCRIPT_VAR_SCREEN_BACKCOLOR: + var->Set(gui.GetBkColor()); + break; + case SCRIPT_VAR_SCREEN_TRCOLOR: + var->Set(gui.GetTransparentColor()); + break; + case SCRIPT_VAR_SCREEN_HALIGN: + { + const char *ha[] = { "left", "center", "right" }; + var->Set(ha[gui.GetHAlign()]); + } + break; + case SCRIPT_VAR_SCREEN_VALIGN: + { + const char *ha[] = { "top", "center", "bottom" }; + var->Set(ha[gui.GetVAlign()]); + } + break; + case SCRIPT_VAR_SCREEN_BACK: + var->Set(params->back); + break; + case SCRIPT_VAR_SCREEN_BACK_LEFT: + var->Set(params->backleft); + break; + case SCRIPT_VAR_SCREEN_BACK_TOP: + var->Set(params->backtop); + break; + case SCRIPT_VAR_SCREEN_BACK_RIGHT: + var->Set(params->backright); + break; + case SCRIPT_VAR_SCREEN_BACK_BOTTOM: + var->Set(params->backbottom); + break; + case SCRIPT_VAR_SCREEN_PRELOAD: + var->Set(""); + break; + case SCRIPT_VAR_SCREEN_HZOOM: + var->Set(params->hscale); + break; + case SCRIPT_VAR_SCREEN_VZOOM: + var->Set(params->vscale); + break; + case SCRIPT_VAR_SCREEN_ROTATE: + var->Set(params->rotate * 90); + break; + case SCRIPT_VAR_SCREEN_HSCROLL: + var->Set(params->hscroll); + break; + case SCRIPT_VAR_SCREEN_VSCROLL: + var->Set(params->vscroll); + break; + case SCRIPT_VAR_SCREEN_BRIGHTNESS: + { + int val; + khwl_get_display_settings(&val, NULL, NULL); + var->Set(val); + } + break; + case SCRIPT_VAR_SCREEN_CONTRAST: + { + int val; + khwl_get_display_settings(NULL, &val, NULL); + var->Set(val); + } + break; + case SCRIPT_VAR_SCREEN_SATURATION: + { + int val; + khwl_get_display_settings(NULL, NULL, &val); + var->Set(val); + } + break; + } +} + +void on_screen_set(int var_id, MmslVariable *var, void *, int , MMSL_OBJECT *) +{ + if (var == NULL) + return; + switch (var_id) + { + case SCRIPT_VAR_SCREEN_SWITCH: + if (var->GetString().CompareNoCase("next") == 0) + script_next_vmode(); + else + { + bool onoff = var->GetInteger() != 0; + gui.SwitchDisplay(onoff); + if (onoff) + { + khwl_set_window_frame(params->bigbackleft, params->bigbacktop, params->bigbackright, params->bigbackbottom); + gui.SetBackground(params->bigback); + } + } + break; + case SCRIPT_VAR_SCREEN_UPDATE: + { + bool upd = false; + if (var->GetString().CompareNoCase("now") == 0) + { + upd = true; + gui.Update(); + } + else + { + upd = var->GetInteger() != 0; + gui.UpdateEnable(upd); + } + if (upd && params->need_setwindow) + { + khwl_set_window(-1, -1, -1, -1, params->hscale, params->vscale, params->hscroll, params->vscroll); + params->need_setwindow = false; + } + } + break; + case SCRIPT_VAR_SCREEN_TVSTANDARD: + gui.SetTv(WINDOW_TVOUT_UNKNOWN, (WINDOW_TVSTANDARD)StringPair::Get(var, tvstandard_pairs, -1)); + khwl_set_window_frame(params->bigbackleft, params->bigbacktop, params->bigbackright, params->bigbackbottom); + gui.SetBackground(params->bigback); + break; + case SCRIPT_VAR_SCREEN_TVOUT: + gui.SetTv((WINDOW_TVOUT)StringPair::Get(var, tvout_pairs, -1), WINDOW_TVSTANDARD_UNKNOWN); + khwl_set_window_frame(params->bigbackleft, params->bigbacktop, params->bigbackright, params->bigbackbottom); + gui.SetBackground(params->bigback); + break; + case SCRIPT_VAR_SCREEN_FULLSCREEN: + gui.SetOsdFullscreen(var->GetInteger() != 0); + break; + case SCRIPT_VAR_SCREEN_PALETTE: + { + SPString pal = var->GetString(); + if (gui.LoadPalette(pal)) + { + params->pal = pal; + console->UpdateFont(); + } + } + break; + case SCRIPT_VAR_SCREEN_PALIDX: + params->pal_idx = var->GetInteger(); + break; + case SCRIPT_VAR_SCREEN_PALALPHA: + { + BYTE *p = gui.GetPaletteEntry(params->pal_idx); + if (p != NULL) + p[0] = (BYTE)var->GetInteger(); + } + break; + case SCRIPT_VAR_SCREEN_PALCOLOR: + { + BYTE *p = gui.GetPaletteEntry(params->pal_idx); + if (p != NULL) + { + SPString str = var->GetString(); + str.TrimLeft(); + BYTE r, g, b; + if (str.GetLength() >= 7 && str[0] == '#') + { + r = (BYTE)(hex2char(str[1]) * 16 + hex2char(str[2])); + g = (BYTE)(hex2char(str[3]) * 16 + hex2char(str[4])); + b = (BYTE)(hex2char(str[5]) * 16 + hex2char(str[6])); + } else + { + int c24 = var->GetInteger(); + r = (BYTE)((c24 >> 16) & 0xff); + g = (BYTE)((c24 >> 8) & 0xff); + b = (BYTE)((c24) & 0xff); + } + khwl_vgargbtotvyuv(r, g, b, p + 1, p + 2, p + 3); + } + } + break; + case SCRIPT_VAR_SCREEN_FONT: + params->font = var->GetString(); + break; + case SCRIPT_VAR_SCREEN_COLOR: + gui.SetColor(var->GetInteger()); + break; + case SCRIPT_VAR_SCREEN_BACKCOLOR: + gui.SetBkColor(var->GetInteger()); + break; + case SCRIPT_VAR_SCREEN_TRCOLOR: + gui.SetTransparentColor(var->GetInteger()); + console->UpdateFont(); + break; + case SCRIPT_VAR_SCREEN_HALIGN: + { + SPString str = var->GetString(); + if (str.CompareNoCase("center") == 0) + gui.SetHAlign(WINDOW_ALIGN_CENTER); + else if (str.CompareNoCase("right") == 0) + gui.SetHAlign(WINDOW_ALIGN_RIGHT); + else + gui.SetHAlign(WINDOW_ALIGN_LEFT); + } + break; + case SCRIPT_VAR_SCREEN_VALIGN: + { + SPString str = var->GetString(); + if (str.CompareNoCase("center") == 0) + gui.SetHAlign(WINDOW_ALIGN_CENTER); + else if (str.CompareNoCase("right") == 0) + gui.SetHAlign(WINDOW_ALIGN_RIGHT); + else + gui.SetHAlign(WINDOW_ALIGN_LEFT); + } + break; + case SCRIPT_VAR_SCREEN_BACK: + { + SPString back = var->GetString(); + if (back.CompareNoCase(params->back) != 0) + { + params->back = back; + params->hscale = 100; + params->vscale = 100; + params->hscroll = 0; + params->vscroll = 0; + params->rotate = -1; + + int bw = params->backright - params->backleft + 1; + int bh = params->backbottom - params->backtop + 1; + if (bw >= params->bigback_width || bh >= params->bigback_height || back.GetLength() == 0) + { + params->bigback_width = bw; + params->bigback_height = bh; + params->bigback = params->back; + params->bigbackleft = params->backleft; + params->bigbacktop = params->backtop; + params->bigbackright = params->backright; + params->bigbackbottom = params->backbottom; + } + + khwl_set_window_frame(params->backleft, params->backtop, params->backright, params->backbottom); + + int old_rotate = params->rotate; + if (!gui.SetBackground(params->back, params->hscale, params->vscale, + params->hscroll, params->vscroll, ¶ms->rotate)) + { + params->back = ""; + mmsl->UpdateVariable(SCRIPT_VAR_SCREEN_BACK); + if (params->rotate != old_rotate) + mmsl->UpdateVariable(SCRIPT_VAR_SCREEN_ROTATE); + } + + params->need_setwindow = false; + } + } + break; + case SCRIPT_VAR_SCREEN_BACK_LEFT: + params->backleft = var->GetInteger(); + if (params->backleft < 0) + { + params->backleft = 0; + mmsl->UpdateVariable(SCRIPT_VAR_SCREEN_BACK_LEFT); + } + if (params->backleft > 719) + { + params->backleft = 719; + mmsl->UpdateVariable(SCRIPT_VAR_SCREEN_BACK_LEFT); + } + break; + case SCRIPT_VAR_SCREEN_BACK_TOP: + params->backtop = var->GetInteger(); + if (params->backtop < 0) + { + params->backtop = 0; + mmsl->UpdateVariable(SCRIPT_VAR_SCREEN_BACK_TOP); + } + if (params->backtop > 479) + { + params->backtop = 479; + mmsl->UpdateVariable(SCRIPT_VAR_SCREEN_BACK_TOP); + } + break; + case SCRIPT_VAR_SCREEN_BACK_RIGHT: + params->backright = var->GetInteger(); + if (params->backright < 0) + { + params->backright = 0; + mmsl->UpdateVariable(SCRIPT_VAR_SCREEN_BACK_RIGHT); + } + if (params->backright > 719) + { + params->backright = 719; + mmsl->UpdateVariable(SCRIPT_VAR_SCREEN_BACK_RIGHT); + } + break; + case SCRIPT_VAR_SCREEN_BACK_BOTTOM: + params->backbottom = var->GetInteger(); + if (params->backbottom < 0) + { + params->backbottom = 0; + mmsl->UpdateVariable(SCRIPT_VAR_SCREEN_BACK_BOTTOM); + } + if (params->backbottom > 479) + { + params->backbottom = 479; + mmsl->UpdateVariable(SCRIPT_VAR_SCREEN_BACK_BOTTOM); + } + break; + case SCRIPT_VAR_SCREEN_PRELOAD: + { + SPString fname = var->GetString(); + if (fname == "") + { + guiimg->ClearImageData(); +#if 0 // !!!!!!!!!!!!!!!!!!! + guifonts->ClearFontData(); +#endif + } + else + { + if (fname.FindNoCase(".fon") > 0 || fname.FindNoCase(".fnt") > 0) + guifonts->GetFont(fname); + else if (fname.FindNoCase(".gif") > 0) + guiimg->GetImageData(fname); + } + } + break; + case SCRIPT_VAR_SCREEN_HZOOM: + { + SPString z = var->GetString(); + if (z.CompareNoCase("in") == 0) + params->hscale += 5; + else if (z.CompareNoCase("out") == 0) + params->hscale -= 5; + else + params->hscale = var->GetInteger(); + if (params->hscale < 25) + params->hscale = 25; + if (params->hscale > 400) + params->hscale = 400; + params->hscroll = 0; + params->vscroll = 0; + if (gui.IsUpdateEnabled()) + { + khwl_set_window(-1, -1, -1, -1, params->hscale, params->vscale, params->hscroll, params->vscroll); + params->need_setwindow = false; + } + else + params->need_setwindow = true; + } + break; + case SCRIPT_VAR_SCREEN_VZOOM: + { + SPString z = var->GetString(); + if (z.CompareNoCase("in") == 0) + params->vscale += 5; + else if (z.CompareNoCase("out") == 0) + params->vscale -= 5; + else + params->vscale = var->GetInteger(); + if (params->vscale < 25) + params->vscale = 25; + if (params->vscale > 400) + params->vscale = 400; + params->hscroll = 0; + params->vscroll = 0; + if (gui.IsUpdateEnabled()) + { + khwl_set_window(-1, -1, -1, -1, params->hscale, params->vscale, params->hscroll, params->vscroll); + params->need_setwindow = false; + } + else + params->need_setwindow = true; + } + break; + case SCRIPT_VAR_SCREEN_ROTATE: + { + if (var->GetString().CompareNoCase("auto") == 0) + params->rotate = -1; + else + { + params->rotate = var->GetInteger() / 90; + if (params->rotate < 0) + params->rotate = (-params->rotate/4+1)*4+params->rotate; + params->rotate %= 4; + } + params->hscroll = 0; + params->vscroll = 0; + int old_rotate = params->rotate; + gui.SetBackground(params->back, params->hscale, params->vscale, params->hscroll, params->vscroll, ¶ms->rotate); + if (params->rotate != old_rotate) + mmsl->UpdateVariable(SCRIPT_VAR_SCREEN_ROTATE); + } + break; + case SCRIPT_VAR_SCREEN_HSCROLL: + params->hscroll = var->GetInteger(); + if (gui.IsUpdateEnabled()) + { + khwl_set_window(-1, -1, -1, -1, params->hscale, params->vscale, params->hscroll, params->vscroll); + params->need_setwindow = false; + } + else + params->need_setwindow = true; + break; + case SCRIPT_VAR_SCREEN_VSCROLL: + params->vscroll = var->GetInteger(); + if (gui.IsUpdateEnabled()) + { + khwl_set_window(-1, -1, -1, -1, params->hscale, params->vscale, params->hscroll, params->vscroll); + params->need_setwindow = false; + } + else + params->need_setwindow = true; + break; + case SCRIPT_VAR_SCREEN_BRIGHTNESS: + khwl_set_display_settings(var->GetInteger(), -1, -1); + break; + case SCRIPT_VAR_SCREEN_CONTRAST: + khwl_set_display_settings(-1, var->GetInteger(), -1); + break; + case SCRIPT_VAR_SCREEN_SATURATION: + khwl_set_display_settings(-1, -1, var->GetInteger()); + break; + } +} + +//////////////////////////////////////////////////////////////////////// + + +const char *get_button_string(int but) +{ + static const StringPair *pairs[2] = { front_button_pairs2, button_pairs }; + for (int t = 0; t < 2; t++) + { + for (int i = 0; pairs[t][i].str != NULL; i++) + { + if (pairs[t][i].value == but) + return pairs[t][i].str; + } + } + return "?"; +} + +void on_pad_get(int var_id, MmslVariable *var, void *, int , MMSL_OBJECT *) +{ + if (var == NULL) + return; + switch (var_id) + { + case SCRIPT_VAR_PAD_KEY: + var->Set(params->keystr); + break; + case SCRIPT_VAR_PAD_DISPLAY: + case SCRIPT_VAR_PAD_CLEAR: + case SCRIPT_VAR_PAD_SET: + var->Set(""); + break; + } +} + +void on_pad_set(int var_id, MmslVariable *var, void *, int , MMSL_OBJECT *) +{ + if (var == NULL) + return; + switch (var_id) + { + case SCRIPT_VAR_PAD_DISPLAY: + fip_write_string(var->GetString()); + break; + case SCRIPT_VAR_PAD_SET: + fip_write_special(StringPair::Get(var, pad_special_pairs, -1), TRUE); + break; + case SCRIPT_VAR_PAD_KEY: + script_set_key(StringPair::Get(var, button_pairs, 0)); + break; + case SCRIPT_VAR_PAD_CLEAR: + { + SPString str = var->GetString(); + if (str.CompareNoCase("all") == 0) + fip_clear(); + else + fip_write_special(StringPair::Get(var, pad_special_pairs, -1), FALSE); + } + break; + } +} + +//////////////////////////////////////////////////////////////////////// + +void on_drive_get(int var_id, MmslVariable *var, void *, int , MMSL_OBJECT *) +{ + if (var == NULL) + return; + switch (var_id) + { + case SCRIPT_VAR_DRIVE_MEDIATYPE: + StringPair::Set(var, mediatype_pairs, params->status); + break; + case SCRIPT_VAR_DRIVE_TRAY: + if (params->status == CDROM_STATUS_NODISC || + (params->status & CDROM_STATUS_HASDISC) == CDROM_STATUS_HASDISC) + var->Set("close"); + else + var->Set("open"); + break; + } +} + +void on_drive_set(int var_id, MmslVariable *var, void *, int , MMSL_OBJECT *) +{ + if (var == NULL) + return; + switch (var_id) + { + case SCRIPT_VAR_DRIVE_TRAY: + { + BOOL force_status = FALSE; + CDROM_STATUS status = cdrom_getstatus(&force_status); + SPString str = var->GetString(); + if (str.CompareNoCase("toggle") == 0 || + (str.CompareNoCase("open") == 0 && status != CDROM_STATUS_TRAYOPEN) || + (str.CompareNoCase("close") == 0 && status == CDROM_STATUS_TRAYOPEN) + ) + { + toggle_tray(); + } + } + break; + case SCRIPT_VAR_DRIVE_MEDIATYPE: + { + SPString str = var->GetString(); + if (params->status == CDROM_STATUS_HAS_DVD && str.CompareNoCase("iso") == 0) + { + params->require_next_status = (params->saved_iso_status != CDROM_STATUS_UNKNOWN) ? + params->saved_iso_status : CDROM_STATUS_HAS_ISO; + disc_changed(CDROM_STATUS_CURRENT); + } + else if ((params->status == CDROM_STATUS_HAS_ISO || params->status == CDROM_STATUS_HAS_MIXED) && str.CompareNoCase("dvd") == 0) + { + params->saved_iso_status = params->status; + params->require_next_status = CDROM_STATUS_HAS_DVD; + disc_changed(CDROM_STATUS_CURRENT); + } + else + { + params->saved_iso_status = params->status; + params->require_next_status = CDROM_STATUS_UNKNOWN; + disc_changed(CDROM_STATUS_UNKNOWN); + } + } + break; + } +} + +//////////////////////////////////////////////////////////////////////// + +void on_settings_get(int var_id, MmslVariable *var, void *, int , MMSL_OBJECT *) +{ + if (var == NULL) + return; + switch (var_id) + { + case SCRIPT_VAR_SETTINGS_COMMAND: + var->Set(""); + break; + case SCRIPT_VAR_SETTINGS_AUDIOOUT: + StringPair::Set(var, audioout_pairs, settings_get(SETTING_AUDIOOUT)); + break; + case SCRIPT_VAR_SETTINGS_TVTYPE: + StringPair::Set(var, tvtype_pairs, settings_get(SETTING_TVTYPE)); + break; + case SCRIPT_VAR_SETTINGS_TVSTANDARD: + StringPair::Set(var, tvstandard_pairs, settings_get(SETTING_TVSTANDARD)); + break; + case SCRIPT_VAR_SETTINGS_TVOUT: + StringPair::Set(var, tvout_pairs, settings_get(SETTING_TVOUT)); + break; + case SCRIPT_VAR_SETTINGS_DVI: + var->Set(settings_get(SETTING_DVI)); + break; + case SCRIPT_VAR_SETTINGS_HQ_JPEG: + var->Set(settings_get(SETTING_HQ_JPEG)); + break; + case SCRIPT_VAR_SETTINGS_HDD_SPEED: + StringPair::Set(var, hddspeed_pairs, settings_get(SETTING_HDD_SPEED)); + break; + case SCRIPT_VAR_SETTINGS_DVD_PARENTAL: + var->Set(settings_get(SETTING_DVD_PARENTAL)); + break; + case SCRIPT_VAR_SETTINGS_DVD_MV: + var->Set(settings_get(SETTING_DVD_MV)); + break; + case SCRIPT_VAR_SETTINGS_DVD_LANG_MENU: + case SCRIPT_VAR_SETTINGS_DVD_LANG_AUDIO: + case SCRIPT_VAR_SETTINGS_DVD_LANG_SPU: + { + char str[3] = { 0 }; + SETTING_SET sset = SETTING_VERSION; + switch (var_id) + { + case SCRIPT_VAR_SETTINGS_DVD_LANG_MENU: + sset = SETTING_DVD_LANG_MENU; + break; + case SCRIPT_VAR_SETTINGS_DVD_LANG_AUDIO: + sset = SETTING_DVD_LANG_AUDIO; + break; + default: + sset = SETTING_DVD_LANG_SPU; + } + int v = settings_get(sset); + if (v > 0) + { + str[0] = (char)((v >> 8) & 0xff); + str[1] = (char)((v) & 0xff); + var->Set(str); + } else + var->Set(""); + } + break; + case SCRIPT_VAR_SETTINGS_VOLUME: + var->Set(settings_get(SETTING_VOLUME)); + break; + case SCRIPT_VAR_SETTINGS_BALANCE: + var->Set(settings_get(SETTING_BALANCE) - 100); + break; + case SCRIPT_VAR_SETTINGS_BRIGHTNESS: + var->Set(GetScreenAdjustmentSetting(0)); + break; + case SCRIPT_VAR_SETTINGS_CONTRAST: + var->Set(GetScreenAdjustmentSetting(1)); + break; + case SCRIPT_VAR_SETTINGS_SATURATION: + var->Set(GetScreenAdjustmentSetting(2)); + break; + case SCRIPT_VAR_SETTINGS_USER1: + case SCRIPT_VAR_SETTINGS_USER2: + case SCRIPT_VAR_SETTINGS_USER3: + case SCRIPT_VAR_SETTINGS_USER4: + case SCRIPT_VAR_SETTINGS_USER5: + case SCRIPT_VAR_SETTINGS_USER6: + case SCRIPT_VAR_SETTINGS_USER7: + case SCRIPT_VAR_SETTINGS_USER8: + case SCRIPT_VAR_SETTINGS_USER9: + case SCRIPT_VAR_SETTINGS_USER10: + case SCRIPT_VAR_SETTINGS_USER11: + case SCRIPT_VAR_SETTINGS_USER12: + case SCRIPT_VAR_SETTINGS_USER13: + case SCRIPT_VAR_SETTINGS_USER14: + case SCRIPT_VAR_SETTINGS_USER15: + case SCRIPT_VAR_SETTINGS_USER16: + { + SETTING_SET idx = (SETTING_SET)(SETTING_GUI_0 + var_id - SCRIPT_VAR_SETTINGS_USER1); + var->Set(settings_get(idx)); + } + break; + } +} + +void on_settings_set(int var_id, MmslVariable *var, void *, int , MMSL_OBJECT *) +{ + if (var == NULL) + return; + switch (var_id) + { + case SCRIPT_VAR_SETTINGS_COMMAND: + { + SPString cmd = var->GetString(); + if (cmd.CompareNoCase("defaults") == 0) + { + settings_init(true); + for (int i = SCRIPT_VAR_SETTINGS_AUDIOOUT; i <= SCRIPT_VAR_SETTINGS_USER16; i++) + mmsl->UpdateVariable(i); + } + } + break; + case SCRIPT_VAR_SETTINGS_AUDIOOUT: + settings_set(SETTING_AUDIOOUT, StringPair::Get(var, audioout_pairs, 0)); + break; + case SCRIPT_VAR_SETTINGS_TVTYPE: + settings_set(SETTING_TVTYPE, StringPair::Get(var, tvtype_pairs, 0)); + break; + case SCRIPT_VAR_SETTINGS_TVSTANDARD: + settings_set(SETTING_TVSTANDARD, StringPair::Get(var, tvstandard_pairs, 1)); + break; + case SCRIPT_VAR_SETTINGS_TVOUT: + settings_set(SETTING_TVOUT, StringPair::Get(var, tvout_pairs, 0)); + break; + case SCRIPT_VAR_SETTINGS_DVI: + settings_set(SETTING_DVI, var->GetInteger()); + break; + case SCRIPT_VAR_SETTINGS_HQ_JPEG: + { + int val = var->GetInteger(); + val %= 2; + settings_set(SETTING_HQ_JPEG, val); + } + break; + case SCRIPT_VAR_SETTINGS_HDD_SPEED: + if (var->GetString().CompareNoCase("next") == 0) + { + int val = settings_get(SETTING_HDD_SPEED); + val = (val + 1) % (settings_getmax(SETTING_HDD_SPEED) + 1); + settings_set(SETTING_HDD_SPEED, val); + break; + } + settings_set(SETTING_HDD_SPEED, StringPair::Get(var, hddspeed_pairs, 0)); + break; + case SCRIPT_VAR_SETTINGS_DVD_PARENTAL: + settings_set(SETTING_DVD_PARENTAL, var->GetInteger()); + break; + case SCRIPT_VAR_SETTINGS_DVD_MV: + settings_set(SETTING_DVD_MV, var->GetInteger()); + break; + case SCRIPT_VAR_SETTINGS_DVD_LANG_MENU: + case SCRIPT_VAR_SETTINGS_DVD_LANG_AUDIO: + case SCRIPT_VAR_SETTINGS_DVD_LANG_SPU: + { + SETTING_SET sset = SETTING_VERSION; + switch (var_id) + { + case SCRIPT_VAR_SETTINGS_DVD_LANG_MENU: + sset = SETTING_DVD_LANG_MENU; + break; + case SCRIPT_VAR_SETTINGS_DVD_LANG_AUDIO: + sset = SETTING_DVD_LANG_AUDIO; + break; + default: + sset = SETTING_DVD_LANG_SPU; + } + SPString lang = var->GetString(); + if (isalpha(lang[0]) && isalpha(lang[1])) + { + settings_set(sset, (lang[0] << 8) | lang[1]); + } + } + break; + case SCRIPT_VAR_SETTINGS_VOLUME: + settings_set(SETTING_VOLUME, var->GetInteger()); + break; + case SCRIPT_VAR_SETTINGS_BALANCE: + settings_set(SETTING_BALANCE, var->GetInteger() + 100); + break; + case SCRIPT_VAR_SETTINGS_BRIGHTNESS: + SetScreenAdjustmentSetting(0, var->GetInteger()); + break; + case SCRIPT_VAR_SETTINGS_CONTRAST: + SetScreenAdjustmentSetting(1, var->GetInteger()); + break; + case SCRIPT_VAR_SETTINGS_SATURATION: + SetScreenAdjustmentSetting(2, var->GetInteger()); + break; + case SCRIPT_VAR_SETTINGS_USER1: + case SCRIPT_VAR_SETTINGS_USER2: + case SCRIPT_VAR_SETTINGS_USER3: + case SCRIPT_VAR_SETTINGS_USER4: + case SCRIPT_VAR_SETTINGS_USER5: + case SCRIPT_VAR_SETTINGS_USER6: + case SCRIPT_VAR_SETTINGS_USER7: + case SCRIPT_VAR_SETTINGS_USER8: + case SCRIPT_VAR_SETTINGS_USER9: + case SCRIPT_VAR_SETTINGS_USER10: + case SCRIPT_VAR_SETTINGS_USER11: + case SCRIPT_VAR_SETTINGS_USER12: + case SCRIPT_VAR_SETTINGS_USER13: + case SCRIPT_VAR_SETTINGS_USER14: + case SCRIPT_VAR_SETTINGS_USER15: + case SCRIPT_VAR_SETTINGS_USER16: + { + SETTING_SET idx = (SETTING_SET)(SETTING_GUI_0 + var_id - SCRIPT_VAR_SETTINGS_USER1); + settings_set(idx, var->GetInteger()); + } + break; + + } +} + +//////////////////////////////////////////////////////////////////////// + +void on_flash_get(int var_id, MmslVariable *var, void *, int , MMSL_OBJECT *) +{ + if (var == NULL) + return; + switch (var_id) + { + case SCRIPT_VAR_FLASH_FILE: + var->Set(params->flash_file); + break; + case SCRIPT_VAR_FLASH_ADDRESS: + var->Set(params->flash_address < 0 ? 0 : params->flash_address); + break; + case SCRIPT_VAR_FLASH_PROGRESS: + var->Set(params->flash_progress); + break; + } +} + +void on_flash_set(int var_id, MmslVariable *var, void *, int , MMSL_OBJECT *) +{ + if (var == NULL) + return; + switch (var_id) + { + case SCRIPT_VAR_FLASH_FILE: + params->flash_file = var->GetString(); + break; + case SCRIPT_VAR_FLASH_ADDRESS: + params->flash_address = var->GetInteger(); + break; + } + + if (var_id == SCRIPT_VAR_FLASH_FILE || var_id == SCRIPT_VAR_FLASH_ADDRESS) + { + if (params->flash_file != "" && params->flash_address <= 0) + { + params->flash_address = get_firmware_address(); + } + if (params->flash_file != "" && params->flash_address > 0) + { + params->flash_progress = -1; + params->flash_p = -1; + msg("FLASH %s at 0x%x...\n", *params->flash_file, params->flash_address); + + stop_all(); + cdda_close(); + + int ret = flash_file(params->flash_file, params->flash_address); + if (ret < 0) + { + msg_error("Cannot flash file %s at address 0x%x (err=%d).\n", *params->flash_file, + params->flash_address, ret); + mmsl->UpdateVariable(SCRIPT_VAR_FLASH_PROGRESS); + } + else + params->flash_progress = 0; + } + + } +} + diff --git a/src/script.h b/src/script.h new file mode 100644 index 0000000..e5744ed --- /dev/null +++ b/src/script.h @@ -0,0 +1,147 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - script wrapper header file + * \file script.h + * \author bombur + * \version 0.1 + * \date 2.08.2004 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#ifndef SP_SCRIPT_H +#define SP_SCRIPT_H + +#include + +//////////////////////////////////////////////////////////////////// +#ifdef __cplusplus +extern "C" { +#endif + +enum SCRIPT_ERROR_CODE +{ + SCRIPT_ERROR_INVALID = 0, + SCRIPT_ERROR_BAD_AUDIO, + SCRIPT_ERROR_BAD_CODEC, + SCRIPT_ERROR_QPEL, + SCRIPT_ERROR_GMC, + SCRIPT_ERROR_WAIT, + SCRIPT_ERROR_CORRUPTED, +}; + +/// General program cycle +int cycle(bool gfx_only = false); + +/// Initialize script variables & objects +int script_init(); + +/// Deinit script defs. +int script_deinit(); + +/// Update script data (call triggers if something changed) +int script_update(bool gfx_only = false); + +/// Get the current time elapsed since script was started, in milliseconds. +int get_curtime(); + +/// Skip timer for all time elapsed since the last call of script_update(). Used for sleeping. +void script_skiptime(); + +bool script_update_variable(int ID); + +/// 'ison' is the next status +bool player_turn_onoff(bool on); +void player_halt(); + +bool script_next_vmode(); + +void script_key_callback(int key); +void script_drive_callback(CDROM_STATUS); + +void script_time_callback(int secs); +void script_totaltime_callback(int secs); + +void script_error_callback(SCRIPT_ERROR_CODE); + +void script_name_callback(const SPString &); +void script_artist_callback(const SPString &); +void script_audio_info_callback(const SPString &); +void script_video_info_callback(const SPString &); +void script_framesize_callback(int width, int height); +void script_framerate_callback(int frame_rate); +void script_zoom_scroll_reset_callback(); +void script_colorspace_callback(int clrs); +void script_speed_callback(int speed = -1); +void script_player_saved_callback(); + +void script_player_subtitle_callback(const char *); + +void script_dvd_menu_callback(bool ismenu); +void script_dvd_chapter_callback(int chapter); +void script_dvd_title_callback(int title, int numchapters); +void script_cdda_track_callback(int track); +void script_audio_lang_callback(const char *lang); +void script_spu_lang_callback(const char *lang); +void script_audio_stream_callback(int stream); +void script_spu_stream_callback(int stream); + +const char *get_button_string(int); +bool stop_all(); +bool toggle_tray(); +bool start_iso(); +bool start_dvd(); +bool stop_dvd(); +void disc_changed(CDROM_STATUS = CDROM_STATUS_CURRENT); +bool is_internal_playing(); +bool is_playing(); + +/// Get current time (if tv == NULL) or delta-time, in msec. +int script_get_time(struct timeval *tv); + +void player_update_source(char *filepath); + +//////////////////////////////////////////////////// + +enum SCRIPT_TIMER_OBJECT_TYPE +{ + SCRIPT_OBJECT_UPDATE = 0, + SCRIPT_OBJECT_TIMER, +}; + +class ScriptTimerObject +{ +public: + /// ctor + ScriptTimerObject() + { + prev = next = NULL; + } + +public: + MMSL_OBJECT obj; + int var_ID; + SCRIPT_TIMER_OBJECT_TYPE type; + + ScriptTimerObject *prev, *next; +}; + + +#ifdef __cplusplus +} +#endif + +#endif // of SP_SCRIPT_H diff --git a/src/settings.cpp b/src/settings.cpp new file mode 100644 index 0000000..8422483 --- /dev/null +++ b/src/settings.cpp @@ -0,0 +1,231 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - settings setting/getting source file + * \file settings.cpp + * \author bombur + * \version 0.1 + * \date 4.07.2004 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include "settings.h" + +const DWORD cur_version = (DWORD)(SP_VERSION_MASK | 0x0D); + +static int num_settings = 0; +static bool was_reset = false; + +static struct +{ + DWORD size; + DWORD def; + DWORD max; + DWORD value; + int addr; +} settings[max_settings] = +{ + { 4, cur_version, 0xFFFFFFFF }, // SETTING_VERSION + + { 1, 0, 1, }, // SETTING_AUDIOOUT + + { 1, 0, 3, }, // SETTING_HDTV + + { 4, 0, 255, }, // SETTING_DVI + + { 1, 1, 5, }, // SETTING_TVSTANDARD // 1 = "PAL" + { 1, 0, 2, }, // SETTING_TVOUT // 0 = "C/S-Video" + + { 1, 0, 3, }, // SETTING_TVTYPE, // 0 = 4:3 letterbox + + { 1, 0, 1, }, // SETTING_HQ_JPEG, + + { 1, 0, 8, }, // SETTING_DVD_PARENTAL, + { 1, 1, 1, }, // SETTING_DVD_MV, + + { 2, 0x656e, 0xffff, }, // SETTING_DVD_LANG_MENU 'en', + + { 2, 0x656e, 0xffff, }, // SETTING_DVD_LANG_AUDIO 'en', + + { 2, 0x656e, 0xffff, }, // SETTING_DVD_LANG_SPU 'en', + + { 1, 80, 100, }, // SETTING_VOLUME, + + { 1, 100, 200, }, // SETTING_BALANCE, + + + { 4, (500<<20)|(500<<10)|500, (1000<<20)|(1000<<10)|1000, }, // SETTING_BRIGHTNESS_CONTRAST_SATURATION_PAL, + { 4, (500<<20)|(500<<10)|500, (1000<<20)|(1000<<10)|1000, }, // SETTING_BRIGHTNESS_CONTRAST_SATURATION_NTSC, + { 4, (500<<20)|(500<<10)|500, (1000<<20)|(1000<<10)|1000, }, // SETTING_BRIGHTNESS_CONTRAST_SATURATION_OTHER, + + { 4, 0, 0xffffffff, }, // SETTING_GUI_0, + { 4, 0, 0xffffffff, }, // SETTING_GUI_1, + { 4, 0, 0xffffffff, }, // SETTING_GUI_2, + { 4, 0, 0xffffffff, }, // SETTING_GUI_3, + { 4, 0, 0xffffffff, }, // SETTING_GUI_4, + { 4, 0, 0xffffffff, }, // SETTING_GUI_5, + { 4, 0, 0xffffffff, }, // SETTING_GUI_6, + { 4, 0, 0xffffffff, }, // SETTING_GUI_7, + { 4, 0, 0xffffffff, }, // SETTING_GUI_8, + { 4, 0, 0xffffffff, }, // SETTING_GUI_9, + { 4, 0, 0xffffffff, }, // SETTING_GUI_10, + { 4, 0, 0xffffffff, }, // SETTING_GUI_11, + { 4, 0, 0xffffffff, }, // SETTING_GUI_12, + { 4, 0, 0xffffffff, }, // SETTING_GUI_13, + { 4, 0, 0xffffffff, }, // SETTING_GUI_14, + { 4, 0, 0xffffffff, }, // SETTING_GUI_15, + + { 4, 0, 0xffffffff, }, // SETTING_DVD_ID1, + { 4, 0xffffffff, 0xffffffff, }, // SETTING_DVD_POS1, + { 4, 0xffffffff, 0xffffffff, }, // SETTING_DVD_DATA11, + { 4, 0xffffffff, 0xffffffff, }, // SETTING_DVD_DATA12, + + { 4, 0, 0xffffffff, }, // SETTING_DVD_ID2, + { 4, 0xffffffff, 0xffffffff, }, // SETTING_DVD_POS2, + { 4, 0xffffffff, 0xffffffff, }, // SETTING_DVD_DATA21, + { 4, 0xffffffff, 0xffffffff, }, // SETTING_DVD_DATA22, + + { 4, 0, 0xffffffff, }, // SETTING_DVD_ID3, + { 4, 0xffffffff, 0xffffffff, }, // SETTING_DVD_POS3, + { 4, 0xffffffff, 0xffffffff, }, // SETTING_DVD_DATA31, + { 4, 0xffffffff, 0xffffffff, }, // SETTING_DVD_DATA32, + + { 4, 0, 0xffffffff, }, // SETTING_DVD_ID4, + { 4, 0xffffffff, 0xffffffff, }, // SETTING_DVD_POS4, + { 4, 0xffffffff, 0xffffffff, }, // SETTING_DVD_DATA41, + { 4, 0xffffffff, 0xffffffff, }, // SETTING_DVD_DATA42, + + { 4, 0, 0xffffffff, }, // SETTING_DVD_ID5, + { 4, 0xffffffff, 0xffffffff, }, // SETTING_DVD_POS5, + { 4, 0xffffffff, 0xffffffff, }, // SETTING_DVD_DATA51, + { 4, 0xffffffff, 0xffffffff, }, // SETTING_DVD_DATA52, + + { 1, 0, 2, }, // SETTING_HDD_SPEED, + + { 0 }, +}; + +int settings_get_eeprom(int id) +{ + return eeprom_get_value(settings[id].addr, settings[id].size); +} + +BOOL settings_init(bool set_defaults) +{ + settings[0].addr = 0; + int i; + for (i = 1; i < max_settings; i++) + { + if (settings[i].size == 0) + { + num_settings = i; + break; + } + settings[i].addr = settings[i-1].addr + settings[i-1].size; + } + + DWORD stored_version = settings_get_eeprom(SETTING_VERSION); + + bool reset = (stored_version & 0xffffff00) != (cur_version & 0xffffff00); + if ((stored_version & 0xff) < (cur_version & 0xff) || set_defaults) + { + was_reset = true; + reset = true; + } + + for (i = 0; i < num_settings; i++) + { + if (reset) + settings_set((SETTING_SET)i, settings[i].def); + else + { + DWORD from_eeprom = settings_get_eeprom(i); + if (from_eeprom <= settings[i].max) + settings[i].value = from_eeprom; + else + settings_set((SETTING_SET)i, settings[i].def); + } + } + return TRUE; +} + +BOOL settings_set(SETTING_SET id, int val, bool no_eeprom) +{ + if (id < num_settings && (DWORD)val <= settings[id].max) + { + settings[id].value = val; + if (!no_eeprom) + { + eeprom_set_value(settings[id].addr, val, settings[id].size); + msg("Settings: %d = %d\n", id, val); + } + return TRUE; + } + return FALSE; +} + +int settings_get(SETTING_SET id) +{ + return id < num_settings ? settings[id].value : 0; +} + +int settings_getmax(SETTING_SET id) +{ + return id < num_settings ? settings[id].max : 0; +} + +DWORD get_firmware_address() +{ + DWORD addr = 0; + msg("Flash: Detecting firmware address...\n"); + // find ROMFS address +#ifdef WIN32 + addr = 0x6000; +#else + for (int i = 0x6000; i <= 0x10000; i += 0x1000) + { + BYTE *data = (BYTE *)i; + if (memcmp(data, "-rom1fs-", 8) == 0) + { + addr = (DWORD)i; + break; + } + } +#endif + + if (addr == 0) + { + msg("Flash: Cannot find ROMFS address!\n"); + return 0; + } + msg("Flash: ROMFS found @ 0x%08x\n", addr); + + return addr; +} diff --git a/src/settings.h b/src/settings.h new file mode 100644 index 0000000..a7dcf73 --- /dev/null +++ b/src/settings.h @@ -0,0 +1,143 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - user settings header file + * \file settings.h + * \author bombur + * \version 0.1 + * \date 12.01.2005 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#ifndef SP_SETTINGS_H +#define SP_SETTINGS_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "version.h" + +/// This is version identification mask +const DWORD SP_VERSION_MASK = 0xCFDA9900; + +const int max_settings = 64; + +/// Settings IDs (no more than max_settings) +typedef enum +{ + SETTING_VERSION = 0, // not used normally + + SETTING_AUDIOOUT, // 0 = analog, 1 = digital + + SETTING_HDTV, // 0 = off, 1 = 480p, 2 = 720p, 3 = 1080i + + SETTING_DVI, // !RESERVED! 0 = off, X = number read from cfg file?? + + SETTING_TVSTANDARD, // 0 = "NTSC", 1 = "PAL", 2 = "480P", 3 = "576P", 4 = "720P", 5 = "1080I" + + SETTING_TVOUT, // 0 = "C/S-Video", 1 = "C/YPbPr", 2 = "C/RGB" + + SETTING_TVTYPE, // 0 = 4:3 letterbox, 1 = 4:3 panscan, 2 = 16:9, 3 = 16:9 panscan + + SETTING_HQ_JPEG, // 0 = off, 1 = on + + SETTING_DVD_PARENTAL, + SETTING_DVD_MV, + + SETTING_DVD_LANG_MENU, + + SETTING_DVD_LANG_AUDIO, + + SETTING_DVD_LANG_SPU, + + SETTING_VOLUME, + + SETTING_BALANCE, + + SETTING_BRIGHTNESS_CONTRAST_SATURATION_PAL, + SETTING_BRIGHTNESS_CONTRAST_SATURATION_NTSC, + SETTING_BRIGHTNESS_CONTRAST_SATURATION_OTHER, + + SETTING_GUI_0, + SETTING_GUI_1, + SETTING_GUI_2, + SETTING_GUI_3, + SETTING_GUI_4, + SETTING_GUI_5, + SETTING_GUI_6, + SETTING_GUI_7, + SETTING_GUI_8, + SETTING_GUI_9, + SETTING_GUI_10, + SETTING_GUI_11, + SETTING_GUI_12, + SETTING_GUI_13, + SETTING_GUI_14, + SETTING_GUI_15, + + SETTING_DVD_ID1, // most recent + SETTING_DVD_POS1, + SETTING_DVD_DATA11, + SETTING_DVD_DATA12, + SETTING_DVD_ID2, + SETTING_DVD_POS2, + SETTING_DVD_DATA21, + SETTING_DVD_DATA22, + SETTING_DVD_ID3, + SETTING_DVD_POS3, + SETTING_DVD_DATA31, + SETTING_DVD_DATA32, + SETTING_DVD_ID4, + SETTING_DVD_POS4, + SETTING_DVD_DATA41, + SETTING_DVD_DATA42, + SETTING_DVD_ID5, + SETTING_DVD_POS5, + SETTING_DVD_DATA51, + SETTING_DVD_DATA52, + + SETTING_HDD_SPEED, + + +} SETTING_SET; + +/////////////////////////////////////////////////// + +/// Initialize settings (and read EEPROM) +BOOL settings_init(bool set_defaults = false); + +/// Store settings value (and write EEPROM if allowed) +BOOL settings_set(SETTING_SET id, int val, bool no_eeprom = false); + +/// Get stored settings value from cache +int settings_get(SETTING_SET id); + +/// Get max. allowed value for given settings ID +int settings_getmax(SETTING_SET id); + +/////////////////////////////////////////////////// +/// Special functions + +DWORD get_firmware_address(); + +// not yet + +#ifdef __cplusplus +} +#endif + +#endif // of SP_SETTINGS_H diff --git a/src/subtitle.cpp b/src/subtitle.cpp new file mode 100644 index 0000000..a137e8a --- /dev/null +++ b/src/subtitle.cpp @@ -0,0 +1,1416 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - Subtitles for video player source file. + * \file subtitle.cpp + * \author bombur + * \version 0.1 + * \date 01.10.2008 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "script.h" +#include "script-internal.h" +#include "subtitle.h" + + +static const int max_allowed_playlist_subs = 2; + +static Subtitles *sub = NULL; + +static const SubtitleFormat sub_formats[] = +{ + { /* 0 */ + "SubRip", + { SUB(SUB_TOKEN_SKIP_DIGITS), "\n", SUB(SUB_TOKEN_HOUR1), ":", SUB(SUB_TOKEN_MIN1), ":", SUB(SUB_TOKEN_SEC1), SUB(SUB_TOKEN_OPTIONAL_NEXT), ",", SUB(SUB_TOKEN_OPTIONAL_NEXT), SUB(SUB_TOKEN_MSEC1), " --> ", + SUB(SUB_TOKEN_HOUR2), ":", SUB(SUB_TOKEN_MIN2), ":", SUB(SUB_TOKEN_SEC2), SUB(SUB_TOKEN_OPTIONAL_NEXT), ",", SUB(SUB_TOKEN_OPTIONAL_NEXT), SUB(SUB_TOKEN_MSEC2), + NULL + }, + "\n\n" + }, + { /* 1 */ + "SubViewer 1.0", + { "[", SUB(SUB_TOKEN_HOUR1), ":", SUB(SUB_TOKEN_MIN1), ":", SUB(SUB_TOKEN_SEC1), "]\n", + NULL + }, + "\n" + }, + { /* 2 */ + "SubViewer 2.0", + { SUB(SUB_TOKEN_HOUR1), ":", SUB(SUB_TOKEN_MIN1), ":", SUB(SUB_TOKEN_SEC1), ".", SUB(SUB_TOKEN_DSEC1), ",", + SUB(SUB_TOKEN_HOUR2), ":", SUB(SUB_TOKEN_MIN2), ":", SUB(SUB_TOKEN_SEC2), ".", SUB(SUB_TOKEN_DSEC2), "\n", + NULL + }, + "\n\n" + }, + { /* 3 */ + "DVDSubtitle", + { "{T ", SUB(SUB_TOKEN_HOUR1), ":", SUB(SUB_TOKEN_MIN1), ":", SUB(SUB_TOKEN_SEC1), ":", SUB(SUB_TOKEN_DSEC1), "\n", + NULL + }, + "\n}\n" + }, + { /* 4 */ + "DVD Architect", + { SUB(SUB_TOKEN_SKIP_DIGITS_4), "\t", SUB(SUB_TOKEN_HOUR1), ":", SUB(SUB_TOKEN_MIN1), ":", SUB(SUB_TOKEN_SEC1), ":", SUB(SUB_TOKEN_DSEC1), "\t", + SUB(SUB_TOKEN_HOUR2), ":", SUB(SUB_TOKEN_MIN2), ":", SUB(SUB_TOKEN_SEC2), ":", SUB(SUB_TOKEN_DSEC2), "\t", + NULL + }, + "\n\n" + }, + { /* 5 */ + "MicroDVD", + { "{", SUB(SUB_TOKEN_FRAMES1), "}{", SUB(SUB_TOKEN_FRAMES2), "}", + NULL + }, + "\n" + }, + { /* 6 */ + "MPSub", + { SUB(SUB_TOKEN_DELTA_SECS1), SUB(SUB_TOKEN_OPTIONAL_NEXT), ".", SUB(SUB_TOKEN_OPTIONAL_NEXT), SUB(SUB_TOKEN_DELTA_MSECS1), " ", + SUB(SUB_TOKEN_DELTA_SECS2), SUB(SUB_TOKEN_OPTIONAL_NEXT), ".", SUB(SUB_TOKEN_OPTIONAL_NEXT), SUB(SUB_TOKEN_DELTA_MSECS2), "\n", + NULL + }, + "\n\n" + }, + { /* 7 */ + "TMPlayer", + { SUB(SUB_TOKEN_HOUR1), ":", SUB(SUB_TOKEN_MIN1), ":", SUB(SUB_TOKEN_SEC1), ",", SUB(SUB_TOKEN_LINE_NUMBER), "=", + NULL + }, + "\n" + }, + { /* 8 */ + "SubSonic", + { "1 ", SUB(SUB_TOKEN_SEC1_256), ".", SUB(SUB_TOKEN_DSEC1_256), SUB(SUB_TOKEN_OPTIONAL_NEXT_2), " \\ ~:\\", + NULL + }, + "\n" + }, + { /* 9 */ + "SubStation Alpha", + { SUB(SUB_TOKEN_SKIP_TO_NEXT), ",", SUB(SUB_TOKEN_HOUR1_1), ":", SUB(SUB_TOKEN_MIN1), ":", SUB(SUB_TOKEN_SEC1), ".", SUB(SUB_TOKEN_DSEC1), ",", + SUB(SUB_TOKEN_HOUR2_1), ":", SUB(SUB_TOKEN_MIN2), ":", SUB(SUB_TOKEN_SEC2), ".", SUB(SUB_TOKEN_DSEC2), ",", + SUB(SUB_TOKEN_SKIP_TO_NEXT), ",", SUB(SUB_TOKEN_SKIP_TO_NEXT), ",", SUB(SUB_TOKEN_SKIP_TO_NEXT), ",", + SUB(SUB_TOKEN_SKIP_TO_NEXT), ",", SUB(SUB_TOKEN_SKIP_TO_NEXT), ",", SUB(SUB_TOKEN_SKIP_TO_NEXT), ",", + NULL + }, + "\n" + }, + { + NULL, + { NULL }, + NULL + } +}; + +///////////////////////////////////////////////////////////////////// + +BOOL SubtitleData::Alloc(int add_size) +{ + if (add_size > 0 && ptr != NULL) + { + ptr = (char *)SPrealloc((void *)ptr, max_string_length + add_size); + left += add_size; + } + else + ptr = cur = (char *)SPmalloc(max_string_length); + if (ptr == NULL) + return FALSE; + cur[0] = '\0'; + return TRUE; +} + +///////////////////////////////////////////////////////////////////// + +Subtitles::Subtitles() +{ + Reset(); + + saved_left[0] = saved_left[1] = 0; + + max_line_letters_cnt = 35; + + cur_sub = -1; +} + +Subtitles::~Subtitles() +{ + data.DeleteObjects(); + subs.DeleteObjects(); +} + +void Subtitles::Reset() +{ + fd = -1; + cur_method = -1; + buf_idx = 0; + buf_cnt[0] = buf_cnt[1] = 0; + buf_read_left[0] = buf_read_left[1] = 0; + buf_read_cnt = 1; + + is_start_of_file = true; + is_utf8 = false; is_utf16 = false; is_utf16be = false; +} + +void Subtitles::InitTranslationTable(SUBTITLE_CHARSET charset) +{ + static const BYTE koi_win[][2] = + { + { 0xe1, 0xc0 }, { 0xe2, 0xc1 }, { 0xf7, 0xc2 }, { 0xe7, 0xc3 }, + { 0xe4, 0xc4 }, { 0xe5, 0xc5 }, { 0xf6, 0xc6 }, { 0xfa, 0xc7 }, + { 0xe9, 0xc8 }, { 0xea, 0xc9 }, { 0xeb, 0xca }, { 0xec, 0xcb }, + { 0xed, 0xcc }, { 0xee, 0xcd }, { 0xef, 0xce }, { 0xf0, 0xcf }, + { 0xf2, 0xd0 }, { 0xf3, 0xd1 }, { 0xf4, 0xd2 }, { 0xf5, 0xd3 }, + { 0xe6, 0xd4 }, { 0xe8, 0xd5 }, { 0xe3, 0xd6 }, { 0xfe, 0xd7 }, + { 0xfb, 0xd8 }, { 0xfd, 0xd9 }, { 0xff, 0xda }, { 0xf9, 0xdb }, + { 0xf8, 0xdc }, { 0xfc, 0xdd }, { 0xe0, 0xde }, { 0xf1, 0xdf }, + { 0xc1, 0xe0 }, { 0xc2, 0xe1 }, { 0xd7, 0xe2 }, { 0xc7, 0xe3 }, + { 0xc4, 0xe4 }, { 0xc5, 0xe5 }, { 0xd6, 0xe6 }, { 0xda, 0xe7 }, + { 0xc9, 0xe8 }, { 0xca, 0xe9 }, { 0xcb, 0xea }, { 0xcc, 0xeb }, + { 0xcd, 0xec }, { 0xce, 0xed }, { 0xcf, 0xee }, { 0xd0, 0xef }, + { 0xd2, 0xf0 }, { 0xd3, 0xf1 }, { 0xd4, 0xf2 }, { 0xd5, 0xf3 }, + { 0xc6, 0xf4 }, { 0xc8, 0xf5 }, { 0xc3, 0xf6 }, { 0xde, 0xf7 }, + { 0xdb, 0xf8 }, { 0xdd, 0xf9 }, { 0xdf, 0xfa }, { 0xd9, 0xfb }, + { 0xd8, 0xfc }, { 0xdc, 0xfd }, { 0xc0, 0xfe }, { 0xd1, 0xff }, + { 0xb3, 0xa8 }, { 0xa3, 0xb8 }, { 0, 0 }, + }; + + for (int i = 0; i < 256; i++) + charset_translation_table[i] = (BYTE)i; + + if (charset == SUBTITLE_CHARSET_KOI8R) // KOI8 to CP1251 + { + for (int i = 0; koi_win[i][0] != '\0'; i++) + charset_translation_table[koi_win[i][0]] = koi_win[i][1]; + } +} + +void Subtitles::FillUTFTable(SUBTITLE_CHARSET charset) +{ + const static WORD utf_win1251[128] = + { + 0x0402,0x0403,0x201A,0x0453,0x201E,0x2026,0x2020,0x2021,0x20AC,0x2030,0x0409,0x2039,0x040A,0x040C,0x040B,0x040F, + 0x0452,0x2018,0x2019,0x201C,0x201D,0x2022,0x2013,0x2014,0x0000,0x2122,0x0459,0x203A,0x045A,0x045C,0x045B,0x045F, + 0x00A0,0x040E,0x045E,0x0408,0x00A4,0x0490,0x00A6,0x00A7,0x0401,0x00A9,0x0404,0x00AB,0x00AC,0x00AD,0x00AE,0x0407, + 0x00B0,0x00B1,0x0406,0x0456,0x0491,0x00B5,0x00B6,0x00B7,0x0451,0x2116,0x0454,0x00BB,0x0458,0x0405,0x0455,0x0457, + 0x0410,0x0411,0x0412,0x0413,0x0414,0x0415,0x0416,0x0417,0x0418,0x0419,0x041A,0x041B,0x041C,0x041D,0x041E,0x041F, + 0x0420,0x0421,0x0422,0x0423,0x0424,0x0425,0x0426,0x0427,0x0428,0x0429,0x042A,0x042B,0x042C,0x042D,0x042E,0x042F, + 0x0430,0x0431,0x0432,0x0433,0x0434,0x0435,0x0436,0x0437,0x0438,0x0439,0x043A,0x043B,0x043C,0x043D,0x043E,0x043F, + 0x0440,0x0441,0x0442,0x0443,0x0444,0x0445,0x0446,0x0447,0x0448,0x0449,0x044A,0x044B,0x044C,0x044D,0x044E,0x044F, + }; + + const static WORD utf_iso8859_2[128] = + { + 0x0080,0x0081,0x0082,0x0083,0x0084,0x0085,0x0086,0x0087,0x0088,0x0089,0x008A,0x008B,0x008C,0x008D,0x008E,0x008F, + 0x0090,0x0091,0x0092,0x0093,0x0094,0x0095,0x0096,0x0097,0x0098,0x0099,0x009A,0x009B,0x009C,0x009D,0x009E,0x009F, + 0x00A0,0x0104,0x02D8,0x0141,0x00A4,0x013D,0x015A,0x00A7,0x00A8,0x0160,0x015E,0x0164,0x0179,0x00AD,0x017D,0x017B, + 0x00B0,0x0105,0x02DB,0x0142,0x00B4,0x013E,0x015B,0x02C7,0x00B8,0x0161,0x015F,0x0165,0x017A,0x02DD,0x017E,0x017C, + 0x0154,0x00C1,0x00C2,0x0102,0x00C4,0x0139,0x0106,0x00C7,0x010C,0x00C9,0x0118,0x00CB,0x011A,0x00CD,0x00CE,0x010E, + 0x0110,0x0143,0x0147,0x00D3,0x00D4,0x0150,0x00D6,0x00D7,0x0158,0x016E,0x00DA,0x0170,0x00DC,0x00DD,0x0162,0x00DF, + 0x0155,0x00E1,0x00E2,0x0103,0x00E4,0x013A,0x0107,0x00E7,0x010D,0x00E9,0x0119,0x00EB,0x011B,0x00ED,0x00EE,0x010F, + 0x0111,0x0144,0x0148,0x00F3,0x00F4,0x0151,0x00F6,0x00F7,0x0159,0x016F,0x00FA,0x0171,0x00FC,0x00FD,0x0163,0x02D9, + }; + + DWORD i; + for (i = 0; i < 127; i++) + utf16_to_ansi_table[i] = (BYTE)i; + for (i = 127; i < sizeof(utf16_to_ansi_table); i++) + utf16_to_ansi_table[i] = ' '; + if (charset == SUBTITLE_CHARSET_ISO8859_1) + { + for (i = 127; i < 256; i++) + utf16_to_ansi_table[i] = (BYTE)i; + } + else if (charset == SUBTITLE_CHARSET_ISO8859_2) + { + for (i = 0; i < 128; i++) + { + int j = Min((size_t)utf_iso8859_2[i], sizeof(utf16_to_ansi_table)); + if (j > 0) + utf16_to_ansi_table[j] = (BYTE)(i + 128); + } + } + else + { + for (i = 0; i < 128; i++) + { + int j = Min((size_t)utf_win1251[i], sizeof(utf16_to_ansi_table)); + if (j > 0) + utf16_to_ansi_table[j] = (BYTE)(i + 128); + } + } + + if (is_utf8 || is_utf16) + { + for (i = 0; i < 256; i++) + charset_translation_table[i] = (BYTE)i; + } +} + +int Subtitles::AddString(BYTE *str, int &length, bool attach) +{ + int l = length + 1; + if (l > max_string_length) + return -1; + + int n = data.GetN(); + SubtitleData *d = data[n - 1]; + if (n == 0 || d->left < l) + { + if (!attach || n == 0 || !d->Alloc(l)) + { + d = new SubtitleData(); + if (!d->Alloc(0)) + return -1; + if (data.Add(d) < 0) + return -1; + n = data.GetN(); + } + } + + char last_ds; + if (attach && d->cur > d->ptr) + { + *(d->cur - 1) = last_ds = '\n'; + } + else + last_ds = 0; + + // replace all newlines now: + char *dst = d->cur; + int i = 0; + int line_letters_cnt = 0, last_space_cnt = 0; + for (BYTE *s = str; i < length; i++, s++) + { + char ds; + if (*s == '|') + ds = '\n'; + else if (*s == '\\' && s[1] == 'N') + { + ds = '\n'; + s++; + i++; + } + else if (*s == '[' && s[1] == 'b' && s[2] == 'r' && s[3] == ']') + { + ds = '\n'; + s += 3; + i += 3; + } + else + { + ds = charset_translation_table[(BYTE)*s]; + if (ds == ' ') + last_space_cnt = line_letters_cnt; + } + if (line_letters_cnt == 0) + { + if (ds == ' ' || ds == '\n' || ds == '%') + { + continue; + } + } + else if (line_letters_cnt > max_line_letters_cnt) + { + if (last_space_cnt > 0) + { + int delta = line_letters_cnt - last_space_cnt; + s -= delta; + i -= delta; + dst -= delta; + line_letters_cnt = last_space_cnt; + last_space_cnt = 0; + ds = '\n'; + } + } + if (ds == '\n') + { + line_letters_cnt = 0; + if (last_ds == '\n') + { + continue; + } + } + else + line_letters_cnt++; + *dst++ = last_ds = ds; + } + + if (last_ds == '\n' || last_ds == '%') + { + dst--; + } + *dst = '\0'; + int pos = d->cur - d->ptr; + length = dst - d->cur + 1; + d->cur += length; + d->len += length; + d->left -= length; + length--; + return pos; +} + +BOOL Subtitles::Scan(BYTE *&buf, int &left, int line_num, int method, SubtitleFields fields[2]) +{ + memset(fields, 0xff, sizeof(SubtitleFields) * 2); + fields[1].is_set = false; + + SUB_CUR_MODE cur_mode = SUB_CUR_MODE_NORMAL; + SUB_CUR_FORMAT cur_format = SUB_CUR_FORMAT_STRING; + SUB_CUR_FIELD cur_field = SUB_CUR_FIELD_NONE; + int cur_num_digits = 0; + + int field_idx = 0; + const SubtitleFormat &fmt = sub_formats[method]; + for (int i = 0; fmt.start[i] != NULL; i++) + { + const char *str = fmt.start[i]; + if ((int)str < 0x1000) + { + cur_field = SUB_CUR_FIELD_NONE; + + if (str == SUB(SUB_TOKEN_SKIP_TO_NEXT)) + { + cur_mode = SUB_CUR_MODE_SEARCH_NEXT; + continue; + } + else if (str == SUB(SUB_TOKEN_OPTIONAL_NEXT)) + { + cur_mode = SUB_CUR_MODE_OPTIONAL; + continue; + } + else if (str == SUB(SUB_TOKEN_OPTIONAL_NEXT_2)) + { + if (line_num > 0) + cur_mode = SUB_CUR_MODE_OPTIONAL; + continue; + } + if (str >= SUB(SUB_TOKEN_SKIP_DIGITS) && str <= SUB(SUB_TOKEN_SKIP_DIGITS_4)) + { + cur_format = SUB_CUR_FORMAT_DIGITS; + cur_num_digits = str - SUB(SUB_TOKEN_SKIP_DIGITS); + } + + field_idx = ((int)str > 0x100) ? 1 : 0; + switch ((int)str & 0xff) + { + case SUB_TOKEN_HOUR1: + cur_format = SUB_CUR_FORMAT_DIGITS; + cur_num_digits = 2; + cur_field = SUB_CUR_FIELD_HOUR; + break; + case SUB_TOKEN_HOUR1_1: + cur_format = SUB_CUR_FORMAT_DIGITS; + cur_num_digits = 1; + cur_field = SUB_CUR_FIELD_HOUR; + break; + case SUB_TOKEN_MIN1: + cur_format = SUB_CUR_FORMAT_DIGITS; + cur_num_digits = 2; + cur_field = SUB_CUR_FIELD_MIN; + break; + case SUB_TOKEN_SEC1: + cur_format = SUB_CUR_FORMAT_DIGITS; + cur_num_digits = 2; + cur_field = SUB_CUR_FIELD_SEC; + break; + case SUB_TOKEN_DSEC1: + cur_format = SUB_CUR_FORMAT_DIGITS; + cur_num_digits = 2; + cur_field = SUB_CUR_FIELD_MSEC; + break; + case SUB_TOKEN_MSEC1: + cur_format = SUB_CUR_FORMAT_DIGITS; + cur_num_digits = 3; + cur_field = SUB_CUR_FIELD_MSEC; + break; + case SUB_TOKEN_SEC1_256: + cur_format = SUB_CUR_FORMAT_DIGITS; + cur_num_digits = 0; + cur_field = SUB_CUR_FIELD_SEC256; + break; + case SUB_TOKEN_DSEC1_256: + cur_format = SUB_CUR_FORMAT_DIGITS; + cur_num_digits = 2; + cur_field = SUB_CUR_FIELD_MSEC256; + break; + case SUB_TOKEN_DELTA_SECS1: + cur_format = SUB_CUR_FORMAT_DIGITS_SIGN; + cur_num_digits = 0; + cur_field = SUB_CUR_FIELD_DELTA_SEC; + break; + case SUB_TOKEN_DELTA_MSECS1: + cur_format = SUB_CUR_FORMAT_DIGITS; + cur_num_digits = 0; + cur_field = SUB_CUR_FIELD_DELTA_MSEC; + break; + case SUB_TOKEN_FRAMES1: + cur_format = SUB_CUR_FORMAT_DIGITS; + cur_num_digits = 0; + cur_field = SUB_CUR_FIELD_FRAMES; + break; + case SUB_TOKEN_LINE_NUMBER: + cur_format = SUB_CUR_FORMAT_DIGITS; + cur_num_digits = 1; + cur_field = SUB_CUR_FIELD_LINE_NUMBER; + break; + } + + } else + { + cur_format = SUB_CUR_FORMAT_STRING; + cur_field = SUB_CUR_FIELD_NONE; + } + + int value = 0, sign = 1; + while (left > 0) + { + int num_read = 0; + SavePos(1, buf, left); + switch (cur_format) + { + case SUB_CUR_FORMAT_STRING: + { + const char *ss = str; + for (; *ss != '\0'; buf++, left--, num_read++) + { + if (left < 1) + { + if ((buf = ReadMore(&left)) == NULL) + { + num_read = 0; + return FALSE; + } + } + if (*buf == '\r') + continue; + if (*buf != *ss) + { + num_read = 0; + if (!LoadPos(1, buf, left)) + return FALSE; + break; + } + ss++; + } + } + break; + case SUB_CUR_FORMAT_DIGITS_SIGN: + if (*buf == '-') + { + num_read++; + buf++; + left--; + sign = -1; + if (left < 1) + { + if ((buf = ReadMore(&left)) == NULL) + { + num_read = 0; + return FALSE; + } + } + } + case SUB_CUR_FORMAT_DIGITS: + value = 0; + while (left > 0) + { + int max_num_read = cur_num_digits == 0 ? left : cur_num_digits; + for ( ; num_read < max_num_read && left > 0 && isdigit(*buf); num_read++, buf++, left--) + { + value = value * 10 + (*buf - '0'); + } + //if ((cur_num_digits > 0 && num_read == cur_num_digits) || (cur_num_digits == 0 && num_read > 0)) + if (left > 0) + break; + if ((buf = ReadMore(&left)) == NULL) + return FALSE; + } + if (cur_num_digits > 0 && num_read != cur_num_digits) + { + num_read = 0; + value = 0; + } + break; + } + + if (num_read > 0) + { + value *= sign; + + switch (cur_field) + { + case SUB_CUR_FIELD_HOUR: + fields[field_idx].hour = value; + break; + case SUB_CUR_FIELD_MIN: + fields[field_idx].min = value; + break; + case SUB_CUR_FIELD_SEC: + fields[field_idx].sec = value; + break; + case SUB_CUR_FIELD_SEC256: + fields[field_idx].sec256 = value; + break; + case SUB_CUR_FIELD_MSEC: + fields[field_idx].msec = (num_read == 2) ? value * 10 : value; + break; + case SUB_CUR_FIELD_MSEC256: + fields[field_idx].msec256 = (num_read == 2) ? value * 10 : value; + break; + case SUB_CUR_FIELD_DELTA_SEC: + fields[field_idx].delta_sec = value - 1; + break; + case SUB_CUR_FIELD_DELTA_MSEC: + fields[field_idx].delta_msec = ((num_read == 1) ? value * 100 : value * 10) - 1; + break; + case SUB_CUR_FIELD_FRAMES: + fields[field_idx].frames = value; + break; + case SUB_CUR_FIELD_LINE_NUMBER: + fields[field_idx].line_number = value; + break; + case SUB_CUR_FIELD_NONE: + break; + } + if (cur_field != SUB_CUR_FIELD_NONE) + fields[field_idx].is_set = true; + + cur_mode = SUB_CUR_MODE_NORMAL; + } + else if (cur_mode == SUB_CUR_MODE_SEARCH_NEXT) + { + // skip only at current line + buf++; + left--; + if (left < 1) + { + if ((buf = ReadMore(&left)) == NULL) + return FALSE; + } + if (*buf == '\n' || left < 1) + return FALSE; + continue; + } + else if (cur_mode == SUB_CUR_MODE_OPTIONAL) + { + LoadPos(1, buf, left); + cur_mode = SUB_CUR_MODE_NORMAL; + break; + } + else + { + // failure - didn't match the template + return FALSE; + } + + cur_mode = SUB_CUR_MODE_NORMAL; + break; + } + if (left < 1) + { + buf = sub->ReadMore(&left); + if (buf == NULL) + return FALSE; + } + } + return TRUE; +} + +void Subtitles::ConvertUTF16(BYTE *buf, int *left) +{ + int l = *left; + BYTE *wb = buf; + BYTE *rb = wb; + for (; l > 0; l-=2, wb++, rb+=2) + { + WORD u = rb[0] | (rb[1] << 8); + + *wb = (u < sizeof(utf16_to_ansi_table)) ? utf16_to_ansi_table[u] : ' '; + } + + *left = wb - buf; +} + +void Subtitles::ConvertUTF16BE(BYTE *buf, int *left) +{ + int l = *left; + BYTE *wb = buf; + BYTE *rb = wb; + for (; l > 0; l-=2, wb++, rb+=2) + { + WORD u = rb[1] | (rb[0] << 8); + + *wb = (u < sizeof(utf16_to_ansi_table)) ? utf16_to_ansi_table[u] : ' '; + } + + *left = wb - buf; +} + +int Subtitles::ConvertUTF8(BYTE *buf, int *left) +{ + int l = *left - 6; + int d = 1; + BYTE *wb = buf; + BYTE *rb = wb; + + for (; l > 0; wb++, rb += d, l -= d) + { + WORD u = 0xffff; + d = 1; + if ((rb[0] & 0x80) == 0x00) + u = rb[0]; + else if ((rb[0] & 0xe0) == 0xc0 && (rb[1] & 0xc0) == 0x80) + { + u = ((rb[0] & 0x1fL) << 6) | ((rb[1] & 0x3fL) << 0); + if (u >= 0x00000080L) + d = 2; + } + else if ((rb[0] & 0xf0) == 0xe0 && (rb[1] & 0xc0) == 0x80 && (rb[2] & 0xc0) == 0x80) + { + u = ((rb[0] & 0x0fL) << 12) | ((rb[1] & 0x3fL) << 6) | ((rb[2] & 0x3fL) << 0); + if (u >= 0x00000800L) + d = 3; + } + else if ((rb[0] & 0xf8) == 0xf0 && (rb[1] & 0xc0) == 0x80 && (rb[2] & 0xc0) == 0x80 && + (rb[3] & 0xc0) == 0x80) + { + u = ((rb[0] & 0x07L) << 18) | ((rb[1] & 0x3fL) << 12) | + ((rb[2] & 0x3fL) << 6) | ((rb[3] & 0x3fL) << 0); + if (u >= 0x00010000L) + d = 4; + } + else if ((rb[0] & 0xfc) == 0xf8 && (rb[1] & 0xc0) == 0x80 && (rb[2] & 0xc0) == 0x80 && + (rb[3] & 0xc0) == 0x80 && (rb[4] & 0xc0) == 0x80) + { + u = ((rb[0] & 0x03L) << 24) | ((rb[1] & 0x3fL) << 18) | + ((rb[2] & 0x3fL) << 12) | ((rb[3] & 0x3fL) << 6) | ((rb[4] & 0x3fL) << 0); + if (u >= 0x00200000L) + d = 5; + } + else if ((rb[0] & 0xfe) == 0xfc && (rb[1] & 0xc0) == 0x80 && (rb[2] & 0xc0) == 0x80 && + (rb[3] & 0xc0) == 0x80 && (rb[4] & 0xc0) == 0x80 && (rb[5] & 0xc0) == 0x80) + { + u = ((rb[0] & 0x01L) << 30) | ((rb[1] & 0x3fL) << 24) | ((rb[2] & 0x3fL) << 18) | + ((rb[3] & 0x3fL) << 12) | ((rb[4] & 0x3fL) << 6) | ((rb[5] & 0x3fL) << 0); + if (u >= 0x04000000L) + d = 6; + } + + *wb = (u < sizeof(utf16_to_ansi_table)) ? utf16_to_ansi_table[u] : ' '; + } + + *left = wb - buf; + + return rb - buf; +} + +BYTE *Subtitles::ReadMore(int *left, bool force_read) +{ + // we don't want to read more data if subtitle format is unknown + if (cur_method < 0 && !force_read) + return NULL; + int new_bufidx = (++buf_idx) & 1; + BYTE *b = buf[new_bufidx]; + if (buf_read_cnt <= buf_cnt[new_bufidx]) + { + *left = buf_read_left[new_bufidx]; + } else + { + int left0 = read(fd, b, read_buf_length); + if (is_start_of_file) + { + if (b[0] == 0xff && b[1] == 0xfe) + { + sub->is_utf16 = true; + msg("Subtitle: UTF-16 Detected!\n"); + FillUTFTable(params->info.subtitle_charset); + b += 2; + left0 -= 2; + } + if (b[0] == 0xfe && b[1] == 0xff) + { + sub->is_utf16be = true; + msg("Subtitle: UTF-16BE Detected!\n"); + FillUTFTable(params->info.subtitle_charset); + b += 2; + left0 -= 2; + } + else if (b[0] == 0xef && b[1] == 0xbb && b[2] == 0xbf) + { + sub->is_utf8 = true; + msg("Subtitle: UTF-8 Detected!\n"); + FillUTFTable(params->info.subtitle_charset); + b += 3; + left0 -= 3; + } + is_start_of_file = false; + } + *left = left0; + + // UTF8/16 conversion + if (sub->is_utf16) + { + ConvertUTF16(b, left); + } + if (sub->is_utf16be) + { + ConvertUTF16BE(b, left); + } + else if (sub->is_utf8) + { + left0 -= ConvertUTF8(b, left); + lseek(fd, -left0, SEEK_CUR); + } + + buf_read_cnt++; + } + if (*left < 1) + { + if (*left < 0) + { + msg("Subtitle: cannot read file!\n"); + } + return NULL; + } + buf_read_left[new_bufidx] = *left; + buf_cnt[new_bufidx] = buf_read_cnt; + buf_idx = new_bufidx; + return b; +} + +void Subtitles::SavePos(int saveidx, BYTE *buf, int left) +{ + saved_bufidx[saveidx] = buf_idx; + saved_buf[saveidx] = buf; + saved_left[saveidx] = left; + saved_bufcnt[saveidx] = buf_cnt[buf_idx]; +} + +BOOL Subtitles::LoadPos(int saveidx, BYTE *&buf, int &left) +{ + if (buf_cnt[saved_bufidx[saveidx]] != saved_bufcnt[saveidx] || saved_buf[saveidx] == NULL) + return FALSE; + buf_idx = saved_bufidx[saveidx]; + buf = saved_buf[saveidx]; + left = saved_left[saveidx]; + return TRUE; +} + +/////////////////////////////////////////////////////////////// + +void Subtitle::Add(BYTE *str, int str_length, SubtitleFields result[2]) +{ + if (str_length > 0) + sub->AddString(str, str_length, false); + + SubtitleRecord rec; + rec.string_length = (WORD)str_length; + rec.delta_time = GetTime(result[0]); + records.Add(rec); + + if (result[1].is_set) + { + SubtitleRecord rec; + rec.string_length = 0; + rec.delta_time = GetTime(result[1]); + records.Add(rec); + } +} + +short Subtitle::GetTime(const SubtitleFields & f) +{ + int new_time = cur_time; + if (f.frames >= 0) + { + new_time = (int)(INT64(100000) * (LONGLONG)f.frames / fps); + } + else + { + if (f.sec256 >= 0 && f.msec256 >= 0) + { + new_time = f.sec256 * 100 + f.msec256 / 10; + if (new_time < last_256 || new_time > last_256 + 25600) + last_256_add += 25600; + last_256 = new_time; + new_time += last_256_add; + } + if (f.delta_sec != -1) + { + new_time += (f.delta_sec + 1) * 100; + } + if (f.delta_msec != -1) + { + new_time += (f.delta_msec + 1) / 10; + } + + if (f.sec >= 0) + { + new_time = f.hour * 360000 + f.min * 6000 + f.sec * 100 + f.msec / 10; + } + } + int delta = new_time - cur_time; + cur_time = new_time; + return (short)delta; +} + + +///////////////////////////////////////////////////////////////////// + +int SimilarityCompare(SPString *str1, SPString *str2) +{ + // this is a little bit slow + SPString s[2]; + s[0] = *str1; + s[1] = *str2; + for (int i = 0; i < 2; i++) + { + int name_pos = s[i].ReverseFind('/'); + if (name_pos > 0) + s[i] = s[i].Mid(name_pos + 1); + name_pos = s[i].ReverseFind('.'); + if (name_pos >= 0) + s[i] = s[i].Left(name_pos); + } + + return sub->vid_file.Similar(s[1]) - sub->vid_file.Similar(s[0]); +} + +BOOL read_subtitles(char *video_fname) +{ + static const char *subt_ext[] = { ".sub", ".srt", ".ssa", ".txt", NULL }; + int i; + SPSafeDelete(sub); + sub = new Subtitles; + + if (params == NULL) + return FALSE; + + fip_write_string("LoAd"); + + sub->InitTranslationTable(params->info.subtitle_charset); + sub->max_line_letters_cnt = params->info.subtitle_wrap; + + // now add subtitle files + for (i = 0; i < params->playlists.GetN(); i++) + { + if (params->playlists[i] == NULL) + continue; + SPList &il = params->playlists[i]->items; + + for (int j = 0; j < il.GetN(); j++) + { + SPString &name = il[j]->name; + // check extensions + bool found = false; + for (int k = 0; subt_ext[k] != NULL; k++) + { + if (name.FindNoCase(subt_ext[k]) == name.GetLength() - 4) + { + found = true; + break; + } + } + if (found) + { + if (sub->subs.GetN() < max_allowed_playlist_subs) + { + add_subtitle_file(name); + // enable first user-selected subtitle file by default + if (sub->subs.GetN() > 0) + sub->cur_sub = 0; + } + + // now remove item from the playlist + if (params->playlists[i]->itemhash != NULL) + params->playlists[i]->itemhash->Remove(*il[j]); + SPSafeDelete(il[j]); + il.Remove(j); + j--; + } + } + } + +//!!!!!!!!!!!!!!!!!!!! +//add_subtitle_file("/cdrom/SUB/MicroDVD.sub"); +//add_subtitle_file("/cdrom/SUB/MPSub.sub"); +//add_subtitle_file("/cdrom/SUB/DVDSubtitle.sub"); +//add_subtitle_file("/cdrom/SUB/SubViewer 1.0.sub"); +//add_subtitle_file("/cdrom/SUB/SubSonic.sub"); +//add_subtitle_file("/cdrom/SUB/SubRip.srt"); + +//add_subtitle_file("/cdrom/SUB/LOTR/DVD Architect.sub"); +//add_subtitle_file("/cdrom/SUB/LOTR/DVDSubtitle.sub"); +//add_subtitle_file("/cdrom/SUB/LOTR/MicroDVD.sub"); +//add_subtitle_file("/cdrom/SUB/LOTR/MPSub.sub"); +//add_subtitle_file("/cdrom/SUB/LOTR/SubRip.srt"); +//add_subtitle_file("/cdrom/SUB/LOTR/SubSonic.sub"); +//add_subtitle_file("/cdrom/SUB/LOTR/SubStation Alpha.ssa"); +//add_subtitle_file("/cdrom/SUB/LOTR/SubViewer1.sub"); +//add_subtitle_file("/cdrom/SUB/LOTR/SubViewer2.sub"); +//add_subtitle_file("/cdrom/SUB/LOTR/TMPlayer.sub"); +//add_subtitle_file("/cdrom/Scary_movie_RUS_2000.srt"); + + // try adding curdir subtitle, if there's only one (and it wasn't added) + if (sub->subs.GetN() == 0) + { + SPClassicList files; + DIR *dir = cdrom_opendir(params->folder); + SPString name, path = params->folder; + if (path.ReverseFind('/') != path.GetLength() - 1) + path += "/"; + while (dir != NULL) + { + struct dirent *d = cdrom_readdir(dir); + if (d == NULL) + break; + if ((d->d_name[0] == '.' && d->d_name[1] == '\0') || + (d->d_name[0] == '.' && d->d_name[1] == '.' && d->d_name[2] == '\0')) + continue; + struct stat64 statbuf; + name = path + d->d_name; + if (cdrom_stat(*name, &statbuf) < 0) + { + msg("Subtitle: Cannot stat %s\n", *name); + statbuf.st_mode = 0; + //continue; + } + if (S_ISDIR(statbuf.st_mode)) + continue; + + // is it subtitle file? + bool found = false; + for (int k = 0; subt_ext[k] != NULL; k++) + { + if (name.FindNoCase(subt_ext[k]) == name.GetLength() - 4) + { + found = true; + break; + } + } + if (found) + { + found = false; + for (int i = 0; i < sub->subs.GetN(); i++) + { + if (sub->subs[i]->fname.CompareNoCase(name) == 0) + { + found = true; + break; + } + } + if (!found) + files.Add(name); + } + } + cdrom_closedir(dir); + + sub->vid_file = video_fname; + int name_pos = sub->vid_file.ReverseFind('/'); + if (name_pos > 0) + sub->vid_file = sub->vid_file.Mid(name_pos + 1); + name_pos = sub->vid_file.ReverseFind('.'); + if (name_pos >= 0) + sub->vid_file = sub->vid_file.Left(name_pos); + + // now sort and cut the list + files.Sort(SimilarityCompare); + for (int i = 0; i < files.GetN() && sub->subs.GetN() < max_allowed_playlist_subs; i++) + { + add_subtitle_file(files[i]); + } + } + + return TRUE; +} + +BOOL delete_subtitles() +{ + SPSafeDelete(sub); + return TRUE; +} + +BOOL add_subtitle_file(char *fname) +{ + if (sub == NULL) + return FALSE; + + msg("Subtitle: Reading %s...\n", fname); + + sub->Reset(); + + sub->fd = cdrom_open(fname, O_RDONLY); + if (sub->fd < 0) + { + msg("Subtitle: cannot open file %s\n", fname); + return FALSE; + } + + int line_num = 0; + Subtitle *news = NULL; + + sub->cur_method = -1; + bool detected = false; + + BYTE str_buf[4096]; + + for (;;) + { + int left = 0; + BYTE *buf = sub->ReadMore(&left, !detected); + if (buf == NULL) + break; + + while (left > 0) + { + int method = MAX(sub->cur_method, 0); + SubtitleFields result[2]; + bool found = false; + do + { + sub->SavePos(0, buf, left); + if (sub->Scan(buf, left, line_num, method, result)) + { + found = true; + break; + } + // try again + if (sub->cur_method < 0) + { + if (!sub->LoadPos(0, buf, left)) + { + close(sub->fd); + return FALSE; + } + } + method++; + } while (sub->cur_method < 0 && sub_formats[method].name != NULL); + // skip or need more data? + if (!found) + { + do + { + while (left > 0 && *buf != '\n') + { + buf++; + left--; + } + } while (left < 1 && (buf = sub->ReadMore(&left)) != NULL); + do + { + while (left > 0 && (*buf == '\n' || *buf == '\r')) + { + buf++; + left--; + } + } while (left < 1 && (buf = sub->ReadMore(&left)) != NULL); + continue; + } + + sub->cur_method = method; + + // add subtitle to the list + if (!detected) + { + news = new Subtitle(); + news->fname = fname; + news->name = fname; + int name_pos = news->name.ReverseFind('/'); + if (name_pos > 0) + news->name = news->name.Mid(name_pos + 1); + name_pos = news->name.ReverseFind('.'); + if (name_pos >= 0) + news->name = news->name.Left(name_pos); + news->name = news->name.Left(10); + + if (sub->data.GetN() > 0) + { + news->data_idx = sub->data.GetN() - 1; + SubtitleData *d = sub->data[news->data_idx]; + news->data_start_pos = d->cur - d->ptr; + } else + { + news->data_idx = 0; + news->data_start_pos = 0; + } + news->cur_data_idx = news->data_idx; + news->cur_data_pos = news->data_start_pos; + sub->subs.Add(news); + detected = true; + + msg("Subtitle: * %s format detected!\n", sub_formats[sub->cur_method].name); + } + + // now read the string + BYTE *str = str_buf; + const char *end = sub_formats[method].end; + int str_length = 0; + found = false; + + while (left > 0 && str_length < 4096) + { + found = true; + sub->SavePos(1, buf, left); + const char *e = end; + do + { + for (; *e != '\0' && left > 0; buf++, left--) + { + if (*buf == '\r') + continue; + if (*buf != *e) + { + found = false; + break; + } + e++; + } + } while (left < 1 && (buf = sub->ReadMore(&left)) != NULL); + if (buf == NULL) + break; + if (found) + break; + sub->LoadPos(1, buf, left); + if (*buf != '\r') + { + *str++ = *buf; + str_length++; + } + + buf++; + left--; + + if (left < 1) + { + if ((buf = sub->ReadMore(&left)) == NULL) + { + close(sub->fd); + return FALSE; + } + } + } + + + if (news->fps == 0 && result[0].frames > 0) + { + char buf[128]; + int l = MIN(str_length, 100); + strncpy(buf, (const char *)str_buf, l); + buf[l] = '\0'; + int f1 = 0, f2 = 0; + sscanf(buf, "%d.%d", &f1, &f2); + news->fps = f1 * 1000 + f2; + continue; + } + + if (result[0].line_number > 1) + { + // attach to previous string + if (str_length > 0) + { + sub->AddString(str_buf, str_length, true); + SubtitleRecord &rec = news->records[news->records.GetN() - 1]; + rec.string_length = (WORD)(rec.string_length + str_length + 1); + } + } else + { + news->Add(str_buf, str_length, result); + line_num = (line_num + 1) & 1; + } + } + + // if didn't guess, no need to read more... + if (sub->cur_method < 0) + { + msg("Subtitle: Error! Unknown subtitle file format.\n"); + close(sub->fd); + return FALSE; + } + } + + news->is_ready = true; + if (news->records.GetN() > 0) + { + news->cur_pts = news->records[0].delta_time * 900; + news->cur_record = 0; + } + + close(sub->fd); + +//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +#if 0 +{ +int sub_idx = 0; +if (sub->subs.GetN() > sub_idx && sub->subs[sub_idx]->is_ready) +{ + int pi = sub->subs[sub_idx]->data_idx, pp = sub->subs[sub_idx]->data_start_pos, t = 0; + for (int z = 0; z < sub->subs[sub_idx]->records.GetN(); z++) + { + int l = sub->subs[sub_idx]->records[z].string_length; + char *s; + t += sub->subs[sub_idx]->records[z].delta_time; + if (l > 0) + { + s = sub->data[pi]->ptr + pp; + pp += l + 1; + } + else + s = ""; + if (sub->data.GetN() > 0) + if (pp >= sub->data[pi]->len) + { + if (pi < sub->data.GetN() - 1) + pi++; + pp = 0; + } + } +} +} +#endif + return TRUE; +} + +BOOL show_subtitles(LONGLONG pts) +{ + if (sub == NULL || sub->cur_sub < 0 || pts < 0) + return FALSE; + + Subtitle *cur = sub->subs[sub->cur_sub]; + + const char *s = NULL; + if (pts > cur->cur_pts) + { + for (int z = cur->cur_record; z < cur->records.GetN(); z++) + { + SubtitleRecord &r = cur->records[z]; + LONGLONG d_pts = (LONGLONG)((int)r.delta_time * 900); + + if (z != cur->cur_record) + { + cur->cur_pts += d_pts; + cur->cur_record = z; + if (pts < cur->cur_pts) + break; + } + + cur->cur_last_pts = cur->cur_pts; + cur->cur_last_record = z; + int l = cur->records[cur->cur_record].string_length; + if (l > 0 && sub->data.GetN() > 0) + { + s = sub->data[cur->cur_data_idx]->ptr + cur->cur_data_pos; + cur->cur_data_pos += l + 1; + if (cur->cur_data_pos >= sub->data[cur->cur_data_idx]->len) + { + if (cur->cur_data_idx < sub->data.GetN() - 1) + cur->cur_data_idx++; + cur->cur_data_pos = 0; + } + } + else + s = ""; + } + } + else if (pts < cur->cur_last_pts && cur->cur_last_record > 0) + { + int z; + for (z = cur->cur_last_record; z >= 0; z--) + { + SubtitleRecord &r = cur->records[z]; + LONGLONG d_pts = (LONGLONG)((int)r.delta_time * 900); + + cur->cur_pts = cur->cur_last_pts; + cur->cur_record = cur->cur_last_record; + cur->cur_last_record--; + cur->cur_last_pts -= d_pts; + + int l = cur->records[z].string_length; + if (l > 0 && sub->data.GetN() > 0) + { + cur->cur_data_pos -= l + 1; + if (cur->cur_data_pos < 0) + { + if (cur->cur_data_idx > 0) + { + cur->cur_data_idx--; + cur->cur_data_pos = sub->data[cur->cur_data_idx]->len - (l + 1); + } else + cur->cur_data_pos = 0; + } + } + + if (pts > cur->cur_last_pts) + break; + } + if (z < 0 || cur->cur_last_pts == 0) + { + cur->cur_last_pts = 0; + cur->cur_last_record = -1; + if (cur->records.GetN() > 0) + { + cur->cur_pts = cur->records[0].delta_time * 900; + cur->cur_record = 0; + } + } + // don't show subtitles in reverse mode + s = ""; + } + + if (s != NULL) + { + int t = (int)(pts / 900); + msg("[%02d:%02d:%02d.%02d] %s\n\n", t/360000, (t % 360000) / 6000, (t % 6000) / 100, (t % 100), s); + script_player_subtitle_callback(s); + return TRUE; + } + return FALSE; +} + +BOOL subtitle_next() +{ + if (sub == NULL || sub->subs.GetN() < 1 || params == NULL) + return FALSE; + if (++sub->cur_sub >= sub->subs.GetN()) + sub->cur_sub = -1; + params->info.spu_stream = sub->cur_sub + 1; + if (sub->cur_sub >= 0) + { + params->info.spu_lang = sub->subs[sub->cur_sub]->name; + msg("Subtitle: * Set to '%s'.\n", *params->info.spu_lang); + } + else + { + params->info.spu_lang = SPString(); + msg("Subtitle: * Disabled.\n"); + } + + script_player_subtitle_callback(""); + return TRUE; +} diff --git a/src/subtitle.h b/src/subtitle.h new file mode 100644 index 0000000..8ca3f71 --- /dev/null +++ b/src/subtitle.h @@ -0,0 +1,280 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - Subtitles for video player header file + * \file subtitle.h + * \author bombur + * \version 0.1 + * \date 01.10.2008 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#ifndef SP_SUBTITLE_H +#define SP_SUBTITLE_H + +/// 1 Subtitle record +typedef struct SubtitleRecord +{ + short delta_time; // in 1/100 secs + WORD string_length; // string length (not including '\0') +} SubtitleRecord; + +typedef struct SubtitleFormat +{ + const char *name; + const char *start[32]; + const char *end; +} SubtitleFormat; + +typedef struct SubtitleFields +{ + int hour; + int min; + int sec; + int msec; + int sec256; + int msec256; + int delta_sec; + int delta_msec; + int frames; + int line_number; + bool is_set; +} SubtitleFields; + +#define SUB_TOKEN_HOUR1 0x1 +#define SUB_TOKEN_HOUR2 0x101 +#define SUB_TOKEN_HOUR1_1 0x2 +#define SUB_TOKEN_HOUR2_1 0x102 + +#define SUB_TOKEN_MIN1 0x3 +#define SUB_TOKEN_MIN2 0x103 + +#define SUB_TOKEN_SEC1 0x4 +#define SUB_TOKEN_SEC2 0x104 + +#define SUB_TOKEN_DSEC1 0x5 +#define SUB_TOKEN_DSEC2 0x105 + +#define SUB_TOKEN_MSEC1 0x6 +#define SUB_TOKEN_MSEC2 0x106 + +#define SUB_TOKEN_SEC1_256 0x7 +#define SUB_TOKEN_DSEC1_256 0x8 + +#define SUB_TOKEN_DELTA_SECS1 0x9 +#define SUB_TOKEN_DELTA_SECS2 0x109 + +#define SUB_TOKEN_DELTA_MSECS1 0xa +#define SUB_TOKEN_DELTA_MSECS2 0x10a + +#define SUB_TOKEN_FRAMES1 0xb +#define SUB_TOKEN_FRAMES2 0x10b + +#define SUB_TOKEN_LINE_NUMBER 0xc + +// special +#define SUB_TOKEN_SKIP_DIGITS 0x20 +#define SUB_TOKEN_SKIP_DIGITS_4 0x24 +#define SUB_TOKEN_SKIP_TO_NEXT 0x30 +#define SUB_TOKEN_OPTIONAL_NEXT 0x40 +#define SUB_TOKEN_OPTIONAL_NEXT_2 0x41 + +#define SUB(s) (const char *)s + +enum SUB_CUR_FORMAT +{ + SUB_CUR_FORMAT_STRING = 0, + SUB_CUR_FORMAT_DIGITS, + SUB_CUR_FORMAT_DIGITS_SIGN, +}; + +enum SUB_CUR_MODE +{ + SUB_CUR_MODE_NORMAL = 0, + SUB_CUR_MODE_SEARCH_NEXT, + SUB_CUR_MODE_OPTIONAL, +}; + +enum SUB_CUR_FIELD +{ + SUB_CUR_FIELD_NONE = 0, + SUB_CUR_FIELD_HOUR, + SUB_CUR_FIELD_MIN, + SUB_CUR_FIELD_SEC, + SUB_CUR_FIELD_SEC256, + SUB_CUR_FIELD_MSEC, + SUB_CUR_FIELD_MSEC256, + SUB_CUR_FIELD_DELTA_SEC, + SUB_CUR_FIELD_DELTA_MSEC, + SUB_CUR_FIELD_FRAMES, + SUB_CUR_FIELD_LINE_NUMBER, +}; + +enum SUBTITLE_CHARSET +{ + SUBTITLE_CHARSET_DEFAULT, + SUBTITLE_CHARSET_ISO8859_1, + SUBTITLE_CHARSET_ISO8859_2, + SUBTITLE_CHARSET_CP1251, + SUBTITLE_CHARSET_KOI8R, +}; + +const int max_string_length = 8192; +const int read_buf_length = 4096; + +/////////////////////////////////////////////////////////// + +class Subtitle +{ +public: + /// ctor + Subtitle() + { + is_ready = false; + data_idx = 0; + data_start_pos = 0; + fps = 0; + cur_time = 0; + last_256 = 0; + last_256_add = 0; + + cur_record = 0; + cur_last_record = -1; + cur_pts = cur_last_pts = 0; + cur_data_idx = 0; + cur_data_pos = 0; + } + + void Add(BYTE *str, int str_length, SubtitleFields result[2]); + + /// Get time, in 1/100s of second. + short GetTime(const SubtitleFields & f); + +public: + SPList records; + bool is_ready; + + int data_idx, data_start_pos; + LONGLONG fps; + SPString fname, name; + + int cur_time; + int last_256, last_256_add; + + // the _next_ record we're waiting for + int cur_record, cur_data_idx, cur_data_pos; + int cur_last_record; + LONGLONG cur_pts, cur_last_pts; +}; + + +class SubtitleData +{ +public: + /// ctor + SubtitleData() + { + ptr = NULL; + cur = NULL; + len = 0; + left = max_string_length; + } + /// dtor + ~SubtitleData() + { + SPSafeFree(ptr); + } + + BOOL Alloc(int add_size); + +public: + char *ptr, *cur; + int len, left; +}; + +class Subtitles +{ +public: + /// ctor + Subtitles(); + /// dtor + ~Subtitles(); + + /// Reset subtitle parser. + void Reset(); + + void InitTranslationTable(SUBTITLE_CHARSET charset); + + /// Read more data to the buffer + BYTE *ReadMore(int *left, bool force_read = false); + + /// Add string and return offset + int AddString(BYTE *, int &length, bool attach); + + int Scan(BYTE *&buf, int &left, int line_num, int method, SubtitleFields fields[2]); + + void SavePos(int saveidx, BYTE *buf, int left); + BOOL LoadPos(int saveidx, BYTE *&buf, int &left); + +public: + SPList subs; + SPList data; + + /// Current video file being played. + SPString vid_file; + + int max_line_letters_cnt; + + BYTE buf[2][read_buf_length]; + int buf_idx; + int buf_cnt[2]; // used to identify buffers (check for changes and avoid double reading) + int buf_read_left[2]; + int buf_read_cnt; + int fd; + + int cur_method; + + int cur_sub; + + int saved_bufidx[2], saved_left[2]; + int saved_bufcnt[2]; + BYTE *saved_buf[2]; + + BYTE charset_translation_table[256]; + BYTE utf16_to_ansi_table[2048]; + + bool is_start_of_file; + bool is_utf8, is_utf16, is_utf16be; + +protected: + + int ConvertUTF8(BYTE *buf, int *left); // returns number of bytes read + void ConvertUTF16(BYTE *buf, int *left); + void ConvertUTF16BE(BYTE *buf, int *left); + void FillUTFTable(SUBTITLE_CHARSET charset); +}; + +BOOL read_subtitles(char *video_fname); + +BOOL delete_subtitles(); + +BOOL add_subtitle_file(char *fname); + +BOOL show_subtitles(LONGLONG pts); + +BOOL subtitle_next(); + +#endif // of SP_SUBTITLE_H diff --git a/src/version-DX.h b/src/version-DX.h new file mode 100644 index 0000000..e84d349 --- /dev/null +++ b/src/version-DX.h @@ -0,0 +1,5 @@ + + +const char * const SP_NAME = "SP-DX"; + +const int SP_VERSION = 151; diff --git a/src/version-MP.h b/src/version-MP.h new file mode 100644 index 0000000..8bd03af --- /dev/null +++ b/src/version-MP.h @@ -0,0 +1,4 @@ + +const char * const SP_NAME = "SP-MP"; + +const int SP_VERSION = 151; diff --git a/src/version-MT.h b/src/version-MT.h new file mode 100644 index 0000000..4bdcafe --- /dev/null +++ b/src/version-MT.h @@ -0,0 +1,4 @@ + +const char * const SP_NAME = "SP-MT"; + +const int SP_VERSION = 151; diff --git a/src/version-WIN.h b/src/version-WIN.h new file mode 100644 index 0000000..e9617fe --- /dev/null +++ b/src/version-WIN.h @@ -0,0 +1,4 @@ + +const char * const SP_NAME = "SP-WIN"; + +const int SP_VERSION = 151; diff --git a/src/version.h b/src/version.h new file mode 100644 index 0000000..4d8c75f --- /dev/null +++ b/src/version.h @@ -0,0 +1,22 @@ +#ifndef SP_VERSION_H +#define SP_VERSION_H + + +#ifdef SP_PLAYER_TECHNOSONIC + #include "version-MP.h" +#endif + +#ifdef SP_PLAYER_DREAMX108 + #include "version-DX.h" +#endif + +#ifdef SP_PLAYER_MECOTEK + #include "version-MT.h" +#endif + +#ifdef WIN32 + #include "version-WIN.h" +#endif + + +#endif // of SP_VERSION_H diff --git a/src/video.cpp b/src/video.cpp new file mode 100644 index 0000000..2238594 --- /dev/null +++ b/src/video.cpp @@ -0,0 +1,2125 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - Video player source file. + * \file video.cpp + * \author bombur + * \version 0.1 + * \date 07.03.2007 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#ifdef INTERNAL_VIDEO_PLAYER + +#define RESYNC_TIMEOUT INT64(120000) + +#define NEW_SYNC +#define USE_RESYNCS_CONTROL + +//#define SKIP_AUDIO + +#define VIDEO_INTERNAL +#include +#include +#include +#include +#include +#include + +int num_packets = 0; +static int info_cnt = 0; +bool video_msg = true; +#define MSG if (video_msg) msg + +static int max_info_cnt = 32; +static const int DIVX3_BUF_MARGIN = 128; + + +//#define VIDEO_PACKET_DEBUG +//#define VIDEO_USE_MMAP + +#define DUMP_FRAME msg +//#define DUMP_FRAME gui_update();msg + +static Video *video = NULL; + +BYTE *VideoAlloc(int size) +{ +#ifdef VIDEO_USE_MMAP + return (BYTE *)mmap(0, size, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0); +#else + return (BYTE *)SPmalloc(size); +#endif +} + +void VideoFree(BYTE *&buf, int /*size*/) +{ + if (buf != NULL) + { +#ifdef VIDEO_USE_MMAP + munmap(buf, size); +#else + SPfree(buf); +#endif + buf = NULL; + } + +} + +//////////////////////////////////////// +// some helper functions for MPEG stream parsing + +static void video_bits_init(BYTE *buf, int len); +static int video_skip_bits(int num_bits); +static DWORD video_get_bits(int num_bits); +static int video_log2(DWORD v); + + +Video::Video() +{ + int i; + type = VIDEO_CONTAINER_UNKNOWN; + + playing = false; + stopping = false; + is_eof = false; + event = MEDIA_EVENT_OK; + scale = 1000; + rate = 24000; + scr = 0; + saved_pts = 0; + displ_pts_base = 0; + video_fmt = RIFF_VIDEO_UNKNOWN; + audio_fmt = RIFF_AUDIO_UNKNOWN; + chunktype = VIDEO_CHUNK_UNKNOWN; + chunkleft = 0; + look4chunk = false; + test_for_qpel_gmc = true; + wait_for_resync = false; + + for (i = 0; i < 16; i++) + { + mux_buf[i] = NULL; + allocated_mux_buf[i] = NULL; + allocated_mux_buf_size[i] = 0; + divx_buf[i] = NULL; + divx_base[i] = NULL; + } + mux_numbufs = 12; + mux_bufsize = 32768; + allocated_numbufs = 0; + no_partial = false; + + fd = -1; + + width = height = 0; + fps = 0; + qpel_flag = false; + gmc_flag = false; + video_frames = 0; + fourcc[0] = '\0'; + + cur_track = 0; + first_track = 0; + num_tracks = 0; + cur_audio_bits = 0; + + for (i = 0; i < VIDEO_MAX_AUDIO_TRACKS; i++) + { + track[i].wfe = NULL; + } + + min_delta_pts = 2000; + max_delta_pts = 8000; + minus_delta_pts = 0; + saved_video_pts = 0; + video_pos_base = 0; + abs_video_pos = 0; + ResetAvgPts(); + + total_frames = 0; + audio_total_bytes = 0; + audio_delta = SPTM_SCR_FLAG; + audio_pts_offset = 0; + + video_pos = 0; + last_key_pos = -min_delta_keyframes - 1; + cur_key_pos = 0; + + cur_key_idx = 0; + last_good_key_idx = -1; + last_key_idx = -1; + + frame_pos = 0; + last_frame_pos = 0; + + max_rev_incr = def_max_rev_incr; + + searching = false; + skip_fwd = false; + skip_rev = false; + skip_wait = false; + skip_waiting = false; + last_keyframe_time = 0; + delayed_skip = 0; + cancel_skip = false; + audio_halfrate = false; + skip_cnt = 0; + + cur_offs = 0; + + video_packet_len = 0; + old_video_packet_len = 0; + + good_delta_stc = 90000; + + in_divx_packet = false; + need_to_stop = false; +} + +Video::~Video() +{ + int i; + for (i = VIDEO_MAX_AUDIO_TRACKS - 1; i >= 0; i--) + SPSafeFree(track[i].wfe); + for (i = 15; i >= 0; i--) + { + //SPSafeFree(divx_base[i]); + VideoFree(divx_base[i], (video->mux_bufsize + DIVX3_BUF_MARGIN * 2 + 8)); + } + for (i = allocated_numbufs - 1; i >= 0; i--) + { + //SPSafeFree(mux_buf[i]); + VideoFree(allocated_mux_buf[i], allocated_mux_buf_size[i]); + } +} + +VideoKeyFrame *Video::GetIndex(int idx) +{ + int y = idx / num_key_frames_block; + if (y >= indexes.GetN()) + return NULL; + int x = idx % num_key_frames_block; + if (x >= indexes[y].GetN()) + return NULL; + return &indexes[y][x]; +} + +int Video::GetKeyFrame(LONGLONG time) +{ + int idx1 = 0, idx2 = last_key_idx; + static LONGLONG stime; + + bool need_to_restore_pos = false; + + if (time == VIDEO_KEY_FRAME_CONTINUE) + time = stime; + else + stime = time; + + switch (time) + { + case VIDEO_KEY_FRAME_NEXT: + msg("Video: SEARCH NEXT!\n"); + idx1 = idx2 = cur_key_idx + 1; + skip_cnt++; + break; + case VIDEO_KEY_FRAME_PREV: + msg("Video: SEARCH PREV!\n"); + idx1 = idx2 = cur_key_idx - 1; + skip_cnt++; + break; + default: + { + msg("Video: SEARCH TIME = %d !\n", (int)time); + VideoKeyFrame *curidx = GetIndex(cur_key_idx); + if (curidx != NULL) + { + if (time < curidx->i * scale / rate) + { + idx1 = 0; idx2 = cur_key_idx - 1; + } else + { + idx1 = cur_key_idx; idx2 = last_key_idx; + } + } + } + break; + } + + // now we have idx range, so use dichotomy + int ret = 0; + while (idx1 <= idx2) + { + VideoKeyFrame *lastidx = GetIndex(last_key_idx); + if (lastidx != NULL) + { + LONGLONG last_time = lastidx->i * scale / rate; + if (time > last_time) + { + idx1 = last_key_idx + 1; + ret = GetNextIndexes(); + need_to_restore_pos = true; + idx2 = MAX(last_key_idx, idx1); + if (ret < 0) + break; + continue; + } + } + // if we need to extend index more... + if (idx2 > last_key_idx) + { + int k; + for (k = 0; k < max_rev_incr; k++) + { + ret = GetNextIndexes(); + need_to_restore_pos = true; + if (ret != 0) + break; + } + max_rev_incr += def_max_rev_incr; + if (ret < 0 && k == 0) + break; + ret = 0; + continue; + } + if (idx1 == idx2) + break; + // ok, now all range is inside buf + int newidx = (idx1 + idx2) / 2; + VideoKeyFrame *median = GetIndex(newidx); + if (median == NULL) + break; + if (time <= median->i * scale / rate) + { + idx2 = newidx; + } else + { + idx1 = newidx + 1; + } + } + +msg("[%d] ret=%d idx1=%d idx2=%d\n", cur_key_idx, ret, idx1, idx2); + + if (ret >= 0 && idx1 == idx2) + { + if (idx1 < 0) + { + video->cancel_skip = true; + return 0; + } + VideoKeyFrame *idx = GetIndex(idx1); + if (idx != NULL) + { + abs_video_pos = idx->i; + last_chunk_len = video_packet_len = idx->len; + cur_key_offs = last_chunk_offs = video_offs = idx->offs; + video_lseek(video_offs, SEEK_SET); + MSG("AVI: I-FRAME #%d @ (" PRINTF_64d ",%d)\n", abs_video_pos, idx->offs, idx->len); + if (time != VIDEO_KEY_FRAME_NEXT && time != VIDEO_KEY_FRAME_PREV) + cur_key_idx = 0; + last_good_key_idx = idx1; + scr = INT64(90000) * abs_video_pos * scale / rate; + info_cnt = max_info_cnt + 1; + return 1; + } + } + + // we failed to search in index + if (ret < 0 && time == VIDEO_KEY_FRAME_NEXT) + { + if (need_to_restore_pos) + { + if (last_chunk_len < 1) + return -1; + video_lseek(last_chunk_offs + 8 + PAD_EVEN(last_chunk_len), SEEK_SET); + } + return GetNextKeyFrame(); + } + + return -1; +} + +void Video::UpdateTotalTime() +{ + // do nothing, normally total play time cannot change +} + +int Video::SetCurrentIndex(int i, int len, LONGLONG offs) +{ + int cur_index_x, cur_index_y; + if (i >= last_key_pos + min_delta_keyframes) + { + if (AddIndex(i, len, offs) < 0) + return -1; + } + if (cur_key_idx > 0) + { + int cki = cur_key_idx - 1; + cur_index_x = cki % num_key_frames_block; + for (cur_index_y = cki / num_key_frames_block; cur_index_y >= 0; cur_index_y--, cur_index_x = indexes[cur_index_y].GetN() - 1) + { + for (; cur_index_x >= 0; cur_index_x--, cki--) + { + if (indexes[cur_index_y][cur_index_x].i < i) + { + goto next; + } + cur_key_idx = cki; + } + } + } + +next: + // find new index positions... + cur_index_x = cur_key_idx % num_key_frames_block; + for (cur_index_y = cur_key_idx / num_key_frames_block; cur_index_y < indexes.GetN(); cur_index_y++, cur_index_x = 0) + { + for (; cur_index_x < indexes[cur_index_y].GetN(); cur_index_x++, cur_key_idx++) + { + if (indexes[cur_index_y][cur_index_x].i >= i) + { + return 0; + } + } + } + return 0; +} + +int Video::AddIndex(int i, int len, LONGLONG offs) +{ + static VideoKeyFrame kf; + +//msg("*** %d %d %I64d\n", i, len, offs); + + if (indexes.GetN() < 1 || indexes[indexes.GetN() - 1].GetN() >= num_key_frames_block) + { + indexes.SetN(indexes.GetN() + 1); + indexes[indexes.GetN() - 1].Reserve(num_key_frames_block); + } + int y = indexes.GetN() - 1; + + kf.i = i; + kf.len = len; + kf.offs = offs; + int x = indexes[y].Add(kf); + if (x < 0) + return -1; + + last_key_pos = i; + last_key_idx = y * num_key_frames_block + x; + return 0; +} + +void Video::ResetAvgPts() +{ + for (int i = 0; i < saved_delta_pts_num; i++) + saved_delta_pts[i] = 0; + saved_delta_pts_idx = 0; + avg_delta_pts = 0; + +} + +LONGLONG Video::FixAudioPts(LONGLONG pts) +{ + static int num_corrections = 0; + LONGLONG delta = avg_delta_pts / saved_delta_pts_num; + if (delta > max_delta_pts + min_delta_pts) + { + minus_delta_pts = delta - min_delta_pts; + if (num_corrections++ < 10) + msg("AVI: Audio far ahead video (%d). Correction = %d.\n", (int)delta, (int)minus_delta_pts); + // reset avg + ResetAvgPts(); + } + + pts -= minus_delta_pts; + + avg_delta_pts -= saved_delta_pts[saved_delta_pts_idx]; + saved_delta_pts[saved_delta_pts_idx++] = pts - saved_video_pts; + avg_delta_pts += pts - saved_video_pts; + saved_delta_pts_idx %= saved_delta_pts_num; + + + + return pts; +} + +//////////////////////////////////////////////// + +int video_play(char *filepath) +{ + if (filepath == NULL) + { + if (video != NULL) + { + if (video->skip_fwd || video->skip_rev || video->searching) + video->cancel_skip = true; + else + { + fip_write_special(FIP_SPECIAL_PLAY, 1); + fip_write_special(FIP_SPECIAL_REPEAT, 0); + fip_write_special(FIP_SPECIAL_PAUSE, 0); + mpeg_setspeed(MPEG_SPEED_NORMAL); + } + } + return 0; + } + + if (strncasecmp(filepath, "/cdrom/", 7) != 0 && strncasecmp(filepath, "/hdd/", 5) != 0) + return -1; + + int ret = media_open(filepath, MEDIA_TYPE_VIDEO); + if (ret < 0) + { + msg_error("Video: media open FAILED.\n"); + video_stop(); + return ret; + } + MSG("Video: Start...\n"); + + num_packets = 0; + + video_detect_formats(); + + if (video->video_fmt == RIFF_VIDEO_UNKNOWN) + { + msg_error("Video: Video codec not supported.\n"); + script_error_callback(SCRIPT_ERROR_BAD_CODEC); + video_stop(); + return -1; + } + + video->scr = 0; + + if (video->video_fmt == RIFF_VIDEO_MPEG12) + { + max_info_cnt = 1; + + mpeg_init(MPEG_2, FALSE, TRUE, TRUE); + video->mux_numbufs = 16; + video->mux_bufsize = 32768; + mpeg_setbuffer(MPEG_BUFFER_1, BUF_BASE, video->mux_numbufs, video->mux_bufsize); + + ((VideoMpg *)video)->GetTimeLength(); + } else + { + max_info_cnt = 16; + + //video->UpdateTotalTime(); + int totaltime = video->rate != 0 ? (int)((LONGLONG)video->video_frames * video->scale / video->rate) : 0; + script_totaltime_callback(totaltime); + + video->indexes.Reserve(1024); + + mpeg_init(MPEG_4, FALSE, TRUE, FALSE); + + mpeg_set_scale_rate((DWORD *)&video->scale, (DWORD *)&video->rate); + video->good_delta_stc = RESYNC_TIMEOUT * video->scale / video->rate; + + int tvtype = settings_get(SETTING_TVTYPE); + KHWL_VIDEOMODE vmode = KHWL_VIDEOMODE_NORMAL; + if (tvtype == 2 || tvtype == 3) + vmode = KHWL_VIDEOMODE_WIDE; + //khwl_setvideomode(KHWL_VIDEOMODE_NORMAL, TRUE); + khwl_setvideomode(vmode, FALSE); + + mpeg_setframesize(video->width, video->height, true); + + if (video->video_fmt == RIFF_VIDEO_DIV3) + { + video->mux_numbufs = 2; + video->mux_bufsize = 200960; + } + + bool all_allocated = false; + int max_allocs[4]; +#if 0 + max_allocs[0] = 1; + max_allocs[1] = 2; + max_allocs[2] = video->mux_numbufs; + max_allocs[3] = 0; +#endif + max_allocs[0] = video->mux_numbufs; + max_allocs[1] = 0; + + for (int num_tries = 0; max_allocs[num_tries] != 0; num_tries++) + { + all_allocated = true; + int i = 0; + for (int num_allocs = 0; num_allocs < max_allocs[num_tries]; num_allocs++) + { + int nb = video->mux_numbufs / max_allocs[num_tries]; + int bsize = video->mux_bufsize * nb + 8; + BYTE *buf = VideoAlloc(bsize); + if (buf == NULL) + { + all_allocated = false; + break; + } + video->allocated_mux_buf_size[video->allocated_numbufs] = bsize; + video->allocated_mux_buf[video->allocated_numbufs] = buf; + video->allocated_numbufs++; + + for (int j = 0; j < nb; j++, i++) + { + video->mux_buf[i] = buf; + buf += video->mux_bufsize; + } + } + if (all_allocated) + break; + } + if (!all_allocated) + { + msg_error("Video: Cannot allocate mux buffers.\n"); + video_stop(); + return -1; + } + mpeg_setbuffer_array(MPEG_BUFFER_1, video->mux_buf, video->mux_numbufs, video->mux_bufsize); + } + + if (video->video_fmt == RIFF_VIDEO_DIV3) + { + khwl_display_clear(); + script_error_callback(SCRIPT_ERROR_WAIT); + gui_update(); + + if (divx_transcode_preinit() < 0) + { + msg_error("Video: Cannot pre-init DivX3 transcoder.\n"); + video_stop(); + return -1; + } + + if (divx_transcode_init(video->width, video->height, video->rate) < 0) + { + msg_error("Video: Cannot init DivX3 transcoder.\n"); + video_stop(); + return -1; + } + + for (int i = 0; i < video->mux_numbufs; i++) + { + video->divx_base[i] = VideoAlloc(video->mux_bufsize + DIVX3_BUF_MARGIN * 2 + 8); + if (video->divx_base[i] == NULL) + { + msg_error("Video: Cannot allocate DivX3 transcoding buffer [%d].\n", i); + video_stop(); + return -1; + } + video->divx_buf[i] = video->divx_base[i] + DIVX3_BUF_MARGIN; + } + + mpeg_setbuffer_array(MPEG_BUFFER_3, video->divx_buf, video->mux_numbufs, video->mux_bufsize); + video->divx_cur_bufpos = 0; + video->divx_cur_bufleft = mpeg_getbufsize(MPEG_BUFFER_3); + + video->no_partial = true; + + //script_error_callback(SCRIPT_ERROR_NONE); + } + media_skip_buffer(NULL); + + // if we need a separate decompression for audio... + if (video->video_fmt != RIFF_VIDEO_MPEG12) + { + if (video->audio_fmt != RIFF_AUDIO_UNKNOWN && video->audio_fmt != RIFF_AUDIO_NONE) + { + video->audio_halfrate = (video->video_fmt == RIFF_VIDEO_DIV3); + if (!audio_init(video->audio_fmt, video->audio_halfrate)) + { + msg_error("Video: Cannot init audio decompressor.\n"); + script_error_callback(SCRIPT_ERROR_BAD_AUDIO); + } + } else + script_audio_info_callback(""); + + if (video->first_track >= 0) + { + if (video_set_audio_track(video->first_track) < 0) + { + msg_error("Video: Cannot init audio.\n"); + script_error_callback(SCRIPT_ERROR_BAD_AUDIO); + } + } else + MSG("Video: * No audio tracks...\n"); + + audio_set_ac3_getinfo(false); + } else + script_audio_info_callback(""); + + // read & parse subtitle files + read_subtitles(filepath); + + // write dvd-specific FIP stuff... + const char *digits = " 00000"; + fip_write_string(digits); + fip_write_special(FIP_SPECIAL_COLON1, 1); + fip_write_special(FIP_SPECIAL_COLON2, 1); + + fip_write_special(FIP_SPECIAL_PLAY, 1); + fip_write_special(FIP_SPECIAL_REPEAT, 0); + fip_write_special(FIP_SPECIAL_PAUSE, 0); + + script_time_callback(0); + + script_video_info_callback(video->video_fmt_str); + if (video->video_fmt != RIFF_VIDEO_MPEG12) + { + script_framesize_callback(video->width, video->height); + script_framerate_callback(mpeg_get_fps()); + } + + video->playing = true; + video->searching = false; + video->stopping = false; + video->skip_fwd = false; + video->skip_rev = false; + video->skip_wait = false; + video->skip_waiting = false; + + video->in_divx_packet = false; + video->need_to_stop = false; + + info_cnt = 0; + mpeg_start(); + + return 0; +} + +bool video_search_fourcc(DWORD fourcc, const DWORD *values) +{ + for (int i = 0; values[i] != 0xffffffff; i++) + { + if (fourcc == values[i]) + return true; + } + return false; +} + +void video_detect_formats() +{ + static const DWORD mpeg12[] = { + FOURCC('M','P','E','G'), FOURCC('m','p','e','g'), + FOURCC('P','I','M','1'), FOURCC('p','i','m','1'), + FOURCC('M','P','G','2'), FOURCC('m','p','g','2'), + 0xffffffff + }; + static const DWORD div3[] = { + FOURCC('D','I','V','3'), FOURCC('d','i','v','3'), + FOURCC('D','I','V','4'), FOURCC('d','i','v','4'), + FOURCC('D','I','V','5'), FOURCC('d','i','v','5'), + FOURCC('D','I','V','6'), FOURCC('d','i','v','6'), + FOURCC('M','P','4','3'), FOURCC('m','p','4','3'), + FOURCC('A','P','4','1'), FOURCC('a','p','4','1'), + FOURCC('M','P','G','3'), FOURCC('m','p','g','3'), + FOURCC('C','O','L','0'), FOURCC('c','o','l','0'), + FOURCC('C','O','L','1'), FOURCC('c','o','l','1'), + FOURCC('N','A','V','I'), FOURCC('S','A','N','3'), + 0xffffffff + }; + static const DWORD mpeg4[] = { + FOURCC('D','I','V','X'), FOURCC('d','i','v','x'), + FOURCC('D','i','v','x'), FOURCC('D','i','v','X'), + FOURCC('D','X','5','0'), FOURCC('d','x','5','0'), + FOURCC('M','P','4','S'), FOURCC('m','p','4','s'), + FOURCC('M','P','4','V'), FOURCC('m','p','4','v'), + FOURCC('M','4','S','2'), FOURCC('m','4','s','2'), + FOURCC('F','M','P','4'), FOURCC('f','m','p','4'), + FOURCC('X','V','I','D'), FOURCC('x','v','i','d'), + FOURCC('3','I','V','0'), FOURCC('3','I','V','1'), + FOURCC('3','I','V','2'), FOURCC('3','I','V','D'), + FOURCC('3','I','V','X'), FOURCC('3','V','I','D'), + FOURCC('A','T','M','4'), FOURCC('B','L','Z','0'), + FOURCC('F','V','F','W'), FOURCC('H','D','X','4'), + FOURCC('D','M','4','V'), FOURCC('M','P','G','4'), + FOURCC('M','4','C','C'), FOURCC('m','4','c','c'), + FOURCC('P','V','M','M'), FOURCC('R','M','P','4'), + FOURCC('S','E','D','G'), FOURCC('N','D','I','G'), + FOURCC('W','V','1','F'), FOURCC('X','V','I','X'), + 0xffffffff + + }; + + DWORD v4cc = FOURCC(video->fourcc[0], video->fourcc[1], video->fourcc[2], video->fourcc[3]); + + if (video_search_fourcc(v4cc, mpeg12)) + { + video->video_fmt = RIFF_VIDEO_MPEG12; + video->video_fmt_str.Printf("MPEG-1/2 ('%s')", video->fourcc); + } + else if (video_search_fourcc(v4cc, div3)) + { + video->video_fmt = RIFF_VIDEO_DIV3; + video->video_fmt_str.Printf("DivX3 ('%s')", video->fourcc); + } + else if (video_search_fourcc(v4cc, mpeg4)) + { + video->video_fmt = RIFF_VIDEO_MPEG4; + video->video_fmt_str.Printf("MPEG-4 ('%s')", video->fourcc); + } + MSG("Video: * Video format %s detected!\n", *video->video_fmt_str); + + if (video->video_fmt != RIFF_VIDEO_MPEG12) + { + if (video->cur_track < 0 || video->track[video->cur_track].wfe == NULL) + { + video->audio_fmt = RIFF_AUDIO_NONE; + video->audio_fmt_str = (char *)"None"; + MSG("Video: * Audio track not found.\n"); + } + else + { + video->audio_fmt = audio_get_audio_format(video->track[video->cur_track].wfe->w_format_tag); + video->audio_fmt_str = audio_get_audio_format_string(video->audio_fmt); + MSG("Video: * Audio format %s detected.\n", *video->audio_fmt_str); + } + } +} + +void video_update_info() +{ + if (video == NULL) + return; + static int old_secs = 0; + KHWL_TIME_TYPE displ; + displ.pts = 0; + displ.timeres = 90000; + if (video->skip_fwd || video->skip_rev || video->searching) + displ.pts = video->scr; + else if (mpeg_is_displayed()) + { + khwl_getproperty(KHWL_TIME_SET, etimVideoFrameDisplayedTime, sizeof(displ), &displ); + displ.pts += INT64(90000) * video->video_pos_base * video->scale / video->rate; + displ.pts += video->displ_pts_base; + } + + if ((LONGLONG)displ.pts != video->saved_pts) + { + if ((LONGLONG)displ.pts >= 0) + { + video->saved_pts = displ.pts; + + if (!video->skip_fwd && !video->skip_rev && !video->searching) + show_subtitles(displ.pts); + + char fip_out[10]; + int secs = (int)(displ.pts / 90000); + if (secs < 0) + secs = 0; + if (secs >= 10*3600) + secs = 10*3600-1; + if (secs != old_secs) + { + script_time_callback(secs); + // check if total time changed + video->UpdateTotalTime(); + + fip_out[0] = ' '; + fip_out[1] = ' '; + fip_out[2] = (char)((secs/3600) + '0'); + int secs3600 = secs%3600; + fip_out[3] = (char)(((secs3600/60)/10) + '0'); + fip_out[4] = (char)(((secs3600/60)%10) + '0'); + fip_out[5] = (char)(((secs3600%60)/10) + '0'); + fip_out[6] = (char)(((secs3600%60)%10) + '0'); + fip_out[7] = '\0'; + fip_write_string(fip_out); + + old_secs = secs; + } + } + } +} + +int video_read_input(BYTE * &buf, int &len, bool wait) +{ + int ret = 0; + + // don't read more blocks if we just need more output + if (video->skip_fwd || video->skip_rev || video->audio_fmt == RIFF_AUDIO_UNKNOWN + || video->audio_fmt == RIFF_AUDIO_NONE + // don't read more blocks if audio is not ready + || (video->audio_fmt != RIFF_AUDIO_UNKNOWN + && video->audio_fmt != RIFF_AUDIO_NONE && audio_ready())) + { + do + { + int skiphdr = 0; + if (video->skip_fwd || video->skip_rev) + { + if (video->event != MEDIA_EVENT_VIDEO_PARTIAL && video->event != MEDIA_EVENT_NOP) + { + video->chunkleft = 0; + video->old_video_packet_len = video->video_packet_len; + video->video_packet_len = 0; + + ret = video->GetKeyFrame(video->skip_fwd ? VIDEO_KEY_FRAME_NEXT : VIDEO_KEY_FRAME_PREV); + if (ret == 0) // wait... + { + if (wait) + { + if (cycle() < 0) + return -1; + continue; + } + return 0; + } + skiphdr = 8; + video->chunkleft = video->video_packet_len + skiphdr; + if (ret < 1) + { + ret = 1; + video->event = MEDIA_EVENT_STOP; + } + } + } + if (video->skip_fwd || video->skip_rev) + { + if (video->event != MEDIA_EVENT_STOP) + { + len = video->chunkleft; + video->event = MEDIA_EVENT_NOP; + ret = media_read_block(&buf, &video->event, &len); + if (video->event == MEDIA_EVENT_OK) + { + video->chunkleft -= len; + video->event = (video->chunkleft > 0) ? MEDIA_EVENT_VIDEO_PARTIAL : MEDIA_EVENT_VIDEO; + len -= skiphdr; + buf += skiphdr; + } + } + } + if (!video->skip_fwd && !video->skip_rev) // normal play + { + video->event = MEDIA_EVENT_OK; + len = 0; + buf = NULL; + + ret = media_get_next_block(&buf, &video->event, &len); + } + if (ret > 0 && buf == NULL) + { + msg_error("Video: Not initialized. STOP!\n"); + return -1; + } + if (ret == -1) + { + msg_error("Video: Error getting next block!\n"); + return -1; + } + else if (ret == 0) // wait... + { + if (wait) + { + if (cycle() < 0) + return -1; + continue; + } + return 0; + } + break; + } while (wait); + + if (video->event == MEDIA_EVENT_STOP) + { + MSG("Video: STOP Event triggered!\n"); + if (!video->skip_fwd && !video->skip_rev) + mpeg_play_normal(); + video->stopping = true; + return 0; + } + + if (video->video_fmt == RIFF_VIDEO_DIV3) + { + if (video->event == MEDIA_EVENT_VIDEO || video->event == MEDIA_EVENT_VIDEO_PARTIAL) + { + mpeg_setbufidx(MPEG_BUFFER_1, NULL); + +//msg("[%d] DECODE len=%d <%02x %02x %02x %02x %02x %02x %02x %02x> (buf=%08x)\n", num_packets, (video->event == MEDIA_EVENT_VIDEO ? len + 8 : len), +// buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7], buf); +//gui_update(); + + bitstream_decode_buf_init(0, buf, video->event == MEDIA_EVENT_VIDEO ? len + 8 : len, true); + } + } + } + + return 1; +} + +int video_more_output(bool wait) +{ + if (video->divx_cur_bufleft < 24/*16*/) + { + do + { + if (video->need_to_stop) + return -1; + if (mpeg_find_free_blocks(MPEG_BUFFER_3) == 0) + { + if (wait) + { + if (cycle() < 0) + return -1; + continue; + } + return 0; // wait... + } + break; + } while (wait); + if (video == NULL) + return -1; + video->divx_cur_bufpos = 0; + video->divx_cur_bufleft = mpeg_getbufsize(MPEG_BUFFER_3); + } + BYTE *tmpbuf = mpeg_getcurbuf(MPEG_BUFFER_3); + BYTE *buf = tmpbuf + video->divx_cur_bufpos; + int len = video->divx_cur_bufleft - 8; + +//msg("[%d] ENCODE len=%d (buf=%08x)\n", num_packets, len, buf); +//gui_update(); + + bitstream_encode_buf_init(buf, len); + + return 1; +} + +int video_send_video_packet(MPEG_BUFFER which, BYTE *buf, int len, bool start_of_packet) +{ + while (len > 0) + { + MpegPacket *packet = NULL; + packet = mpeg_feed_getlast(); + if (packet == NULL) // well, it won't really help + return 0; + memset((BYTE *)packet + 4, 0, sizeof(MpegPacket) - 4); + + BYTE *base = buf; + int curlen = len; + + // find next VOP start + if (video->video_fmt == RIFF_VIDEO_MPEG4) + { + for (; len >= 0 && (base == buf || *buf != 0 || buf[1] != 0 || buf[2] != 1 || buf[3] != 0xb6); len--) + { + if (video->test_for_qpel_gmc) + { + if (buf[0] == 0 && buf[1] == 0 && buf[2] == 1 && (buf[3] >= 0x20 && buf[3] <= 0x2F)) + { + video_decode_vol_header(buf + 4, len - 4); + if (video->gmc_flag) + { + msg("Video: Error! Cannot play MPEG-4 with GMC.\n"); + script_error_callback(SCRIPT_ERROR_GMC); + return -1; + } + else if (video->qpel_flag) + { + msg("Video: Error! Cannot play MPEG-4 with QPEL.\n"); + script_error_callback(SCRIPT_ERROR_QPEL); + return -1; + } + video->test_for_qpel_gmc = false; + } + } + buf++; + } + + if (len > 0) + curlen -= len; + } else + len = -1; + + VIDEO_FRAME_TYPE frm_type = VIDEO_FRAME_NONE; + packet->type = 0; + packet->pData = base; + packet->size = curlen; + if (base[0] == 0 && base[1] == 0 && base[2] == 1 && base[3] == 0xb6) + { + frm_type = (VIDEO_FRAME_TYPE)(base[4] >> 6); + // add I-frames to the index (but ignore too frequent I-frames) + if (frm_type == VIDEO_FRAME_I) + { + video->SetCurrentIndex(video->abs_video_pos, + video->video_packet_len, video->video_offs); + video->cur_key_offs = video->video_offs; + video->cur_key_pos = video->abs_video_pos; + video->last_frame_pos = video->frame_pos; + } else + { + // wrong index - skip it + if (video->skip_fwd || video->skip_rev) + { + video->cur_key_idx = video->last_good_key_idx; + break; + } + } + if (frm_type == VIDEO_FRAME_I || frm_type == VIDEO_FRAME_P) + { + if (video->skip_fwd || video->skip_rev) + { + if (video->skip_rev) + { + // \TODO: it'd be better to patch 'time_incr' header bits... + mpeg_setpts(0); + } + packet->pts = 0; + packet->flags = 0; + packet->scr = 0 | SPTM_SCR_FLAG; + } else + { + packet->pts = (LONGLONG)video->video_pos * video->scale; + packet->scr = packet->pts | SPTM_SCR_FLAG; + packet->pts += video->scale * 10; + packet->flags = 0x80; + + video->saved_video_pts = packet->pts; + +#if 0 +msg("v: %d\n", packet->pts); +#endif + +#ifdef USE_RESYNCS_CONTROL + if (mpeg_is_playing()) + { + LONGLONG stc = mpeg_getpts(); + LONGLONG good_scr = INT64(90000) * packet->pts / video->rate; + + //msg("%d %d\n", (int)stc, (int)good_scr); + if (stc > good_scr + video->good_delta_stc) + { + khwl_pause(); + mpeg_start(); + msg("Video: Resync...\n"); + video->wait_for_resync = false; + } + } +#endif + } + } + + } + +#if 0 +msg("PACKET [%d] len=%d (buf=%08x)\n", num_packets, packet->size, packet->pData); +if (packet->size > 0) +{ +BYTE *buf = packet->pData; +int len = packet->size; +DUMP_FRAME("%02x %02x %02x %02x %02x %02x %02x %02x ... %02x %02x %02x %02x %02x %02x %02x %02x\n", + buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7], + buf[len-8], buf[len-7], buf[len-6], buf[len-5], buf[len-4], buf[len-3], buf[len-2], buf[len-1]); +} +#endif + +#if 0 +{ +FILE *fp; +fp = fopen("out.m4v", "ab"); +//fwrite("00dc\0\0\0\0", 8, 1, fp); +fwrite(packet->pData, packet->size, 1, fp); +fclose(fp); +} +#endif +#if 0 +msg("[%d]\t\tsize=%d\t\tpts=%d\n", packet->type, packet->size, (int)packet->pts); +#endif + + // increase bufidx + mpeg_setbufidx(which, packet); + num_packets++; + + // frame order analysis +#ifdef NEW_SYNC + if (frm_type == VIDEO_FRAME_I) + { + mpeg_correct_pts(); + while (mpeg_feed_pop()) + ; + mpeg_feed(MPEG_FEED_VIDEO); + } + else if (frm_type == VIDEO_FRAME_P) + { + if (!mpeg_feed_isempty()) + { + mpeg_correct_pts(); + while (mpeg_feed_pop()) + ; + } + mpeg_feed_push(); + } + else + { + if (!mpeg_feed_isempty()) + mpeg_feed_push(); + else + mpeg_feed(MPEG_FEED_VIDEO); + } +#else + mpeg_feed(MPEG_FEED_VIDEO); +#endif + } + return 1; +} + +void video_decode_vol_header(BYTE *buf, int len) +{ + if (video == NULL) + return; + video->qpel_flag = false; + video->gmc_flag = false; + + video_bits_init(buf, len); + video_skip_bits(1); + video_skip_bits(8); + int vo_ver_id; + if (video_get_bits(1)) + { + vo_ver_id = video_get_bits(4); + video_skip_bits(3); + } else + vo_ver_id = 1; + + int aspect_ratio_info = video_get_bits(4); + if (aspect_ratio_info == 15) // extended + video_skip_bits(16); + + if (video_get_bits(1)) // vol control parameters + { + video_skip_bits(3); + if (video_get_bits(1)) // vbv parameters + { + video_skip_bits(16); + video_skip_bits(16); + video_skip_bits(16); + video_skip_bits(15); + video_skip_bits(16); + } + } + + int vol_shape = video_get_bits(2); + if(vol_shape == 3 && vo_ver_id != 1) // gray shape + video_skip_bits(4); + + // marker + video_skip_bits(1); + + int time_base_den = video_get_bits(16); + + // marker + video_skip_bits(1); + + if (video_get_bits(1)) // fixed_vop_rate + { + int time_increment_bits = video_log2(time_base_den - 1) + 1; + if (time_increment_bits < 1) + time_increment_bits = 1; + + video_skip_bits(time_increment_bits); + } + + if (vol_shape != 2) // not bin-only shape + { + if (vol_shape == 0) // rect shape + { + video_skip_bits(1); + /*int width = */video_get_bits(13); + video_skip_bits(1); + /*int height = */video_get_bits(13); + video_skip_bits(1); + } + + /*int progressive_frame = 1 - */video_get_bits(1); + video_skip_bits(1); + int vol_sprite_usage = video_get_bits(vo_ver_id == 1 ? 1 : 2); + if (vol_sprite_usage == 2) + video->gmc_flag = true; + if (vol_sprite_usage == 1 || vol_sprite_usage == 2) // static or GMC + { + if (vol_sprite_usage == 1) + { + video_skip_bits(14); + video_skip_bits(14); + video_skip_bits(14); + video_skip_bits(14); + } + video_skip_bits(9); + if (vol_sprite_usage == 1) + video_skip_bits(1); + } + if (video_get_bits(1) == 1) + video_skip_bits(8); + + if (video_get_bits(1)) // vol_quant_type + { + int i; + if (video_get_bits(1)) + { + for (i = 0; i < 64; i++) + { + if (video_get_bits(8) == 0) + break; + } + } + + if (video_get_bits(1)) + { + for (i = 0; i < 64; i++) + { + if (video_get_bits(8) == 0) + break; + } + } + } + + if (vo_ver_id != 1) + video->qpel_flag = video_get_bits(1) != 0; + } +} + +int divx3_callback(BITSTREAM_MODE mode, BYTE *outbuf, int outlen) +{ + if (mode != BITSTREAM_MODE_OUTPUT) + { + mpeg_release_packet(MPEG_BUFFER_1, NULL); + } + + if (outlen != 0) + { + int ll = (outlen + 7) & (~7); + video->divx_cur_bufleft -= ll; + video->divx_cur_bufpos += ll; + if (video_send_video_packet(MPEG_BUFFER_3, outbuf, outlen, false) < 0) + return -1; + } + + if (mode == BITSTREAM_MODE_INPUT) + { + BYTE *inbuf; + int inlen; + if (video_read_input(inbuf, inlen, true) < 0) + return -1; + } + + if (mode == BITSTREAM_MODE_OUTPUT) + { + if (video_more_output(true) < 0) + return -1; + } + + return 0; +} + +extern int frame_number; + +/// Advance playing +int video_loop() +{ + static BYTE *buf = NULL; + static int len = 0; + int ret; + + // for video transcoding + +#if 0 +again: +#endif + + if (video == NULL) + return 1; + + if (video->need_to_stop) + video_stop(); + + if (video == NULL) + return 1; + + if (video->stopping) + { + if (video->skip_fwd || video->skip_rev || video->searching) + { + video_stop(); + return 1; + } + if (mpeg_wait(TRUE) == 1) + { + video_stop(); + return 1; + } + } + + if (video->delayed_skip != 0 && !video->in_divx_packet) + { + if (!video->skip_fwd && !video->skip_rev) + media_seek_curleft(); + if (video->delayed_skip == 1) + video->skip_fwd = true; + else if (video->delayed_skip == 2) + video->skip_rev = true; + video->delayed_skip = 0; + } + + if (video->searching && !video->cancel_skip) + { + int ret = video->GetKeyFrame(VIDEO_KEY_FRAME_CONTINUE); + if (ret > 0) + video->cancel_skip = true; + } + + if (video->cancel_skip && (video->skip_fwd || video->skip_rev || video->searching) && !video->in_divx_packet) + { + video_lseek(video->video_offs, SEEK_SET); + video->video_pos_base = video->abs_video_pos;// = video->last_key_pos - 1; + video->video_pos = 0; + video->audio_total_bytes = -1; + video->audio_pos = 0; + video->frame_pos = video->last_frame_pos - 1; + + fip_write_special(FIP_SPECIAL_PLAY, 1); + fip_write_special(FIP_SPECIAL_REPEAT, 0); + fip_write_special(FIP_SPECIAL_PAUSE, 0); + + if (video->skip_waiting) + mpeg_release_packet(MPEG_BUFFER_1, NULL); + + mpeg_setspeed(MPEG_SPEED_NORMAL); + mpeg_set_scale_rate((DWORD *)&video->scale, (DWORD *)&video->rate); + // we'll correct display pts later... + mpeg_setpts(0); + + script_speed_callback(MPEG_SPEED_NORMAL); + + video->skip_fwd = false; + video->skip_rev = false; + video->skip_wait = false; + video->skip_waiting = false; + video->searching = false; + video->cancel_skip = false; + video->chunkleft = 0; + video->max_rev_incr = def_max_rev_incr; + + media_skip_buffer(NULL); + + if (video->audio_fmt != RIFF_AUDIO_UNKNOWN && video->audio_fmt != RIFF_AUDIO_NONE) + audio_reset(); + + info_cnt = 0; + } + + if (!video->stopping && !video->searching && !video->in_divx_packet) + { + if (!video->skip_waiting) + { + ret = video_read_input(buf, len, false); + if (ret == 0) + return 0; + if (ret < 0) + { + return 1; + } + } + + if (video->skip_wait) + { + int cur_time = script_get_time(NULL); + int delta = 300; + if (cur_time < video->last_keyframe_time + delta) + { + video->skip_waiting = true; + return 0; + } + video->last_keyframe_time = cur_time; + } + + if (video->video_fmt == RIFF_VIDEO_MPEG12) + { + if (((VideoMpg *)video)->ProcessChunk(buf, len) == 0) + return 0; + } + + else if (video->event == MEDIA_EVENT_VIDEO || video->event == MEDIA_EVENT_VIDEO_PARTIAL) + { + if (video->video_fmt == RIFF_VIDEO_DIV3) + { + video->in_divx_packet = true; + ret = video_more_output(false); + if (ret == 0) + { + video->in_divx_packet = false; + return 0; + } + if (ret < 0) + { + video->event = MEDIA_EVENT_NOP; + } +#if 0 +FILE *fp; +fp = fopen("out.m4v", "ab"); +fwrite("00dc", 4, 1, fp); +fwrite("\0\0\0\0", 4, 1, fp); +int fffpos = ftell(fp); +fclose(fp); +#endif + + // ok, now transcode + if (divx_transcode(divx3_callback /*, len */) < 0) + // skip this frame + { + if (video == NULL) + { + return 1; + } + video->event = MEDIA_EVENT_NOP; + mpeg_release_packet(MPEG_BUFFER_1, NULL); + } + +//msg("-- %d done\n", frame_number); +//gui_update(); + +#if 0 +fp = fopen("out.m4v", "rb+"); +fseek(fp, 0, SEEK_END); +int fffpos2 = ftell(fp); +fseek(fp, fffpos - 4, SEEK_SET); +int fff = fffpos2 - fffpos; +fwrite(&fff, 4, 1, fp); +fseek(fp, fffpos2, SEEK_SET); +if ((fffpos2 - fffpos) & 1) +{ + fputc(0, fp); +} +fclose(fp); +#endif + video->in_divx_packet = false; + video->skip_waiting = false; + } + else + { + if (video_send_video_packet(MPEG_BUFFER_1, buf, len, video->event == MEDIA_EVENT_VIDEO) < 0) + return 1; + video->skip_waiting = false; + } + + } + else if (video->event == MEDIA_EVENT_AUDIO || video->event == MEDIA_EVENT_AUDIO_PARTIAL) + { +#ifndef SKIP_AUDIO + if (video->audio_fmt != RIFF_AUDIO_UNKNOWN && video->audio_fmt != RIFF_AUDIO_NONE) + { + // send multiple frames (packets) in one chunk + BYTE *b = buf; + for (;;) + { + MpegPacket *packet = NULL; + packet = mpeg_feed_getlast(); + if (packet == NULL) // well, it won't really help + return 0; + + int numread = audio_parse_packet(packet, b, len); + if (numread == 0) + break; + len -= numread; + b += numread; + + // audio syncronisation... + int bps = audio_get_output_bps(); + if (bps > 0) + { + if (video->audio_total_bytes < 0) + { + video->audio_total_bytes = (video->saved_video_pts + video->audio_delta - video->scale * 10 - video->audio_pts_offset) * bps / video->rate; + } + + packet->pts = ((LONGLONG)video->audio_total_bytes * video->rate / bps) + video->audio_pts_offset; + packet->scr = packet->pts | SPTM_SCR_FLAG; + packet->pts += video->scale * 10; + + packet->flags = 0x80; + +#if 0 + msg("a: %d\t[%d]\n", (int)packet->pts, (int)(packet->pts - video->saved_video_pts)); +#endif + + if (video->audio_delta == (LONGLONG)SPTM_SCR_FLAG && video->saved_video_pts > 0) + video->audio_delta = packet->pts - video->saved_video_pts; + + } else + { + packet->pts = packet->scr = 0; + packet->flags = 0; +#if 0 + msg("a: %d\n", (int)packet->pts); +#endif + } + + if (video->audio_total_bytes >= 0) + video->audio_total_bytes += packet->size; + + mpeg_feed(MPEG_FEED_AUDIO); + num_packets++; + } + } +#endif + } + } + + if (info_cnt++ > max_info_cnt) + { + info_cnt = 0; + video_update_info(); + } + +#if 0 + if (!video->in_divx_packet) + goto again; +#endif + + return 0; +} + +/// Pause playing +BOOL video_pause() +{ + fip_write_special(FIP_SPECIAL_PLAY, 0); + fip_write_special(FIP_SPECIAL_REPEAT, 0); + fip_write_special(FIP_SPECIAL_PAUSE, 1); + + mpeg_setspeed(MPEG_SPEED_PAUSE); + + return TRUE; +} + +/// Stop playing +BOOL video_stop() +{ + if (video != NULL) + { + if (video->in_divx_packet) + { + video->need_to_stop = true; + return FALSE; + } + + delete_subtitles(); + + mpeg_deinit(); + khwl_setvideomode(KHWL_VIDEOMODE_NONE, TRUE); + if (video->playing) + { + video->playing = false; + } + video->stopping = false; + + media_close(); + } + return TRUE; +} + +void video_setdebug(BOOL ison) +{ + video_msg = ison == TRUE; +} +BOOL video_getdebug() +{ + return video_msg; +} + +/////////////////////////////////////////// + +BOOL video_open(const char *filepath) +{ + if (video != NULL) + video_close(); + int fd = cdrom_open(filepath, O_RDONLY); + if (fd < 0) + { + msg_error("Video: Cannot open file %s.\n", filepath); + return FALSE; + } + + // detect container type + SPString fname = filepath; + video = NULL; + if (fname.FindNoCase(".avi") >= 0 || fname.FindNoCase(".divx") >= 0) + { + video = new VideoAvi(); + } + else if (fname.FindNoCase(".mpg") >= 0 || fname.FindNoCase(".mpeg") >= 0 || fname.FindNoCase(".vob") >= 0 + || fname.FindNoCase(".dat") >= 0) + { + video = new VideoMpg(); + } + else if (fname.FindNoCase(".mp4") >= 0 || fname.FindNoCase(".3gp") >= 0 || fname.FindNoCase(".mov") >= 0) + { + //video = new VideoQt(); + msg_error("Video: MP4 container currently not supported.\n"); + close(fd); + return FALSE; + } + + for (int i = 0; i < 2; i++) + { + if (video == NULL) + { + // try to auto-detect video type + BYTE hdr[16]; + lseek64(fd, 0, SEEK_SET); + if (read(fd, hdr, 12) != 12) + { + msg_error("Video: Cannot read file header.\n"); + return FALSE; + } + lseek64(fd, 0, SEEK_SET); + if (strncmp((char *)hdr, "RIFF", 4) == 0 && strncmp((char *)hdr+8, "AVI ", 4) == 0) + { + video = new VideoAvi(); + } + else if ((hdr[0] == 0 && hdr[1] == 0 && hdr[2] == 1 && (hdr[3] == 0xba || hdr[3] == 0xb3)) + || (strncmp((char *)hdr, "RIFF", 4) == 0 && strncmp((char *)hdr+8, "CDXA", 4) == 0)) + { + video = new VideoMpg(); + } + else + { + msg_error("Video: Unknown media file type.\n"); + close(fd); + return FALSE; + } + i++; + } + // try to parse... + if (video != NULL) + { + video->fd = fd; + + if (video->Parse()) + { + video->look4chunk = false; + return TRUE; + } + SPSafeDelete(video); + } + } + close(fd); + return FALSE; +} + +BOOL video_close() +{ + if (video != NULL) + { + if (video->video_fmt == RIFF_VIDEO_DIV3) + { + divx_transcode_deinit(); + divx_transcode_predeinit(); + } + + if (video->audio_fmt != RIFF_AUDIO_UNKNOWN && video->audio_fmt != RIFF_AUDIO_NONE) + audio_deinit(); + + close(video->fd); + SPSafeDelete(video); + return TRUE; + } + return FALSE; +} + +VIDEO_CHUNK_TYPE video_getnext(BYTE *buf, int buflen, int *pos, int *left, int *len) +{ + if (video != NULL) + return video->GetNext(buf, buflen, pos, left, len); + return VIDEO_CHUNK_UNKNOWN; +} + +int video_read(BYTE *buf, int len) +{ + if (video == NULL || buf == NULL || len < 1) + return 0; + int n = 0, newlen = 0; + video->cur_offs = lseek64(video->fd, 0, SEEK_CUR); + while (newlen < len) + { + n = read (video->fd, buf + newlen, len - newlen); + if (n == 0) + { + video->is_eof = true; + break; + } + if (n < 0) + { + if (errno == EINTR) + continue; + else + break; + } + newlen += n; + } + + return newlen; +} + +bool video_eof() +{ + return video == NULL || video->is_eof; +} + +void video_shiftpos(int offs) +{ + video->cur_offs -= offs; +} + +LONGLONG video_lseek(LONGLONG off, int where) +{ + LONGLONG fp = lseek64(video->fd, off, where); + media_set_filepos(fp); + return fp; +} + +int video_set_audio_track(int track) +{ + if (track == -1) + { + track = video->cur_track + 1; + if (track >= video->num_tracks) + track = video->first_track; + } + if (track < video->first_track || track >= video->num_tracks) + { + script_audio_stream_callback(-1); + return -1; + } + + if (video->video_fmt == RIFF_VIDEO_MPEG12) + { + msg("MPEG: Set audio stream #%d.\n", track); + mpeg_setaudiostream(track); + video->cur_track = track; + script_audio_stream_callback(video->cur_track + 1); + return 0; + } + + if (video->audio_fmt == RIFF_AUDIO_UNKNOWN || video->audio_fmt == RIFF_AUDIO_NONE) + return -1; + + if (video->track[track].wfe == NULL) + { + msg("Video: Cannot change audio tracks of unknown format.\n"); + script_error_callback(SCRIPT_ERROR_INVALID); + return -1; + } + + RIFF_AUDIO_FORMAT newfmt = audio_get_audio_format(video->track[track].wfe->w_format_tag); + if (newfmt == RIFF_AUDIO_UNKNOWN || newfmt == RIFF_AUDIO_NONE) + { + msg_error("Video: Audio format not supported.\n"); + script_error_callback(SCRIPT_ERROR_BAD_AUDIO); + return -1; + } + if (newfmt != video->audio_fmt) + { + audio_deinit(); + khwl_stop(); + video->audio_fmt = newfmt; + video->audio_halfrate = (video->video_fmt == RIFF_VIDEO_DIV3); + if (!audio_init(video->audio_fmt, video->audio_halfrate)) + { + msg_error("Video: Cannot init audio decompressor.\n"); + script_error_callback(SCRIPT_ERROR_BAD_AUDIO); + } + + video_play_from_last_keyframe(); + } + + MSG("Video: Setting audio params.\n"); + if (audio_setaudioparams(video->audio_fmt, video->track[track].wfe, + video->audio_halfrate ? 2 : 1) < 0) + { + msg_error("Video: Audio format %d not supported!\n", video->audio_fmt); + video->cur_track = -1; + return -1; + } + + audio_reset(); + + if (track != video->cur_track) + { + video->cur_track = track; + video->audio_total_bytes = -1; + } + video->cur_audio_bits = video->track[video->cur_track].wfe->w_bits_per_sample; + + script_audio_stream_callback(video->cur_track + 1); + + return 0; +} + +///////////////////////////////////////////////////////////// + +BOOL video_seek(int seconds) +{ + if (video == NULL) + return FALSE; + + if (seconds > 10 * 60) + { + script_error_callback(SCRIPT_ERROR_WAIT); + script_player_subtitle_callback(""); + gui_update(); + } + + int ret = video->GetKeyFrame(seconds); + if (ret < 0) + { + script_error_callback(SCRIPT_ERROR_INVALID); + return FALSE; + } + + if (video->video_fmt != RIFF_VIDEO_MPEG12) + { + mpeg_stop(); + if (ret > 0) + { + video->cancel_skip = true; + } + + video->event = MEDIA_EVENT_OK; + video->searching = true; + } + + info_cnt = 0; + + return TRUE; +} + +int video_forward(BOOL fast) +{ + if (video->video_fmt == RIFF_VIDEO_MPEG12) + { + if (video->GetKeyFrame(VIDEO_KEY_FRAME_NEXT) < 0) + return -1; + info_cnt = 0; + return 0; + } + + fip_write_special(FIP_SPECIAL_PLAY, 0); + fip_write_special(FIP_SPECIAL_REPEAT, 1); + mpeg_setspeed(MPEG_SPEED_FAST_FWD_MASK); + mpeg_setpts(0); + script_player_subtitle_callback(""); + + // skip the rest of current chunk + if (!video->in_divx_packet) + { + if (!video->skip_fwd && !video->skip_rev) + media_seek_curleft(); + } + if (video->skip_waiting) + mpeg_release_packet(MPEG_BUFFER_1, NULL); + + video->event = MEDIA_EVENT_OK; + video->searching = false; + video->skip_rev = false; + video->skip_fwd = true; + video->skip_waiting = false; + video->skip_wait = !fast; + video->max_rev_incr = def_max_rev_incr; + video->skip_cnt = 0; + video->last_keyframe_time = script_get_time(NULL); + info_cnt = 0; + + if (video->in_divx_packet) + { + video->delayed_skip = 1; + video->skip_fwd = false; + } + + return 1; +} +int video_rewind(BOOL fast) +{ + if (video->video_fmt == RIFF_VIDEO_MPEG12) + { + if (video->GetKeyFrame(VIDEO_KEY_FRAME_PREV) < 0) + return -1; + info_cnt = 0; + return 0; + } + + fip_write_special(FIP_SPECIAL_PLAY, 0); + fip_write_special(FIP_SPECIAL_REPEAT, 1); + mpeg_setspeed(MPEG_SPEED_FAST_REV_MASK); + mpeg_setpts(0); + script_player_subtitle_callback(""); + + // skip the rest of current chunk + if (!video->in_divx_packet) + { + if (!video->skip_fwd && !video->skip_rev) + media_seek_curleft(); + } + if (video->skip_waiting) + mpeg_release_packet(MPEG_BUFFER_1, NULL); + + video->event = MEDIA_EVENT_OK; + video->searching = false; + video->skip_fwd = false; + video->skip_rev = true; + video->skip_waiting = false; + video->skip_wait = !fast; + video->skip_cnt = 0; + video->last_keyframe_time = script_get_time(NULL); + info_cnt = 0; + + if (video->in_divx_packet) + { + video->delayed_skip = 2; + video->skip_rev = false; + } + + return 1; +} + +void video_set_audio_offset(LONGLONG offset) +{ + if (video != NULL) + { + if (video->video_fmt != RIFF_VIDEO_MPEG12) + video->audio_pts_offset = offset * (LONGLONG)video->rate / INT64(90000); + } +} + +LONGLONG video_get_audio_offset() +{ + if (video != NULL) + { + if (video->video_fmt != RIFF_VIDEO_MPEG12) + return INT64(90000) * video->audio_pts_offset / video->rate; + } + return 0; +} + +int video_play_from_last_keyframe() +{ + // skip the rest of current chunk + if (!video->in_divx_packet) + { + if (!video->skip_fwd && !video->skip_rev) + media_seek_curleft(); + } + + video_lseek(video->cur_key_offs, SEEK_SET); + video->abs_video_pos = video->cur_key_pos; + video->video_pos_base = video->abs_video_pos;// = video->last_key_pos - 1; + video->video_pos = 0; + video->audio_total_bytes = video->audio_delta; + video->audio_pts_offset = 0; + video->audio_pos = 0; + video->frame_pos = video->last_frame_pos - 1; + + video->chunkleft = 0; + video->max_rev_incr = def_max_rev_incr; + + media_skip_buffer(NULL); + + info_cnt = 0; + + mpeg_setspeed(MPEG_SPEED_NORMAL); + mpeg_set_scale_rate((DWORD *)&video->scale, (DWORD *)&video->rate); + // we'll correct display pts later... + mpeg_setpts(0); + + return 0; +} + +//////////////////////////////////////////////////////////////// + +static DWORD v_dec_v = 0; +static int v_bitidx = 0, v_left = 0; + +static void video_bits_init(BYTE *buf, int len) +{ + bitstream_decode_buf_init(1, buf, len, true); + bitstream_decode_start(1, v_dec_v, v_bitidx, v_left); +} + +static DWORD video_get_bits(int num_bits) +{ + DWORD ret; + bitstream_get_bits(ret, 1, v_dec_v, v_bitidx, v_left, num_bits); + return ret; +} + +static int video_skip_bits(int num_bits) +{ + bitstream_skip_bits(1, v_dec_v, v_bitidx, v_left, num_bits); + return 0; +} + +static const BYTE video_log2_tab[256] = +{ + 0,0,1,1,2,2,2,2,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4, + 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5, + 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, + 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, +}; + +int video_log2(DWORD v) +{ + int n = 0; + if (v & 0xffff0000) + { + v >>= 16; + n += 16; + } + if (v & 0xff00) + { + v >>= 8; + n += 8; + } + + n += video_log2_tab[v]; + + return n; +} + + +#endif diff --git a/src/video.h b/src/video.h new file mode 100644 index 0000000..3455f17 --- /dev/null +++ b/src/video.h @@ -0,0 +1,260 @@ +////////////////////////////////////////////////////////////////////////// +/** + * SigmaPlayer source project - Video player header file + * \file video.h + * \author bombur + * \version 0.1 + * \date 07.03.2007 + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111, USA. + */ +////////////////////////////////////////////////////////////////////////// + +#ifndef SP_VIDEO_H +#define SP_VIDEO_H + +typedef enum +{ + VIDEO_CONTAINER_UNKNOWN = 0, + + VIDEO_CONTAINER_AVI = 1, + VIDEO_CONTAINER_MPEG = 2, + VIDEO_CONTAINER_QT = 3, + +} VIDEO_CONTAINER_TYPE; + +typedef enum +{ + VIDEO_CHUNK_UNKNOWN = 0, + VIDEO_CHUNK_VIDEO = 1, + VIDEO_CHUNK_AUDIO = 2, + VIDEO_CHUNK_VIDEO_PARTIAL = 3, + VIDEO_CHUNK_AUDIO_PARTIAL = 4, + VIDEO_CHUNK_SUBT = 5, + + VIDEO_CHUNK_FRAGMENT = 6, // if chunk header is split + VIDEO_CHUNK_HEADER = 7, // MPEG header w/o audio/video data + + VIDEO_CHUNK_RECOVERY = 8, + + VIDEO_CHUNK_EOF = 100, +} VIDEO_CHUNK_TYPE; + +typedef enum +{ + RIFF_VIDEO_UNKNOWN = -1, + RIFF_VIDEO_MPEG12 = 0, + RIFF_VIDEO_MPEG4 = 1, + RIFF_VIDEO_DIV3 = 2, + +} RIFF_VIDEO_FORMAT; + +typedef enum +{ + VIDEO_FRAME_NONE = -1, + VIDEO_FRAME_I = 0, + VIDEO_FRAME_P, + VIDEO_FRAME_B, +} VIDEO_FRAME_TYPE; + +#ifdef VIDEO_INTERNAL + +#include "divx.h" + +extern bool video_msg; + +typedef struct VideoKeyFrame +{ + int i; + int len; + LONGLONG offs; +} VideoKeyFrame; + +static const int num_key_frames_block = 32768 / sizeof(VideoKeyFrame); +static const int min_delta_keyframes = 10; +static const int def_max_rev_incr = 10; +static const int saved_delta_pts_num = 25; + +#define VIDEO_MAX_AUDIO_TRACKS 8 + +#define VIDEO_KEY_FRAME_NEXT INT64(-0x7ffffffffffffff0) +#define VIDEO_KEY_FRAME_PREV INT64(-0x7ffffffffffffff1) +#define VIDEO_KEY_FRAME_CONTINUE INT64(-0x7ffffffffffffff2) + +/// Generic base video player class +class Video +{ +public: + /// ctor + Video(); + + /// dtor + virtual ~Video(); + + virtual BOOL Parse() = 0; + virtual VIDEO_CHUNK_TYPE GetNext(BYTE *buf, int buflen, int *pos, int *left, int *len) = 0; + + /// Returns 0 if found, -1 if failed, 1 for EOF. + virtual int GetNextIndexes() = 0; + /// Find next key-frame in raw mode + virtual int GetNextKeyFrame() = 0; + + virtual int GetKeyFrame(LONGLONG time); + virtual void UpdateTotalTime(); + + int SetCurrentIndex(int i, int len, LONGLONG offs); + int AddIndex(int i, int len, LONGLONG offs); + + /// Fix audio packet PTS if audio is far ahead video + LONGLONG FixAudioPts(LONGLONG pts); + +public: + VIDEO_CONTAINER_TYPE type; + + bool playing, stopping, is_eof; + MEDIA_EVENT event; + int scale, rate; + LONGLONG scr; + LONGLONG good_delta_stc; + LONGLONG saved_pts, displ_pts_base; + bool wait_for_resync; + + RIFF_VIDEO_FORMAT video_fmt; + RIFF_AUDIO_FORMAT audio_fmt; + SPString video_fmt_str, audio_fmt_str; + + VIDEO_CHUNK_TYPE chunktype; + int chunkleft; + int video_packet_len, old_video_packet_len; + bool look4chunk; + bool test_for_qpel_gmc; + BYTE chunkheader[8]; + + BYTE *mux_buf[16], *allocated_mux_buf[16]; + int mux_numbufs, mux_bufsize; + int allocated_numbufs; + int allocated_mux_buf_size[16]; + bool no_partial; + + BYTE *divx_buf[16], *divx_base[16]; + int divx_cur_bufpos, divx_cur_bufleft; + bool in_divx_packet; + bool need_to_stop; + // for divx3 optimizations + bool audio_halfrate; + + //// file container reader - common vars: + int fd; + + int width, height; + float fps; + int video_frames; + bool qpel_flag, gmc_flag; + char fourcc[5]; + + RiffAudioTrack track[VIDEO_MAX_AUDIO_TRACKS]; + int cur_track, first_track, num_tracks; + int cur_audio_bits; + + int total_frames; + LONGLONG audio_delta, audio_total_bytes, audio_pts_offset; // needed for PTS calcs. + int num_idx; + + SPClassicList > indexes; + + int abs_video_pos, video_pos_base, video_pos, last_key_pos, cur_key_pos; + int audio_pos; + int cur_key_idx, last_good_key_idx, last_key_idx; + int frame_pos, last_frame_pos; + int max_rev_incr; + + bool skip_fwd, skip_rev, searching, skip_wait, skip_waiting; + int delayed_skip; // used to set skip_fwd/rev if not ready immediately + int last_keyframe_time; + int skip_cnt; + bool cancel_skip; + + LONGLONG cur_offs, video_offs, cur_key_offs; + + // for raw-mode seeking + LONGLONG last_chunk_offs; + int last_chunk_len; + + LONGLONG saved_video_pts; + +private: + /// Get given key-frame index + VideoKeyFrame *GetIndex(int idx); + + void ResetAvgPts(); + + LONGLONG saved_delta_pts[saved_delta_pts_num]; + LONGLONG avg_delta_pts, min_delta_pts, max_delta_pts, minus_delta_pts; + int saved_delta_pts_idx; +}; + +LONGLONG video_lseek(LONGLONG off, int where); +int video_read(BYTE *buf, int len); + + +#endif + +/// Play Video file +int video_play(char *filepath = NULL); + +/// Advance playing +int video_loop(); + +/// Pause playing +BOOL video_pause(); + +/// Stop playing +BOOL video_stop(); + +/// Seek to given time and play +BOOL video_seek(int seconds); + +int video_forward(BOOL fast); +int video_rewind(BOOL fast); + +void video_set_audio_offset(LONGLONG offset); +LONGLONG video_get_audio_offset(); + +void video_setdebug(BOOL ison); +BOOL video_getdebug(); + +/// Set audio track (if -1 then cycle through all) +int video_set_audio_track(int track); + +bool video_search_fourcc(DWORD fourcc, const DWORD *values); + +//////////////////////////////// +BOOL video_open(const char *filepath); +BOOL video_close(); + +LONGLONG video_lseek(LONGLONG off, int where); +int video_read(BYTE *buf, int len); +bool video_eof(); + +VIDEO_CHUNK_TYPE video_getnext(BYTE *buf, int buflen, int *pos, int *left, int *len); +void video_shiftpos(int offs); +void video_detect_formats(); +void video_update_info(); + +int video_play_from_last_keyframe(); + +void video_decode_vol_header(BYTE *buf, int len); + +#endif // of SP_VIDEO_H diff --git a/win32/dvd.dsp b/win32/dvd.dsp new file mode 100644 index 0000000..aee8929 --- /dev/null +++ b/win32/dvd.dsp @@ -0,0 +1,123 @@ +# Microsoft Developer Studio Project File - Name="dvd" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Static Library" 0x0104 + +CFG=dvd - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "dvd.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "dvd.mak" CFG="dvd - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "dvd - Win32 Release" (based on "Win32 (x86) Static Library") +!MESSAGE "dvd - Win32 Debug" (based on "Win32 (x86) Static Library") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +RSC=rc.exe + +!IF "$(CFG)" == "dvd - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release/dvd" +# PROP BASE Intermediate_Dir "Release/dvd" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release/dvd" +# PROP Intermediate_Dir "Release/dvd" +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_MBCS" /D "_LIB" /YX /FD /c +# ADD CPP /nologo /MD /W3 /GX /O2 /I "../src/libsp/win32/include" /I "../src" /I "../src/contrib/libdvdnav/src" /I "../src/contrib/libdvdnav/src/dvdread" /I "../src/contrib/libdvdnav/msvc" /D "NDEBUG" /D "WIN32" /D "_MBCS" /D "_LIB" /D "DVDCSS_USE_EXTERNAL_CSS" /YX /FD /c +# ADD BASE RSC /l 0x419 /d "NDEBUG" +# ADD RSC /l 0x419 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LIB32=link.exe -lib +# ADD BASE LIB32 /nologo +# ADD LIB32 /nologo + +!ELSEIF "$(CFG)" == "dvd - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug/dvd" +# PROP BASE Intermediate_Dir "Debug/dvd" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug/dvd" +# PROP Intermediate_Dir "Debug/dvd" +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_MBCS" /D "_LIB" /YX /FD /GZ /c +# ADD CPP /nologo /MDd /W3 /Gm /GX /ZI /Od /I "../src/libsp/win32/include" /I "../src" /I "../src/contrib/libdvdnav/src" /I "../src/contrib/libdvdnav/src/dvdread" /I "../src/contrib/libdvdnav/msvc" /D "_DEBUG" /D "WIN32" /D "_MBCS" /D "_LIB" /D "DVDCSS_USE_EXTERNAL_CSS" /YX /FD /GZ /c +# ADD BASE RSC /l 0x419 /d "_DEBUG" +# ADD RSC /l 0x419 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LIB32=link.exe -lib +# ADD BASE LIB32 /nologo +# ADD LIB32 /nologo + +!ENDIF + +# Begin Target + +# Name "dvd - Win32 Release" +# Name "dvd - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Source File + +SOURCE=..\src\dvd\dvd.cpp +# ADD CPP /I "../src/contrib/libdvdnav/src/vm" /D "DVDNAV_COMPILE" +# End Source File +# Begin Source File + +SOURCE=..\src\dvd\dvd_player.cpp +# End Source File +# Begin Source File + +SOURCE=..\src\dvd\dvd_css.c +# ADD CPP /I "../src/contrib/libdvdcss/src" /I "../src/contrib/libdvdcss/win32" +# End Source File +# Begin Source File + +SOURCE=..\src\dvd\dvd_misc.c +# ADD CPP /I "../src/contrib/libdvdnav/src/vm" /D "HAVE_CONFIG_H" /D "DVDNAV_COMPILE" +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# Begin Source File + +SOURCE=..\src\dvd.h +# End Source File +# Begin Source File + +SOURCE=..\src\dvd\dvd-internal.h +# End Source File +# Begin Source File + +SOURCE=..\src\dvd_misc.h +# End Source File +# End Group +# End Target +# End Project diff --git a/win32/dvd.vcproj b/win32/dvd.vcproj new file mode 100644 index 0000000..28b70aa --- /dev/null +++ b/win32/dvd.vcproj @@ -0,0 +1,284 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/win32/dvd_mod.dsp b/win32/dvd_mod.dsp new file mode 100644 index 0000000..dfa326e --- /dev/null +++ b/win32/dvd_mod.dsp @@ -0,0 +1,186 @@ +# Microsoft Developer Studio Project File - Name="dvd_mod" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102 + +CFG=dvd_mod - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "dvd_mod.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "dvd_mod.mak" CFG="dvd_mod - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "dvd_mod - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "dvd_mod - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +MTL=midl.exe +RSC=rc.exe + +!IF "$(CFG)" == "dvd_mod - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release/dvd_mod" +# PROP BASE Intermediate_Dir "Release/dvd_mod" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release/dvd_mod" +# PROP Intermediate_Dir "Release/dvd_mod" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "DVD_MOD_EXPORTS" /YX /FD /c +# ADD CPP /nologo /MD /W3 /GX /O2 /I "../src/libsp/win32/include" /I "../src" /I "../src/contrib/libdvdnav/src" /I "../src/contrib/libdvdnav/src/dvdread" /I "../src/contrib/libdvdnav/msvc" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "DVD_MOD_EXPORTS" /D "DVDCSS_USE_EXTERNAL_CSS" /D "SP_MEMDEBUG_OFF" /D "COMPILE_MODULE" /YX /FD /c +# ADD BASE MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x419 /d "NDEBUG" +# ADD RSC /l 0x419 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /machine:I386 +# ADD LINK32 /nologo /dll /machine:I386 /out:"../modules/dvd.bin" +# SUBTRACT LINK32 /nodefaultlib + +!ELSEIF "$(CFG)" == "dvd_mod - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug/dvd_mod" +# PROP BASE Intermediate_Dir "Debug/dvd_mod" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug/dvd_mod" +# PROP Intermediate_Dir "Debug/dvd_mod" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /MTd /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "DVD_MOD_EXPORTS" /YX /FD /GZ /c +# ADD CPP /nologo /MDd /W3 /Gm /GX /Zi /Od /I "../src/libsp/win32/include" /I "../src" /I "../src/contrib/libdvdnav/src" /I "../src/contrib/libdvdnav/src/dvdread" /I "../src/contrib/libdvdnav/msvc" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /D "_MBCS" /D "_USRDLL" /D "DVD_MOD_EXPORTS" /D "DVDCSS_USE_EXTERNAL_CSS" /D "SP_MEMDEBUG_OFF" /D "COMPILE_MODULE" /YX /FD /GZ /c +# ADD BASE MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32 +# ADD BASE RSC /l 0x419 /d "_DEBUG" +# ADD RSC /l 0x419 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /dll /debug /machine:I386 /pdbtype:sept +# ADD LINK32 /nologo /dll /incremental:no /debug /machine:I386 /out:"../modules/dvd.bin" /pdbtype:sept +# SUBTRACT LINK32 /nodefaultlib + +!ENDIF + +# Begin Target + +# Name "dvd_mod - Win32 Release" +# Name "dvd_mod - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Source File + +SOURCE=..\src\dvd\dvd.cpp +# ADD CPP /I "../src/contrib/libdvdnav/src/vm" /D "DVDNAV_COMPILE" +# End Source File +# Begin Source File + +SOURCE=..\src\dvd\dvd_css.c +# ADD CPP /I "../src/contrib/libdvdcss/src" /I "../src/contrib/libdvdcss/win32" +# End Source File +# Begin Source File + +SOURCE=..\src\dvd\dvd_misc.c +# ADD CPP /I "../src/contrib/libdvdnav/src/vm" /D "HAVE_CONFIG_H" /D "DVDNAV_COMPILE" +# End Source File +# Begin Source File + +SOURCE=..\src\dvd\dvd_player.cpp +# End Source File +# Begin Source File + +SOURCE=..\src\dvd\main.cpp +# End Source File +# Begin Source File + +SOURCE="..\src\module-dvd.cpp" + +!IF "$(CFG)" == "dvd_mod - Win32 Release" + +!ELSEIF "$(CFG)" == "dvd_mod - Win32 Debug" + +# ADD CPP /Gm- +# SUBTRACT CPP /YX + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE="..\src\module-init.cpp" + +!IF "$(CFG)" == "dvd_mod - Win32 Release" + +!ELSEIF "$(CFG)" == "dvd_mod - Win32 Debug" + +# ADD CPP /Gm- +# SUBTRACT CPP /YX + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\src\module.cpp + +!IF "$(CFG)" == "dvd_mod - Win32 Release" + +!ELSEIF "$(CFG)" == "dvd_mod - Win32 Debug" + +# ADD CPP /Gm- +# SUBTRACT CPP /YX + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE=..\src\libsp\win32\sp_module.cpp +# End Source File +# Begin Source File + +SOURCE=..\src\libsp\containers\string.cpp +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# Begin Source File + +SOURCE="..\src\dvd\dvd-internal.h" +# End Source File +# Begin Source File + +SOURCE=..\src\dvd.h +# End Source File +# Begin Source File + +SOURCE=..\src\dvd_misc.h +# End Source File +# End Group +# End Target +# End Project diff --git a/win32/dvd_mod.vcproj b/win32/dvd_mod.vcproj new file mode 100644 index 0000000..aae97bc --- /dev/null +++ b/win32/dvd_mod.vcproj @@ -0,0 +1,458 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/win32/dvdcss.dsp b/win32/dvdcss.dsp new file mode 100644 index 0000000..03886f0 --- /dev/null +++ b/win32/dvdcss.dsp @@ -0,0 +1,151 @@ +# Microsoft Developer Studio Project File - Name="dvdcss" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Static Library" 0x0104 + +CFG=dvdcss - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "dvdcss.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "dvdcss.mak" CFG="dvdcss - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "dvdcss - Win32 Release" (based on "Win32 (x86) Static Library") +!MESSAGE "dvdcss - Win32 Debug" (based on "Win32 (x86) Static Library") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +RSC=rc.exe + +!IF "$(CFG)" == "dvdcss - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_MBCS" /D "_LIB" /YX /FD /c +# ADD CPP /nologo /MD /W3 /GX /O2 /I "../src/contrib/libdvdcss/win32" /I "../src/libsp/win32/include" /D "WIN32" /D "NDEBUG" /D "_MBCS" /D "_LIB" /YX /Fo"Debug/libdvdcss/" /Fd"Debug/libdvdcss/" /FD /c +# ADD BASE RSC /l 0x419 /d "NDEBUG" +# ADD RSC /l 0x419 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LIB32=link.exe -lib +# ADD BASE LIB32 /nologo +# ADD LIB32 /nologo + +!ELSEIF "$(CFG)" == "dvdcss - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_MBCS" /D "_LIB" /YX /FD /GZ /c +# ADD CPP /nologo /MDd /W3 /Gm /GX /ZI /Od /I "../src/contrib/libdvdcss/win32" /I "../src/libsp/win32/include" /D "WIN32" /D "_DEBUG" /D "_MBCS" /D "_LIB" /YX /Fo"Debug/libdvdcss/" /Fd"Debug/libdvdcss/" /FD /GZ /c +# ADD BASE RSC /l 0x419 /d "_DEBUG" +# ADD RSC /l 0x419 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LIB32=link.exe -lib +# ADD BASE LIB32 /nologo +# ADD LIB32 /nologo + +!ENDIF + +# Begin Target + +# Name "dvdcss - Win32 Release" +# Name "dvdcss - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Source File + +SOURCE="..\src\contrib\libdvdcss\src\css.c" +# End Source File +# Begin Source File + +SOURCE="..\src\contrib\libdvdcss\src\device.c" +# End Source File +# Begin Source File + +SOURCE="..\src\contrib\libdvdcss\src\error.c" + +!IF "$(CFG)" == "dvdcss - Win32 Release" + +# PROP Intermediate_Dir "Release/error" + +!ELSEIF "$(CFG)" == "dvdcss - Win32 Debug" + +# PROP Intermediate_Dir "Debug/error" + +!ENDIF + +# End Source File +# Begin Source File + +SOURCE="..\src\contrib\libdvdcss\src\ioctl.c" +# End Source File +# Begin Source File + +SOURCE="..\src\contrib\libdvdcss\src\libdvdcss.c" +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# Begin Source File + +SOURCE="..\src\contrib\libdvdcss\src\bsdi_dvd.h" +# End Source File +# Begin Source File + +SOURCE="..\src\contrib\libdvdcss\src\common.h" +# End Source File +# Begin Source File + +SOURCE="..\src\contrib\libdvdcss\src\css.h" +# End Source File +# Begin Source File + +SOURCE="..\src\contrib\libdvdcss\src\csstables.h" +# End Source File +# Begin Source File + +SOURCE="..\src\contrib\libdvdcss\src\device.h" +# End Source File +# Begin Source File + +SOURCE="..\src\contrib\libdvdcss\src\ioctl.h" +# End Source File +# Begin Source File + +SOURCE="..\src\contrib\libdvdcss\src\libdvdcss.h" +# End Source File +# End Group +# End Target +# End Project diff --git a/win32/dvdcss.vcproj b/win32/dvdcss.vcproj new file mode 100644 index 0000000..572bf91 --- /dev/null +++ b/win32/dvdcss.vcproj @@ -0,0 +1,324 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/win32/dvdnav.dsp b/win32/dvdnav.dsp new file mode 100644 index 0000000..45d6548 --- /dev/null +++ b/win32/dvdnav.dsp @@ -0,0 +1,248 @@ +# Microsoft Developer Studio Project File - Name="dvdnav" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Static Library" 0x0104 + +CFG=dvdnav - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "dvdnav.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "dvdnav.mak" CFG="dvdnav - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "dvdnav - Win32 Release" (based on "Win32 (x86) Static Library") +!MESSAGE "dvdnav - Win32 Debug" (based on "Win32 (x86) Static Library") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +RSC=rc.exe + +!IF "$(CFG)" == "dvdnav - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_MBCS" /D "_LIB" /YX /FD /c +# ADD CPP /nologo /MD /W3 /GX /O2 /I "../src/contrib/libdvdnav/src" /I "../src/contrib/libdvdnav/src/dvdread" /I "../src/contrib/libdvdnav/src/vm" /I "../src/libsp/win32/include" /I "../src/contrib/libdvdcss/src" /I "../src/contrib/libdvdnav/msvc" /D "NDEBUG" /D "WIN32" /D "_MBCS" /D "_LIB" /D "HAVE_CONFIG_H" /D "DVDNAV_COMPILE" /YX /Fo"Release/libdvdnav/" /Fd"Release/libdvdnav/" /FD /c +# ADD BASE RSC /l 0x419 /d "NDEBUG" +# ADD RSC /l 0x419 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LIB32=link.exe -lib +# ADD BASE LIB32 /nologo +# ADD LIB32 /nologo + +!ELSEIF "$(CFG)" == "dvdnav - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_MBCS" /D "_LIB" /YX /FD /GZ /c +# ADD CPP /nologo /MDd /W3 /Gm /GX /ZI /Od /I "../src/contrib/libdvdnav/src" /I "../src/contrib/libdvdnav/src/dvdread" /I "../src/contrib/libdvdnav/src/vm" /I "../src/libsp/win32/include" /I "../src/contrib/libdvdcss/src" /I "../src/contrib/libdvdnav/msvc" /D "_DEBUG" /D "WIN32" /D "_MBCS" /D "_LIB" /D "HAVE_CONFIG_H" /D "DVDNAV_COMPILE" /YX /Fo"Debug/libdvdnav/" /Fd"Debug/libdvdnav/" /FD /GZ /c +# ADD BASE RSC /l 0x419 /d "_DEBUG" +# ADD RSC /l 0x419 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LIB32=link.exe -lib +# ADD BASE LIB32 /nologo +# ADD LIB32 /nologo + +!ENDIF + +# Begin Target + +# Name "dvdnav - Win32 Release" +# Name "dvdnav - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Source File + +SOURCE=..\src\contrib\libdvdnav\src\vm\decoder.c +# End Source File +# Begin Source File + +SOURCE="..\src\contrib\libdvdnav\src\dvdread\dvd_input.c" +# End Source File +# Begin Source File + +SOURCE="..\src\contrib\libdvdnav\src\dvdread\dvd_reader.c" +# End Source File +# Begin Source File + +SOURCE="..\src\contrib\libdvdnav\src\dvdread\dvd_udf.c" +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libdvdnav\src\dvdnav.c +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libdvdnav\src\highlight.c +# End Source File +# Begin Source File + +SOURCE="..\src\contrib\libdvdnav\src\dvdread\ifo_print.c" +# End Source File +# Begin Source File + +SOURCE="..\src\contrib\libdvdnav\src\dvdread\ifo_read.c" +# End Source File +# Begin Source File + +SOURCE="..\src\contrib\libdvdnav\src\dvdread\md5.c" +# End Source File +# Begin Source File + +SOURCE="..\src\contrib\libdvdnav\src\dvdread\nav_print.c" +# End Source File +# Begin Source File + +SOURCE="..\src\contrib\libdvdnav\src\dvdread\nav_read.c" +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libdvdnav\src\navigation.c +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libdvdnav\src\read_cache.c +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libdvdnav\src\remap.c +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libdvdnav\src\searching.c +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libdvdnav\src\settings.c +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libdvdnav\src\vm\vm.c +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libdvdnav\src\vm\vmcmd.c +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# Begin Source File + +SOURCE="..\src\contrib\libdvdnav\src\dvdread\bswap.h" +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libdvdnav\src\vm\decoder.h +# End Source File +# Begin Source File + +SOURCE="..\src\contrib\libdvdnav\src\dvdread\dvd_input.h" +# End Source File +# Begin Source File + +SOURCE="..\src\contrib\libdvdnav\src\dvdread\dvd_reader.h" +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libdvdnav\src\dvd_types.h +# End Source File +# Begin Source File + +SOURCE="..\src\contrib\libdvdnav\src\dvdread\dvd_udf.h" +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libdvdnav\src\dvdnav.h +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libdvdnav\src\dvdnav_events.h +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libdvdnav\src\dvdnav_internal.h +# End Source File +# Begin Source File + +SOURCE="..\src\contrib\libdvdnav\src\dvdread\dvdread_internal.h" +# End Source File +# Begin Source File + +SOURCE="..\src\contrib\libdvdnav\src\dvdread\ifo_print.h" +# End Source File +# Begin Source File + +SOURCE="..\src\contrib\libdvdnav\src\dvdread\ifo_read.h" +# End Source File +# Begin Source File + +SOURCE="..\src\contrib\libdvdnav\src\dvdread\ifo_types.h" +# End Source File +# Begin Source File + +SOURCE="..\src\contrib\libdvdnav\src\dvdread\md5.h" +# End Source File +# Begin Source File + +SOURCE="..\src\contrib\libdvdnav\src\dvdread\nav_print.h" +# End Source File +# Begin Source File + +SOURCE="..\src\contrib\libdvdnav\src\dvdread\nav_read.h" +# End Source File +# Begin Source File + +SOURCE="..\src\contrib\libdvdnav\src\dvdread\nav_types.h" +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libdvdnav\src\read_cache.h +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libdvdnav\src\remap.h +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libdvdnav\src\vm\vm.h +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libdvdnav\src\vm\vmcmd.h +# End Source File +# End Group +# End Target +# End Project diff --git a/win32/dvdnav.vcproj b/win32/dvdnav.vcproj new file mode 100644 index 0000000..269a4f1 --- /dev/null +++ b/win32/dvdnav.vcproj @@ -0,0 +1,664 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/win32/gui.dsp b/win32/gui.dsp new file mode 100644 index 0000000..570ca73 --- /dev/null +++ b/win32/gui.dsp @@ -0,0 +1,173 @@ +# Microsoft Developer Studio Project File - Name="gui" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Static Library" 0x0104 + +CFG=gui - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "gui.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "gui.mak" CFG="gui - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "gui - Win32 Release" (based on "Win32 (x86) Static Library") +!MESSAGE "gui - Win32 Debug" (based on "Win32 (x86) Static Library") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +RSC=rc.exe + +!IF "$(CFG)" == "gui - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release/gui" +# PROP BASE Intermediate_Dir "Release/gui" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release/gui" +# PROP Intermediate_Dir "Release/gui" +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_MBCS" /D "_LIB" /YX /FD /c +# ADD CPP /nologo /MD /W4 /GX /O2 /I "../src/libsp/win32/include" /I "../src" /D "WIN32" /D "NDEBUG" /D "_MBCS" /D "_LIB" /YX /FD /c +# ADD BASE RSC /l 0x419 /d "NDEBUG" +# ADD RSC /l 0x419 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LIB32=link.exe -lib +# ADD BASE LIB32 /nologo +# ADD LIB32 /nologo + +!ELSEIF "$(CFG)" == "gui - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug/gui" +# PROP BASE Intermediate_Dir "Debug/gui" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug/gui" +# PROP Intermediate_Dir "Debug/gui" +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_MBCS" /D "_LIB" /YX /FD /GZ /c +# ADD CPP /nologo /MDd /W4 /Gm /GX /ZI /Od /I "../src/libsp/win32/include" /I "../src" /D "WIN32" /D "_DEBUG" /D "_MBCS" /D "_LIB" /YX /FD /GZ /c +# ADD BASE RSC /l 0x419 /d "_DEBUG" +# ADD RSC /l 0x419 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LIB32=link.exe -lib +# ADD BASE LIB32 /nologo +# ADD LIB32 /nologo + +!ENDIF + +# Begin Target + +# Name "gui - Win32 Release" +# Name "gui - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Source File + +SOURCE=..\src\gui\console.cpp +# End Source File +# Begin Source File + +SOURCE=..\src\gui\font.cpp +# End Source File +# Begin Source File + +SOURCE=..\src\gui\giflib.cpp +# End Source File +# Begin Source File + +SOURCE=..\src\gui\image.cpp +# End Source File +# Begin Source File + +SOURCE=..\src\gui\jpeg.cpp +# ADD CPP /I "../src/contrib/libjpeg" +# End Source File +# Begin Source File + +SOURCE=..\src\gui\mmsl.cpp +# End Source File +# Begin Source File + +SOURCE=..\src\gui\rect.cpp +# End Source File +# Begin Source File + +SOURCE=..\src\gui\res.cpp +# End Source File +# Begin Source File + +SOURCE=..\src\gui\text.cpp +# End Source File +# Begin Source File + +SOURCE=..\src\gui\window.cpp +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# Begin Source File + +SOURCE=..\src\gui\console.h +# End Source File +# Begin Source File + +SOURCE=..\src\gui\font.h +# End Source File +# Begin Source File + +SOURCE=..\src\gui\giflib.h +# End Source File +# Begin Source File + +SOURCE=..\src\gui\image.h +# End Source File +# Begin Source File + +SOURCE=..\src\gui\jpeg.h +# End Source File +# Begin Source File + +SOURCE=..\src\gui\mmsl.h +# End Source File +# Begin Source File + +SOURCE=..\src\gui\rect.h +# End Source File +# Begin Source File + +SOURCE=..\src\gui\res.h +# End Source File +# Begin Source File + +SOURCE=..\src\gui\text.h +# End Source File +# Begin Source File + +SOURCE=..\src\gui\window.h +# End Source File +# End Group +# End Target +# End Project diff --git a/win32/gui.vcproj b/win32/gui.vcproj new file mode 100644 index 0000000..ea4196d --- /dev/null +++ b/win32/gui.vcproj @@ -0,0 +1,442 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/win32/init.dsp b/win32/init.dsp new file mode 100644 index 0000000..e10631b --- /dev/null +++ b/win32/init.dsp @@ -0,0 +1,254 @@ +# Microsoft Developer Studio Project File - Name="init" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Console Application" 0x0103 + +CFG=init - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "init.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "init.mak" CFG="init - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "init - Win32 Release" (based on "Win32 (x86) Console Application") +!MESSAGE "init - Win32 Debug" (based on "Win32 (x86) Console Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +RSC=rc.exe + +!IF "$(CFG)" == "init - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_MBCS" /YX /FD /c +# ADD CPP /nologo /MD /W4 /GX /O2 /I "../src/libsp/win32/include" /I "../src" /I "../src/contrib/libdvdnav/src" /I "../src/contrib/libdvdnav/src/dvdread" /I "../src/contrib/libdvdnav/msvc" /D "NDEBUG" /D "WIN32" /D "_MBCS" /D "DVDCSS_USE_EXTERNAL_CSS" /YX /FD /c +# ADD BASE RSC /l 0x419 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /incremental:yes /machine:I386 /out:"../init.exe" +# SUBTRACT LINK32 /debug + +!ELSEIF "$(CFG)" == "init - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Ignore_Export_Lib 0 +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_MBCS" /YX /FD /GZ /c +# ADD CPP /nologo /MDd /W4 /Gm /GX /ZI /Od /I "../src/libsp/win32/include" /I "../src" /I "../src/contrib/libdvdnav/src" /I "../src/contrib/libdvdnav/src/dvdread" /I "../src/contrib/libdvdnav/msvc" /D "_DEBUG" /D "WIN32" /D "_MBCS" /D "DVDCSS_USE_EXTERNAL_CSS" /YX /FD /GZ /c +# ADD BASE RSC /l 0x419 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /debug /machine:I386 /pdbtype:sept +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:windows /debug /machine:I386 /out:"../init.exe" /pdbtype:sept /fixed:no +# SUBTRACT LINK32 /pdb:none + +!ENDIF + +# Begin Target + +# Name "init - Win32 Release" +# Name "init - Win32 Debug" +# Begin Group "Source files" + +# PROP Default_Filter "*.cpp;*.c" +# Begin Source File + +SOURCE=..\src\audio.cpp +# End Source File +# Begin Source File + +SOURCE=..\src\avi.cpp +# End Source File +# Begin Source File + +SOURCE=..\src\bitstream.cpp +# End Source File +# Begin Source File + +SOURCE=..\src\cdda.cpp +# End Source File +# Begin Source File + +SOURCE=..\src\divx.cpp +# End Source File +# Begin Source File + +SOURCE=..\src\init.cpp +# End Source File +# Begin Source File + +SOURCE=..\src\media.cpp +# ADD CPP /D "DVDNAV_COMPILE" +# End Source File +# Begin Source File + +SOURCE="..\src\module-dvd.cpp" +# SUBTRACT CPP /YX +# End Source File +# Begin Source File + +SOURCE="..\src\module-init.cpp" +# SUBTRACT CPP /YX +# End Source File +# Begin Source File + +SOURCE=..\src\module.cpp +# SUBTRACT CPP /YX +# End Source File +# Begin Source File + +SOURCE=..\src\mpg.cpp +# End Source File +# Begin Source File + +SOURCE=..\src\player.cpp +# ADD CPP /D "PLAYER_INFO_EMBED" +# End Source File +# Begin Source File + +SOURCE=..\src\info\player_info.cpp +# ADD CPP /D "PLAYER_INFO_EMBED" +# End Source File +# Begin Source File + +SOURCE="..\src\script-explorer.cpp" +# End Source File +# Begin Source File + +SOURCE="..\src\script-objects.cpp" +# End Source File +# Begin Source File + +SOURCE="..\src\script-player.cpp" +# End Source File +# Begin Source File + +SOURCE=..\src\script.cpp +# End Source File +# Begin Source File + +SOURCE=..\src\settings.cpp +# End Source File +# Begin Source File + +SOURCE=..\src\subtitle.cpp +# End Source File +# Begin Source File + +SOURCE=..\src\video.cpp +# End Source File +# End Group +# Begin Group "Header files" + +# PROP Default_Filter "*.h" +# Begin Source File + +SOURCE=..\src\audio.h +# End Source File +# Begin Source File + +SOURCE=..\src\avi.h +# End Source File +# Begin Source File + +SOURCE=..\src\bitstream.h +# End Source File +# Begin Source File + +SOURCE=..\src\cdda.h +# End Source File +# Begin Source File + +SOURCE="..\src\divx-tables.h" +# End Source File +# Begin Source File + +SOURCE=..\src\divx.h +# End Source File +# Begin Source File + +SOURCE=..\src\module.h +# End Source File +# Begin Source File + +SOURCE=..\src\mpg.h +# End Source File +# Begin Source File + +SOURCE=..\src\player.h +# End Source File +# Begin Source File + +SOURCE=..\src\info\player_info.h +# End Source File +# Begin Source File + +SOURCE="..\src\script-internal.h" +# End Source File +# Begin Source File + +SOURCE=..\src\script.h +# End Source File +# Begin Source File + +SOURCE=..\src\settings.h +# End Source File +# Begin Source File + +SOURCE=..\src\subtitle.h +# End Source File +# Begin Source File + +SOURCE=..\src\video.h +# End Source File +# End Group +# Begin Source File + +SOURCE=..\src\libsp\win32\fip.bmp +# End Source File +# Begin Source File + +SOURCE=..\src\libsp\win32\libsp.rc +# End Source File +# Begin Source File + +SOURCE=..\src\libsp\win32\manifest.xml +# End Source File +# End Target +# End Project diff --git a/win32/init.dsw b/win32/init.dsw new file mode 100644 index 0000000..f8f388b --- /dev/null +++ b/win32/init.dsw @@ -0,0 +1,167 @@ +Microsoft Developer Studio Workspace File, Format Version 6.00 +# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE! + +############################################################################### + +Project: "dvd"=.\dvd.dsp - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ + Begin Project Dependency + Project_Dep_Name dvdnav + End Project Dependency +}}} + +############################################################################### + +Project: "dvd_mod"=.\dvd_mod.dsp - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ + Begin Project Dependency + Project_Dep_Name dvdnav + End Project Dependency +}}} + +############################################################################### + +Project: "dvdcss"=.\dvdcss.dsp - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Project: "dvdnav"=.\dvdnav.dsp - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ + Begin Project Dependency + Project_Dep_Name dvdcss + End Project Dependency +}}} + +############################################################################### + +Project: "gui"=.\gui.dsp - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Project: "init"=.\init.dsp - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ + Begin Project Dependency + Project_Dep_Name libsp + End Project Dependency + Begin Project Dependency + Project_Dep_Name gui + End Project Dependency + Begin Project Dependency + Project_Dep_Name libjpeg + End Project Dependency + Begin Project Dependency + Project_Dep_Name libpng + End Project Dependency + Begin Project Dependency + Project_Dep_Name mw + End Project Dependency + Begin Project Dependency + Project_Dep_Name libid3tag + End Project Dependency + Begin Project Dependency + Project_Dep_Name libmad + End Project Dependency +}}} + +############################################################################### + +Project: "libid3tag"=.\libid3tag.dsp - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Project: "libjpeg"=.\libjpeg.dsp - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Project: "libmad"=.\libmad.dsp - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Project: "libsp"=.\libsp.dsp - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Global: + +Package=<5> +{{{ +}}} + +Package=<3> +{{{ +}}} + +############################################################################### + diff --git a/win32/init.sln b/win32/init.sln new file mode 100644 index 0000000..b45e3f4 --- /dev/null +++ b/win32/init.sln @@ -0,0 +1,90 @@ +п»ї +Microsoft Visual Studio Solution File, Format Version 10.00 +# Visual Studio 2008 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "dvd", "dvd.vcproj", "{9FC94E1B-F135-43E1-AB9D-5D56B8BD3F66}" + ProjectSection(ProjectDependencies) = postProject + {115F0E05-BB93-46B1-93A8-BE1B2B4DED93} = {115F0E05-BB93-46B1-93A8-BE1B2B4DED93} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "dvd_mod", "dvd_mod.vcproj", "{B4DE03D5-6195-4D50-A162-8CA9341A5445}" + ProjectSection(ProjectDependencies) = postProject + {115F0E05-BB93-46B1-93A8-BE1B2B4DED93} = {115F0E05-BB93-46B1-93A8-BE1B2B4DED93} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "dvdcss", "dvdcss.vcproj", "{37D2DD79-B8A3-45F2-82AA-9DD5E1CB2221}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "dvdnav", "dvdnav.vcproj", "{115F0E05-BB93-46B1-93A8-BE1B2B4DED93}" + ProjectSection(ProjectDependencies) = postProject + {37D2DD79-B8A3-45F2-82AA-9DD5E1CB2221} = {37D2DD79-B8A3-45F2-82AA-9DD5E1CB2221} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "gui", "gui.vcproj", "{357B6DE6-0C68-42E7-B70A-4106CEF47D1A}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "init", "init.vcproj", "{DDFB60D8-953D-440F-B6F1-7483A172E1F9}" + ProjectSection(ProjectDependencies) = postProject + {5140CBBF-B016-4381-A38E-C23055D328EC} = {5140CBBF-B016-4381-A38E-C23055D328EC} + {357B6DE6-0C68-42E7-B70A-4106CEF47D1A} = {357B6DE6-0C68-42E7-B70A-4106CEF47D1A} + {5F05E39D-9409-48F4-B739-9ADBE945F637} = {5F05E39D-9409-48F4-B739-9ADBE945F637} + {3BE1FF59-65FA-4A91-996F-3579E44C7C5D} = {3BE1FF59-65FA-4A91-996F-3579E44C7C5D} + {8D244847-BD35-4CC3-BBBA-C877E383E3CF} = {8D244847-BD35-4CC3-BBBA-C877E383E3CF} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libid3tag", "libid3tag.vcproj", "{3BE1FF59-65FA-4A91-996F-3579E44C7C5D}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libjpeg", "libjpeg.vcproj", "{5F05E39D-9409-48F4-B739-9ADBE945F637}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libmad", "libmad.vcproj", "{8D244847-BD35-4CC3-BBBA-C877E383E3CF}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libsp", "libsp.vcproj", "{5140CBBF-B016-4381-A38E-C23055D328EC}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {9FC94E1B-F135-43E1-AB9D-5D56B8BD3F66}.Debug|Win32.ActiveCfg = Debug|Win32 + {9FC94E1B-F135-43E1-AB9D-5D56B8BD3F66}.Debug|Win32.Build.0 = Debug|Win32 + {9FC94E1B-F135-43E1-AB9D-5D56B8BD3F66}.Release|Win32.ActiveCfg = Release|Win32 + {9FC94E1B-F135-43E1-AB9D-5D56B8BD3F66}.Release|Win32.Build.0 = Release|Win32 + {B4DE03D5-6195-4D50-A162-8CA9341A5445}.Debug|Win32.ActiveCfg = Debug|Win32 + {B4DE03D5-6195-4D50-A162-8CA9341A5445}.Debug|Win32.Build.0 = Debug|Win32 + {B4DE03D5-6195-4D50-A162-8CA9341A5445}.Release|Win32.ActiveCfg = Release|Win32 + {B4DE03D5-6195-4D50-A162-8CA9341A5445}.Release|Win32.Build.0 = Release|Win32 + {37D2DD79-B8A3-45F2-82AA-9DD5E1CB2221}.Debug|Win32.ActiveCfg = Debug|Win32 + {37D2DD79-B8A3-45F2-82AA-9DD5E1CB2221}.Debug|Win32.Build.0 = Debug|Win32 + {37D2DD79-B8A3-45F2-82AA-9DD5E1CB2221}.Release|Win32.ActiveCfg = Release|Win32 + {37D2DD79-B8A3-45F2-82AA-9DD5E1CB2221}.Release|Win32.Build.0 = Release|Win32 + {115F0E05-BB93-46B1-93A8-BE1B2B4DED93}.Debug|Win32.ActiveCfg = Debug|Win32 + {115F0E05-BB93-46B1-93A8-BE1B2B4DED93}.Debug|Win32.Build.0 = Debug|Win32 + {115F0E05-BB93-46B1-93A8-BE1B2B4DED93}.Release|Win32.ActiveCfg = Release|Win32 + {115F0E05-BB93-46B1-93A8-BE1B2B4DED93}.Release|Win32.Build.0 = Release|Win32 + {357B6DE6-0C68-42E7-B70A-4106CEF47D1A}.Debug|Win32.ActiveCfg = Debug|Win32 + {357B6DE6-0C68-42E7-B70A-4106CEF47D1A}.Debug|Win32.Build.0 = Debug|Win32 + {357B6DE6-0C68-42E7-B70A-4106CEF47D1A}.Release|Win32.ActiveCfg = Release|Win32 + {357B6DE6-0C68-42E7-B70A-4106CEF47D1A}.Release|Win32.Build.0 = Release|Win32 + {DDFB60D8-953D-440F-B6F1-7483A172E1F9}.Debug|Win32.ActiveCfg = Debug|Win32 + {DDFB60D8-953D-440F-B6F1-7483A172E1F9}.Debug|Win32.Build.0 = Debug|Win32 + {DDFB60D8-953D-440F-B6F1-7483A172E1F9}.Release|Win32.ActiveCfg = Release|Win32 + {DDFB60D8-953D-440F-B6F1-7483A172E1F9}.Release|Win32.Build.0 = Release|Win32 + {3BE1FF59-65FA-4A91-996F-3579E44C7C5D}.Debug|Win32.ActiveCfg = Debug|Win32 + {3BE1FF59-65FA-4A91-996F-3579E44C7C5D}.Debug|Win32.Build.0 = Debug|Win32 + {3BE1FF59-65FA-4A91-996F-3579E44C7C5D}.Release|Win32.ActiveCfg = Release|Win32 + {3BE1FF59-65FA-4A91-996F-3579E44C7C5D}.Release|Win32.Build.0 = Release|Win32 + {5F05E39D-9409-48F4-B739-9ADBE945F637}.Debug|Win32.ActiveCfg = Debug|Win32 + {5F05E39D-9409-48F4-B739-9ADBE945F637}.Debug|Win32.Build.0 = Debug|Win32 + {5F05E39D-9409-48F4-B739-9ADBE945F637}.Release|Win32.ActiveCfg = Release|Win32 + {5F05E39D-9409-48F4-B739-9ADBE945F637}.Release|Win32.Build.0 = Release|Win32 + {8D244847-BD35-4CC3-BBBA-C877E383E3CF}.Debug|Win32.ActiveCfg = Debug|Win32 + {8D244847-BD35-4CC3-BBBA-C877E383E3CF}.Debug|Win32.Build.0 = Debug|Win32 + {8D244847-BD35-4CC3-BBBA-C877E383E3CF}.Release|Win32.ActiveCfg = Release|Win32 + {8D244847-BD35-4CC3-BBBA-C877E383E3CF}.Release|Win32.Build.0 = Release|Win32 + {5140CBBF-B016-4381-A38E-C23055D328EC}.Debug|Win32.ActiveCfg = Debug|Win32 + {5140CBBF-B016-4381-A38E-C23055D328EC}.Debug|Win32.Build.0 = Debug|Win32 + {5140CBBF-B016-4381-A38E-C23055D328EC}.Release|Win32.ActiveCfg = Release|Win32 + {5140CBBF-B016-4381-A38E-C23055D328EC}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/win32/init.vcproj b/win32/init.vcproj new file mode 100644 index 0000000..d556f28 --- /dev/null +++ b/win32/init.vcproj @@ -0,0 +1,726 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/win32/libid3tag.dsp b/win32/libid3tag.dsp new file mode 100644 index 0000000..c2bba71 --- /dev/null +++ b/win32/libid3tag.dsp @@ -0,0 +1,266 @@ +# Microsoft Developer Studio Project File - Name="libid3tag" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Static Library" 0x0104 + +CFG=libid3tag - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "libid3tag.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "libid3tag.mak" CFG="libid3tag - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "libid3tag - Win32 Release" (based on "Win32 (x86) Static Library") +!MESSAGE "libid3tag - Win32 Debug" (based on "Win32 (x86) Static Library") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +RSC=rc.exe + +!IF "$(CFG)" == "libid3tag - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_MBCS" /D "_LIB" /YX /FD /c +# ADD CPP /nologo /MD /GX /O2 /I "..\src\libsp\win32\include" /I "..\src\contrib\libid3tag\fakezlib" /D "NDEBUG" /D "WIN32" /D "_MBCS" /D "_LIB" /D "HAVE_CONFIG_H" /Fo"Release/libid3tag/" /Fd"Release/libid3tag/" /FD /c +# SUBTRACT CPP /YX +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LIB32=link.exe -lib +# ADD BASE LIB32 /nologo +# ADD LIB32 /nologo + +!ELSEIF "$(CFG)" == "libid3tag - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_MBCS" /D "_LIB" /YX /FD /GZ /c +# ADD CPP /nologo /MDd /Gm /GX /ZI /Od /I "..\src\libsp\win32\include" /I "..\src\contrib\libid3tag\fakezlib" /D "_LIB" /D "HAVE_CONFIG_H" /D "WIN32" /D "_DEBUG" /D "_MBCS" /D "DEBUG" /Fo"Debug/libid3tag/" /Fd"Debug/libid3tag/" /FD /GZ /c +# SUBTRACT CPP /YX +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LIB32=link.exe -lib +# ADD BASE LIB32 /nologo +# ADD LIB32 /nologo + +!ENDIF + +# Begin Target + +# Name "libid3tag - Win32 Release" +# Name "libid3tag - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "c" +# Begin Source File + +SOURCE=..\src\contrib\libid3tag\compat.c +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libid3tag\cp1251.c +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libid3tag\crc.c +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libid3tag\debug.c +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libid3tag\field.c +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libid3tag\file.c +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libid3tag\frame.c +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libid3tag\frametype.c +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libid3tag\genre.c +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libid3tag\latin1.c +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libid3tag\parse.c +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libid3tag\render.c +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libid3tag\tag.c +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libid3tag\ucs4.c +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libid3tag\utf16.c +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libid3tag\utf8.c +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libid3tag\util.c +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libid3tag\version.c +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libid3tag\fakezlib\zlib.c +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h" +# Begin Source File + +SOURCE=..\src\contrib\libid3tag\compat.h +# End Source File +# Begin Source File + +SOURCE=.\config.h +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libid3tag\cp1251.h +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libid3tag\crc.h +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libid3tag\debug.h +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libid3tag\field.h +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libid3tag\file.h +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libid3tag\frame.h +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libid3tag\frametype.h +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libid3tag\genre.h +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libid3tag\global.h +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libid3tag\id3tag.h +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libid3tag\latin1.h +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libid3tag\parse.h +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libid3tag\render.h +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libid3tag\tag.h +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libid3tag\ucs4.h +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libid3tag\utf16.h +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libid3tag\utf8.h +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libid3tag\util.h +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libid3tag\version.h +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libid3tag\fakezlib\zlib.h +# End Source File +# End Group +# Begin Group "Data Files" + +# PROP Default_Filter "dat" +# Begin Source File + +SOURCE=..\src\contrib\libid3tag\genre.dat +# End Source File +# End Group +# End Target +# End Project diff --git a/win32/libid3tag.vcproj b/win32/libid3tag.vcproj new file mode 100644 index 0000000..b80957e --- /dev/null +++ b/win32/libid3tag.vcproj @@ -0,0 +1,697 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/win32/libjpeg.dsp b/win32/libjpeg.dsp new file mode 100644 index 0000000..4b87993 --- /dev/null +++ b/win32/libjpeg.dsp @@ -0,0 +1,244 @@ +# Microsoft Developer Studio Project File - Name="libjpeg" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Static Library" 0x0104 + +CFG=libjpeg - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "libjpeg.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "libjpeg.mak" CFG="libjpeg - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "libjpeg - Win32 Release" (based on "Win32 (x86) Static Library") +!MESSAGE "libjpeg - Win32 Debug" (based on "Win32 (x86) Static Library") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +RSC=rc.exe + +!IF "$(CFG)" == "libjpeg - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_MBCS" /D "_LIB" /YX /FD /c +# ADD CPP /nologo /MD /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_MBCS" /D "_LIB" /YX /Fo"Release/libjpeg/" /Fd"Release/libjpeg/" /FD /c +# ADD BASE RSC /l 0x419 /d "NDEBUG" +# ADD RSC /l 0x419 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LIB32=link.exe -lib +# ADD BASE LIB32 /nologo +# ADD LIB32 /nologo + +!ELSEIF "$(CFG)" == "libjpeg - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Win32_Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_MBCS" /D "_LIB" /YX /FD /GZ /c +# ADD CPP /nologo /MDd /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_MBCS" /D "_LIB" /YX /Fo"Debug/libjpeg/" /Fd"Debug/libjpeg/" /FD /GZ /c +# ADD BASE RSC /l 0x419 /d "_DEBUG" +# ADD RSC /l 0x419 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LIB32=link.exe -lib +# ADD BASE LIB32 /nologo +# ADD LIB32 /nologo + +!ENDIF + +# Begin Target + +# Name "libjpeg - Win32 Release" +# Name "libjpeg - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Source File + +SOURCE=..\src\contrib\libjpeg\jcomapi.c +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libjpeg\jdapimin.c +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libjpeg\jdapistd.c +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libjpeg\jdatadst.c +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libjpeg\jdatasrc.c +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libjpeg\jdcoefct.c +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libjpeg\jdcolor.c +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libjpeg\jddctmgr.c +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libjpeg\jdhuff.c +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libjpeg\jdinput.c +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libjpeg\jdmainct.c +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libjpeg\jdmarker.c +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libjpeg\jdmaster.c +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libjpeg\jdmerge.c +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libjpeg\jdphuff.c +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libjpeg\jdpostct.c +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libjpeg\jdsample.c +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libjpeg\jdtrans.c +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libjpeg\jerror.c +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libjpeg\jidctflt.c +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libjpeg\jidctfst.c +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libjpeg\jidctint.c +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libjpeg\jidctred.c +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libjpeg\jmemansi.c +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libjpeg\jmemmgr.c +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libjpeg\jquant1.c +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libjpeg\jquant2.c +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libjpeg\jutils.c +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# Begin Source File + +SOURCE=..\src\contrib\libjpeg\jconfig.h +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libjpeg\jdct.h +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libjpeg\jdhuff.h +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libjpeg\jerror.h +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libjpeg\jinclude.h +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libjpeg\jmemsys.h +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libjpeg\jmorecfg.h +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libjpeg\jpegint.h +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libjpeg\jpeglib.h +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libjpeg\jversion.h +# End Source File +# End Group +# End Target +# End Project diff --git a/win32/libjpeg.vcproj b/win32/libjpeg.vcproj new file mode 100644 index 0000000..1f117b8 --- /dev/null +++ b/win32/libjpeg.vcproj @@ -0,0 +1,782 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/win32/libmad.dsp b/win32/libmad.dsp new file mode 100644 index 0000000..4b49168 --- /dev/null +++ b/win32/libmad.dsp @@ -0,0 +1,213 @@ +# Microsoft Developer Studio Project File - Name="libmad" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Static Library" 0x0104 + +CFG=libmad - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "libmad.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "libmad.mak" CFG="libmad - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "libmad - Win32 Release" (based on "Win32 (x86) Static Library") +!MESSAGE "libmad - Win32 Debug" (based on "Win32 (x86) Static Library") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +RSC=rc.exe + +!IF "$(CFG)" == "libmad - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_MBCS" /D "_LIB" /YX /FD /c +# ADD CPP /nologo /MD /GX /O2 /I "..\src\contrib\libmad\msvc++" /I "..\src\libsp\win32\include" /D "NDEBUG" /D "FPM_INTEL" /D "WIN32" /D "_MBCS" /D "_LIB" /D "HAVE_CONFIG_H" /D "ASO_ZEROCHECK" /Fo"Release/libmad/" /Fd"Release/libmad/" /FD /c +# SUBTRACT CPP /YX +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LIB32=link.exe -lib +# ADD BASE LIB32 /nologo +# ADD LIB32 /nologo + +!ELSEIF "$(CFG)" == "libmad - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_MBCS" /D "_LIB" /YX /FD /GZ /c +# ADD CPP /nologo /MDd /Gm /GX /ZI /Od /I "..\src\contrib\libmad\msvc++" /I "..\src\libsp\win32\include" /D "FPM_DEFAULT" /D "_LIB" /D "HAVE_CONFIG_H" /D "ASO_ZEROCHECK" /D "WIN32" /D "_DEBUG" /D "_MBCS" /D "DEBUG" /Fo"Debug/libmad/" /Fd"Debug/libmad/" /FD /GZ /c +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LIB32=link.exe -lib +# ADD BASE LIB32 /nologo +# ADD LIB32 /nologo + +!ENDIF + +# Begin Target + +# Name "libmad - Win32 Release" +# Name "libmad - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "c" +# Begin Source File + +SOURCE=..\src\contrib\libmad\bit.c +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libmad\decoder.c +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libmad\fixed.c +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libmad\frame.c +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libmad\huffman.c +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libmad\layer12.c +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libmad\layer3.c +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libmad\stream.c +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libmad\synth.c +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libmad\timer.c +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libmad\version.c +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h" +# Begin Source File + +SOURCE=..\src\contrib\libmad\bit.h +# End Source File +# Begin Source File + +SOURCE=.\src\contrib\libmad\config.h +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libmad\decoder.h +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libmad\fixed.h +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libmad\frame.h +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libmad\global.h +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libmad\huffman.h +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libmad\layer12.h +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libmad\layer3.h +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libmad\stream.h +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libmad\synth.h +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libmad\timer.h +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libmad\version.h +# End Source File +# End Group +# Begin Group "Data Files" + +# PROP Default_Filter "dat" +# Begin Source File + +SOURCE=..\src\contrib\libmad\D.dat +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libmad\imdct_s.dat +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libmad\qc_table.dat +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libmad\rq_table.dat +# End Source File +# Begin Source File + +SOURCE=..\src\contrib\libmad\sf_table.dat +# End Source File +# End Group +# End Target +# End Project diff --git a/win32/libmad.vcproj b/win32/libmad.vcproj new file mode 100644 index 0000000..7d83c95 --- /dev/null +++ b/win32/libmad.vcproj @@ -0,0 +1,501 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/win32/libsp.dsp b/win32/libsp.dsp new file mode 100644 index 0000000..3e6bba0 --- /dev/null +++ b/win32/libsp.dsp @@ -0,0 +1,256 @@ +# Microsoft Developer Studio Project File - Name="libsp" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Static Library" 0x0104 + +CFG=libsp - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "libsp.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "libsp.mak" CFG="libsp - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "libsp - Win32 Release" (based on "Win32 (x86) Static Library") +!MESSAGE "libsp - Win32 Debug" (based on "Win32 (x86) Static Library") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +RSC=rc.exe + +!IF "$(CFG)" == "libsp - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_MBCS" /D "_LIB" /YX /FD /c +# ADD CPP /nologo /MD /W4 /GX /O2 /I "../src/libsp" /I "../src/libsp/win32/include" /I "../src" /D "WIN32" /D "NDEBUG" /D "_MBCS" /D "_LIB" /YX /Fo"Release/libsp/" /Fd"Release/libsp/" /FD /c +# ADD BASE RSC /l 0x419 /d "NDEBUG" +# ADD RSC /l 0x419 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LIB32=link.exe -lib +# ADD BASE LIB32 /nologo +# ADD LIB32 /nologo + +!ELSEIF "$(CFG)" == "libsp - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_MBCS" /D "_LIB" /YX /FD /GZ /c +# ADD CPP /nologo /MDd /W4 /Gm /GX /ZI /Od /I "../src/libsp" /I "../src/libsp/win32/include" /I "../src" /D "WIN32" /D "_DEBUG" /D "_MBCS" /D "_LIB" /YX /Fo"Debug/libsp/" /Fd"Debug/libsp/" /FD /GZ /c +# ADD BASE RSC /l 0x419 /d "_DEBUG" +# ADD RSC /l 0x419 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LIB32=link.exe -lib +# ADD BASE LIB32 /nologo +# ADD LIB32 /nologo + +!ENDIF + +# Begin Target + +# Name "libsp - Win32 Release" +# Name "libsp - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Source File + +SOURCE=..\src\libsp\win32\dirent.c +# End Source File +# Begin Source File + +SOURCE=..\src\libsp\containers\membin.cpp +# End Source File +# Begin Source File + +SOURCE=..\src\libsp\win32\sp_cdrom.cpp +# End Source File +# Begin Source File + +SOURCE=..\src\libsp\win32\sp_css.cpp +# End Source File +# Begin Source File + +SOURCE=..\src\libsp\win32\sp_eeprom.cpp +# End Source File +# Begin Source File + +SOURCE=..\src\libsp\win32\sp_fip.cpp +# End Source File +# Begin Source File + +SOURCE=..\src\libsp\win32\sp_flash.cpp +# End Source File +# Begin Source File + +SOURCE=..\src\libsp\win32\sp_khwl.cpp +# End Source File +# Begin Source File + +SOURCE=..\src\libsp\sp_khwl_colors.cpp +# End Source File +# Begin Source File + +SOURCE=..\src\libsp\win32\sp_memory.cpp +# End Source File +# Begin Source File + +SOURCE=..\src\libsp\sp_misc.cpp +# End Source File +# Begin Source File + +SOURCE=..\src\libsp\win32\sp_module.cpp +# End Source File +# Begin Source File + +SOURCE=..\src\libsp\sp_mpeg.cpp +# End Source File +# Begin Source File + +SOURCE=..\src\libsp\sp_msg.cpp +# End Source File +# Begin Source File + +SOURCE=..\src\libsp\sp_video.cpp +# End Source File +# Begin Source File + +SOURCE=..\src\libsp\containers\string.cpp +# End Source File +# Begin Source File + +SOURCE="..\src\libsp\win32\win32-stuff.c" +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# Begin Source File + +SOURCE=..\src\libsp\containers\clist.h +# End Source File +# Begin Source File + +SOURCE=..\src\libsp\win32\dirent.h +# End Source File +# Begin Source File + +SOURCE=..\src\libsp\containers\dllist.h +# End Source File +# Begin Source File + +SOURCE=..\src\libsp\containers\hashlist.h +# End Source File +# Begin Source File + +SOURCE=..\src\libsp\containers\list.h +# End Source File +# Begin Source File + +SOURCE=..\src\libsp\containers\membin.h +# End Source File +# Begin Source File + +SOURCE=..\src\libsp\win32\resource.h +# End Source File +# Begin Source File + +SOURCE=..\src\libsp\containers\slist.h +# End Source File +# Begin Source File + +SOURCE=..\src\libsp\sp_bswap.h +# End Source File +# Begin Source File + +SOURCE=..\src\libsp\sp_cdrom.h +# End Source File +# Begin Source File + +SOURCE=..\src\libsp\win32\sp_css.h +# End Source File +# Begin Source File + +SOURCE=..\src\libsp\sp_eeprom.h +# End Source File +# Begin Source File + +SOURCE=..\src\libsp\sp_fip.h +# End Source File +# Begin Source File + +SOURCE=..\src\libsp\sp_flash.h +# End Source File +# Begin Source File + +SOURCE=..\src\libsp\sp_io.h +# End Source File +# Begin Source File + +SOURCE=..\src\libsp\sp_khwl.h +# End Source File +# Begin Source File + +SOURCE=..\src\libsp\sp_memory.h +# End Source File +# Begin Source File + +SOURCE=..\src\libsp\sp_misc.h +# End Source File +# Begin Source File + +SOURCE=..\src\libsp\sp_module.h +# End Source File +# Begin Source File + +SOURCE=..\src\libsp\sp_mpeg.h +# End Source File +# Begin Source File + +SOURCE=..\src\libsp\sp_msg.h +# End Source File +# Begin Source File + +SOURCE=..\src\libsp\sp_video.h +# End Source File +# Begin Source File + +SOURCE=..\src\libsp\containers\string.h +# End Source File +# Begin Source File + +SOURCE="..\src\libsp\win32\win32-stuff.h" +# End Source File +# End Group +# End Target +# End Project diff --git a/win32/libsp.vcproj b/win32/libsp.vcproj new file mode 100644 index 0000000..b95dd4e --- /dev/null +++ b/win32/libsp.vcproj @@ -0,0 +1,636 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +