From 9ca1e259cb13cdc5bc0723a855bd81157396afe9 Mon Sep 17 00:00:00 2001 From: byuu Date: Wed, 14 Apr 2010 15:46:56 +0000 Subject: [PATCH] Update to bsnes v064 release. A thank you to everyone who helped test the RC to ensure stability. I've uploaded the official v064 release to Google Code. The most important change in this release is the cycle-based PPU renderer; but due to performance reasons the scanline-based renderer remains the default in the Windows binary. If you want to try out the cycle-based renderer, you will need to compile from source for now. Another major change is the introduction of libsnes, which allows one to build bsnes as a shared library that can be used from other programming languages. It is intended both to create a regression testing framework, and to provide API stability for the various projects that use the bsnes core. While I can't guarantee the API to libsnes won't change, I will properly revision it and do everything I can to avoid changing it if possible. --- bsnes.exe | Bin 690176 -> 0 bytes snesfilter.dll | Bin 96568 -> 0 bytes snesfilter/2xsai/2xsai.cpp | 132 + snesfilter/2xsai/2xsai.hpp | 35 + snesfilter/2xsai/implementation.cpp | 1171 +++++ snesfilter/Makefile | 89 + snesfilter/cc.bat | 2 + snesfilter/clean.bat | 1 + snesfilter/direct/direct.cpp | 23 + snesfilter/direct/direct.hpp | 5 + snesfilter/hq2x/hq2x.cpp | 195 + snesfilter/hq2x/hq2x.hpp | 30 + snesfilter/lq2x/lq2x.cpp | 53 + snesfilter/lq2x/lq2x.hpp | 5 + snesfilter/nall/Makefile | 107 + snesfilter/nall/algorithm.hpp | 23 + snesfilter/nall/any.hpp | 74 + snesfilter/nall/array.hpp | 120 + snesfilter/nall/base64.hpp | 90 + snesfilter/nall/bit.hpp | 51 + snesfilter/nall/concept.hpp | 15 + snesfilter/nall/config.hpp | 123 + snesfilter/nall/crc32.hpp | 66 + snesfilter/nall/detect.hpp | 30 + snesfilter/nall/dictionary.hpp | 75 + snesfilter/nall/dl.hpp | 119 + snesfilter/nall/endian.hpp | 38 + snesfilter/nall/file.hpp | 259 ++ snesfilter/nall/filemap.hpp | 190 + snesfilter/nall/foreach.hpp | 31 + snesfilter/nall/function.hpp | 102 + snesfilter/nall/input.hpp | 386 ++ snesfilter/nall/lzss.hpp | 81 + snesfilter/nall/moduloarray.hpp | 40 + snesfilter/nall/platform.hpp | 80 + snesfilter/nall/priorityqueue.hpp | 109 + snesfilter/nall/property.hpp | 91 + snesfilter/nall/qt/Makefile | 55 + snesfilter/nall/qt/check-action.moc.hpp | 41 + snesfilter/nall/qt/concept.hpp | 10 + snesfilter/nall/qt/file-dialog.moc.hpp | 392 ++ snesfilter/nall/qt/hex-editor.moc.hpp | 173 + snesfilter/nall/qt/radio-action.moc.hpp | 41 + snesfilter/nall/qt/window.moc.hpp | 105 + snesfilter/nall/serial.hpp | 80 + snesfilter/nall/serializer.hpp | 145 + snesfilter/nall/sha256.hpp | 143 + snesfilter/nall/sort.hpp | 62 + snesfilter/nall/static.hpp | 20 + snesfilter/nall/stdint.hpp | 44 + snesfilter/nall/string.hpp | 27 + snesfilter/nall/string/base.hpp | 136 + snesfilter/nall/string/cast.hpp | 32 + snesfilter/nall/string/compare.hpp | 72 + snesfilter/nall/string/convert.hpp | 153 + snesfilter/nall/string/core.hpp | 133 + snesfilter/nall/string/filename.hpp | 61 + snesfilter/nall/string/match.hpp | 76 + snesfilter/nall/string/math.hpp | 164 + snesfilter/nall/string/replace.hpp | 103 + snesfilter/nall/string/split.hpp | 56 + snesfilter/nall/string/strl.hpp | 52 + snesfilter/nall/string/strpos.hpp | 60 + snesfilter/nall/string/trim.hpp | 54 + snesfilter/nall/string/utility.hpp | 169 + snesfilter/nall/string/variadic.hpp | 27 + snesfilter/nall/string/xml.hpp | 265 ++ snesfilter/nall/ups.hpp | 190 + snesfilter/nall/utf8.hpp | 72 + snesfilter/nall/utility.hpp | 30 + snesfilter/nall/varint.hpp | 92 + snesfilter/nall/vector.hpp | 240 + snesfilter/ntsc/ntsc.cpp | 380 ++ snesfilter/ntsc/ntsc.moc.hpp | 91 + snesfilter/ntsc/snes_ntsc/snes_ntsc.c | 251 ++ snesfilter/ntsc/snes_ntsc/snes_ntsc.h | 228 + snesfilter/ntsc/snes_ntsc/snes_ntsc_config.h | 26 + snesfilter/ntsc/snes_ntsc/snes_ntsc_impl.h | 439 ++ snesfilter/pixellate2x/pixellate2x.cpp | 38 + snesfilter/pixellate2x/pixellate2x.hpp | 5 + snesfilter/scale2x/scale2x.cpp | 53 + snesfilter/scale2x/scale2x.hpp | 5 + snesfilter/snesfilter.cpp | 83 + snesfilter/snesfilter.hpp | 12 + snesfilter/sync.sh | 2 + snesreader.dll | Bin 761582 -> 0 bytes snesreader/7z_C/7zAlloc.c | 77 + snesreader/7z_C/7zAlloc.h | 23 + snesreader/7z_C/7zBuf.c | 36 + snesreader/7z_C/7zBuf.h | 31 + snesreader/7z_C/7zC.txt | 194 + snesreader/7z_C/7zCrc.c | 35 + snesreader/7z_C/7zCrc.h | 32 + snesreader/7z_C/7zDecode.c | 257 ++ snesreader/7z_C/7zDecode.h | 13 + snesreader/7z_C/7zExtract.c | 93 + snesreader/7z_C/7zExtract.h | 49 + snesreader/7z_C/7zHeader.c | 6 + snesreader/7z_C/7zHeader.h | 57 + snesreader/7z_C/7zIn.c | 1204 ++++++ snesreader/7z_C/7zIn.h | 49 + snesreader/7z_C/7zItem.c | 129 + snesreader/7z_C/7zItem.h | 83 + snesreader/7z_C/7zStream.c | 184 + snesreader/7z_C/Bcj2.c | 132 + snesreader/7z_C/Bcj2.h | 30 + snesreader/7z_C/Bra.h | 60 + snesreader/7z_C/Bra86.c | 85 + snesreader/7z_C/CpuArch.h | 69 + snesreader/7z_C/LzmaDec.c | 1010 +++++ snesreader/7z_C/LzmaDec.h | 223 + snesreader/7z_C/Types.h | 206 + snesreader/7z_C/lzma.txt | 594 +++ snesreader/7z_C/readme.txt | 19 + snesreader/Makefile | 187 + snesreader/cc.bat | 2 + snesreader/clean.bat | 1 + snesreader/fex/Binary_Extractor.cpp | 77 + snesreader/fex/Binary_Extractor.h | 26 + snesreader/fex/Data_Reader.cpp | 551 +++ snesreader/fex/Data_Reader.h | 264 ++ snesreader/fex/File_Extractor.cpp | 341 ++ snesreader/fex/File_Extractor.h | 191 + snesreader/fex/Gzip_Extractor.cpp | 98 + snesreader/fex/Gzip_Extractor.h | 34 + snesreader/fex/Gzip_Reader.cpp | 85 + snesreader/fex/Gzip_Reader.h | 46 + snesreader/fex/Rar_Extractor.cpp | 197 + snesreader/fex/Rar_Extractor.h | 43 + snesreader/fex/Zip7_Extractor.cpp | 252 ++ snesreader/fex/Zip7_Extractor.h | 34 + snesreader/fex/Zip_Extractor.cpp | 390 ++ snesreader/fex/Zip_Extractor.h | 45 + snesreader/fex/Zlib_Inflater.cpp | 257 ++ snesreader/fex/Zlib_Inflater.h | 70 + snesreader/fex/blargg_common.cpp | 51 + snesreader/fex/blargg_common.h | 206 + snesreader/fex/blargg_config.h | 34 + snesreader/fex/blargg_endian.h | 185 + snesreader/fex/blargg_errors.cpp | 113 + snesreader/fex/blargg_errors.h | 80 + snesreader/fex/blargg_source.h | 125 + snesreader/fex/fex.cpp | 323 ++ snesreader/fex/fex.h | 206 + snesreader/filechooser.cpp | 57 + snesreader/filechooser.moc.hpp | 20 + snesreader/libjma/7z.h | 28 + snesreader/libjma/7zlzma.cpp | 50 + snesreader/libjma/aribitcd.h | 73 + snesreader/libjma/ariconst.h | 29 + snesreader/libjma/ariprice.h | 12 + snesreader/libjma/btreecd.h | 126 + snesreader/libjma/crc32.h | 26 + snesreader/libjma/iiostrm.cpp | 132 + snesreader/libjma/iiostrm.h | 210 + snesreader/libjma/inbyte.cpp | 60 + snesreader/libjma/inbyte.h | 76 + snesreader/libjma/jcrc32.cpp | 80 + snesreader/libjma/jma.cpp | 550 +++ snesreader/libjma/jma.h | 88 + snesreader/libjma/lencoder.h | 93 + snesreader/libjma/litcoder.h | 122 + snesreader/libjma/lzma.cpp | 41 + snesreader/libjma/lzma.h | 124 + snesreader/libjma/lzmadec.h | 82 + snesreader/libjma/lzmadecode.cpp | 298 ++ snesreader/libjma/portable.h | 84 + snesreader/libjma/rcdefs.h | 60 + snesreader/libjma/rngcoder.h | 143 + snesreader/libjma/winout.cpp | 89 + snesreader/libjma/winout.h | 90 + snesreader/micro-bunzip/micro-bunzip.c | 515 +++ snesreader/nall/Makefile | 107 + snesreader/nall/algorithm.hpp | 23 + snesreader/nall/any.hpp | 74 + snesreader/nall/array.hpp | 120 + snesreader/nall/base64.hpp | 90 + snesreader/nall/bit.hpp | 51 + snesreader/nall/concept.hpp | 15 + snesreader/nall/config.hpp | 123 + snesreader/nall/crc32.hpp | 66 + snesreader/nall/detect.hpp | 30 + snesreader/nall/dictionary.hpp | 75 + snesreader/nall/dl.hpp | 119 + snesreader/nall/endian.hpp | 38 + snesreader/nall/file.hpp | 259 ++ snesreader/nall/filemap.hpp | 190 + snesreader/nall/foreach.hpp | 31 + snesreader/nall/function.hpp | 102 + snesreader/nall/input.hpp | 386 ++ snesreader/nall/lzss.hpp | 81 + snesreader/nall/moduloarray.hpp | 40 + snesreader/nall/platform.hpp | 80 + snesreader/nall/priorityqueue.hpp | 109 + snesreader/nall/property.hpp | 91 + snesreader/nall/qt/Makefile | 55 + snesreader/nall/qt/check-action.moc.hpp | 41 + snesreader/nall/qt/concept.hpp | 10 + snesreader/nall/qt/file-dialog.moc.hpp | 392 ++ snesreader/nall/qt/hex-editor.moc.hpp | 173 + snesreader/nall/qt/radio-action.moc.hpp | 41 + snesreader/nall/qt/window.moc.hpp | 105 + snesreader/nall/serial.hpp | 80 + snesreader/nall/serializer.hpp | 145 + snesreader/nall/sha256.hpp | 143 + snesreader/nall/sort.hpp | 62 + snesreader/nall/static.hpp | 20 + snesreader/nall/stdint.hpp | 44 + snesreader/nall/string.hpp | 27 + snesreader/nall/string/base.hpp | 136 + snesreader/nall/string/cast.hpp | 32 + snesreader/nall/string/compare.hpp | 72 + snesreader/nall/string/convert.hpp | 153 + snesreader/nall/string/core.hpp | 133 + snesreader/nall/string/filename.hpp | 61 + snesreader/nall/string/match.hpp | 76 + snesreader/nall/string/math.hpp | 164 + snesreader/nall/string/replace.hpp | 103 + snesreader/nall/string/split.hpp | 56 + snesreader/nall/string/strl.hpp | 52 + snesreader/nall/string/strpos.hpp | 60 + snesreader/nall/string/trim.hpp | 54 + snesreader/nall/string/utility.hpp | 169 + snesreader/nall/string/variadic.hpp | 27 + snesreader/nall/string/xml.hpp | 265 ++ snesreader/nall/ups.hpp | 190 + snesreader/nall/utf8.hpp | 72 + snesreader/nall/utility.hpp | 30 + snesreader/nall/varint.hpp | 92 + snesreader/nall/vector.hpp | 240 + snesreader/snesreader.cpp | 227 + snesreader/snesreader.hpp | 7 + snesreader/sync.sh | 2 + snesreader/unrar/archive.cpp | 97 + snesreader/unrar/archive.hpp | 45 + snesreader/unrar/arcread.cpp | 314 ++ snesreader/unrar/array.hpp | 135 + snesreader/unrar/changes.txt | 141 + snesreader/unrar/coder.cpp | 49 + snesreader/unrar/coder.hpp | 24 + snesreader/unrar/compress.hpp | 36 + snesreader/unrar/crc.cpp | 69 + snesreader/unrar/encname.cpp | 57 + snesreader/unrar/encname.hpp | 20 + snesreader/unrar/extract.cpp | 110 + snesreader/unrar/getbits.cpp | 34 + snesreader/unrar/getbits.hpp | 40 + snesreader/unrar/headers.hpp | 145 + snesreader/unrar/license.txt | 40 + snesreader/unrar/model.cpp | 612 +++ snesreader/unrar/model.hpp | 133 + snesreader/unrar/rar.hpp | 209 + snesreader/unrar/rarvm.cpp | 1158 +++++ snesreader/unrar/rarvm.hpp | 112 + snesreader/unrar/rarvmtbl.cpp | 57 + snesreader/unrar/rawread.cpp | 86 + snesreader/unrar/rawread.hpp | 25 + snesreader/unrar/readme.txt | 63 + snesreader/unrar/suballoc.cpp | 261 ++ snesreader/unrar/suballoc.hpp | 88 + snesreader/unrar/technote.txt | 275 ++ snesreader/unrar/unicode.cpp | 106 + snesreader/unrar/unicode.hpp | 10 + snesreader/unrar/unpack.cpp | 1065 +++++ snesreader/unrar/unpack.hpp | 227 + snesreader/unrar/unpack15.cpp | 532 +++ snesreader/unrar/unpack20.cpp | 394 ++ snesreader/unrar/unrar.cpp | 350 ++ snesreader/unrar/unrar.h | 164 + snesreader/unrar/unrar_misc.cpp | 170 + snesreader/unrar/unrar_open.cpp | 45 + snesreader/unrar/whatsnew.txt | 267 ++ snesreader/zlib/adler32.c | 149 + snesreader/zlib/crc32.c | 423 ++ snesreader/zlib/crc32.h | 441 ++ snesreader/zlib/inffast.c | 318 ++ snesreader/zlib/inffast.h | 11 + snesreader/zlib/inffixed.h | 94 + snesreader/zlib/inflate.c | 1368 ++++++ snesreader/zlib/inflate.h | 115 + snesreader/zlib/inftrees.c | 329 ++ snesreader/zlib/inftrees.h | 55 + snesreader/zlib/readme.txt | 10 + snesreader/zlib/zconf.h | 335 ++ snesreader/zlib/zlib.h | 1357 ++++++ snesreader/zlib/zlib.txt | 125 + snesreader/zlib/zutil.c | 318 ++ snesreader/zlib/zutil.h | 269 ++ src/nall/{info/snes.hpp => snes/info.hpp} | 4 +- src/snes/Makefile | 2 +- src/snes/interface/interface.hpp | 2 +- src/snes/libsnes/libsnes.cpp | 47 +- src/snes/libsnes/libsnes.hpp | 26 +- src/snes/snes.hpp | 2 +- src/snes/system/system.cpp | 2 +- src/snes/video/video.cpp | 49 +- src/snes/video/video.hpp | 10 +- src/ui_qt/application/init.cpp | 1 - src/ui_qt/interface.cpp | 20 +- src/ui_qt/interface.hpp | 2 +- src/ui_qt/link/filter.cpp | 34 +- src/ui_qt/link/filter.hpp | 7 +- src/ui_qt/ui-base.hpp | 2 +- src/ui_qt/utility/utility.cpp | 8 - src/ui_qt/utility/utility.hpp | 1 - src/ui_qt/utility/window.cpp | 3 - src/ui_sdl/main.cpp | 4 +- supergameboy/Makefile | 126 + supergameboy/cc.bat | 2 + supergameboy/clean.bat | 1 + supergameboy/common/adaptivesleep.cpp | 56 + supergameboy/common/adaptivesleep.h | 34 + supergameboy/common/array.h | 40 + supergameboy/common/rateest.cpp | 96 + supergameboy/common/rateest.h | 73 + supergameboy/common/resample/blackmansinc.h | 100 + .../common/resample/chainresampler.cpp | 118 + supergameboy/common/resample/chainresampler.h | 189 + supergameboy/common/resample/cic2.h | 198 + supergameboy/common/resample/cic3.h | 382 ++ supergameboy/common/resample/cic4.h | 237 + supergameboy/common/resample/convoluter.h | 156 + supergameboy/common/resample/hammingsinc.h | 100 + supergameboy/common/resample/linint.h | 129 + supergameboy/common/resample/makesinckernel.h | 152 + supergameboy/common/resample/rectsinc.h | 99 + supergameboy/common/resample/resampler.h | 43 + .../common/resample/resamplerinfo.cpp | 61 + supergameboy/common/resample/resamplerinfo.h | 36 + supergameboy/common/resample/subresampler.h | 33 + supergameboy/common/resample/u48div.cpp | 54 + supergameboy/common/resample/u48div.h | 24 + supergameboy/common/resample/upsampler.h | 51 + supergameboy/common/ringbuffer.h | 112 + supergameboy/common/usec.h | 31 + supergameboy/interface/interface.cpp | 373 ++ supergameboy/interface/interface.hpp | 80 + supergameboy/libgambatte/SConstruct | 64 + supergameboy/libgambatte/include/filterinfo.h | 32 + supergameboy/libgambatte/include/gambatte.h | 82 + supergameboy/libgambatte/include/inputstate.h | 30 + .../libgambatte/include/inputstategetter.h | 30 + supergameboy/libgambatte/include/int.h | 29 + .../libgambatte/include/videoblitter.h | 44 + supergameboy/libgambatte/src/bitmap_font.cpp | 328 ++ supergameboy/libgambatte/src/bitmap_font.h | 87 + .../libgambatte/src/colorconversion.cpp | 96 + .../libgambatte/src/colorconversion.h | 46 + supergameboy/libgambatte/src/cpu.cpp | 2842 ++++++++++++ supergameboy/libgambatte/src/cpu.h | 115 + supergameboy/libgambatte/src/event_queue.h | 160 + supergameboy/libgambatte/src/file/file.cpp | 73 + supergameboy/libgambatte/src/file/file.h | 42 + .../libgambatte/src/file/file_zip.cpp | 167 + .../libgambatte/src/file/unzip/crypt.h | 132 + .../libgambatte/src/file/unzip/ioapi.c | 177 + .../libgambatte/src/file/unzip/ioapi.h | 75 + .../libgambatte/src/file/unzip/unzip.c | 1605 +++++++ .../libgambatte/src/file/unzip/unzip.h | 354 ++ supergameboy/libgambatte/src/gambatte.cpp | 184 + supergameboy/libgambatte/src/initstate.cpp | 281 ++ supergameboy/libgambatte/src/initstate.h | 26 + supergameboy/libgambatte/src/insertion_sort.h | 51 + supergameboy/libgambatte/src/interrupter.cpp | 44 + supergameboy/libgambatte/src/interrupter.h | 38 + supergameboy/libgambatte/src/memory.cpp | 1867 ++++++++ supergameboy/libgambatte/src/memory.h | 238 + supergameboy/libgambatte/src/osd_element.h | 65 + supergameboy/libgambatte/src/rtc.cpp | 157 + supergameboy/libgambatte/src/rtc.h | 97 + supergameboy/libgambatte/src/savestate.h | 184 + supergameboy/libgambatte/src/sound.cpp | 155 + supergameboy/libgambatte/src/sound.h | 95 + .../libgambatte/src/sound/channel1.cpp | 257 ++ supergameboy/libgambatte/src/sound/channel1.h | 91 + .../libgambatte/src/sound/channel2.cpp | 161 + supergameboy/libgambatte/src/sound/channel2.h | 70 + .../libgambatte/src/sound/channel3.cpp | 207 + supergameboy/libgambatte/src/sound/channel3.h | 100 + .../libgambatte/src/sound/channel4.cpp | 300 ++ supergameboy/libgambatte/src/sound/channel4.h | 99 + .../libgambatte/src/sound/duty_unit.cpp | 148 + .../libgambatte/src/sound/duty_unit.h | 64 + .../libgambatte/src/sound/envelope_unit.cpp | 101 + .../libgambatte/src/sound/envelope_unit.h | 50 + .../libgambatte/src/sound/length_counter.cpp | 87 + .../libgambatte/src/sound/length_counter.h | 44 + .../libgambatte/src/sound/master_disabler.h | 31 + .../libgambatte/src/sound/sound_unit.h | 35 + .../src/sound/static_output_tester.h | 41 + .../libgambatte/src/state_osd_elements.cpp | 169 + .../libgambatte/src/state_osd_elements.h | 29 + supergameboy/libgambatte/src/statesaver.cpp | 407 ++ supergameboy/libgambatte/src/statesaver.h | 37 + supergameboy/libgambatte/src/video.cpp | 1474 +++++++ supergameboy/libgambatte/src/video.h | 293 ++ .../libgambatte/src/video/basic_add_event.cpp | 75 + .../libgambatte/src/video/basic_add_event.h | 56 + .../libgambatte/src/video/break_event.cpp | 35 + .../libgambatte/src/video/break_event.h | 59 + .../src/video/filters/catrom2x.cpp | 194 + .../libgambatte/src/video/filters/catrom2x.h | 40 + .../src/video/filters/catrom3x.cpp | 360 ++ .../libgambatte/src/video/filters/catrom3x.h | 40 + .../libgambatte/src/video/filters/filter.h | 39 + .../src/video/filters/kreed2xsai.cpp | 243 ++ .../src/video/filters/kreed2xsai.h | 40 + .../src/video/filters/maxsthq2x.cpp | 2875 ++++++++++++ .../libgambatte/src/video/filters/maxsthq2x.h | 41 + .../src/video/filters/maxsthq3x.cpp | 3845 +++++++++++++++++ .../libgambatte/src/video/filters/maxsthq3x.h | 40 + .../libgambatte/src/video/irq_event.cpp | 36 + .../libgambatte/src/video/irq_event.h | 52 + .../libgambatte/src/video/ly_counter.cpp | 62 + .../libgambatte/src/video/ly_counter.h | 69 + .../libgambatte/src/video/lyc_irq.cpp | 42 + supergameboy/libgambatte/src/video/lyc_irq.h | 67 + .../libgambatte/src/video/m3_extra_cycles.cpp | 101 + .../libgambatte/src/video/m3_extra_cycles.h | 56 + .../libgambatte/src/video/mode0_irq.cpp | 95 + .../libgambatte/src/video/mode0_irq.h | 42 + .../libgambatte/src/video/mode1_irq.cpp | 33 + .../libgambatte/src/video/mode1_irq.h | 56 + .../libgambatte/src/video/mode2_irq.cpp | 63 + .../libgambatte/src/video/mode2_irq.h | 40 + .../libgambatte/src/video/mode3_event.cpp | 62 + .../libgambatte/src/video/mode3_event.h | 47 + .../libgambatte/src/video/sc_reader.cpp | 62 + .../libgambatte/src/video/sc_reader.h | 77 + .../libgambatte/src/video/scx_reader.cpp | 71 + .../libgambatte/src/video/scx_reader.h | 85 + .../libgambatte/src/video/sprite_mapper.cpp | 187 + .../libgambatte/src/video/sprite_mapper.h | 148 + .../libgambatte/src/video/video_event.h | 50 + .../src/video/video_event_comparer.h | 31 + supergameboy/libgambatte/src/video/we.cpp | 59 + supergameboy/libgambatte/src/video/we.h | 118 + .../src/video/we_master_checker.cpp | 58 + .../libgambatte/src/video/we_master_checker.h | 73 + supergameboy/libgambatte/src/video/window.h | 47 + .../libgambatte/src/video/wx_reader.cpp | 65 + .../libgambatte/src/video/wx_reader.h | 83 + supergameboy/libgambatte/src/video/wy.cpp | 105 + supergameboy/libgambatte/src/video/wy.h | 187 + supergameboy/nall/Makefile | 107 + supergameboy/nall/algorithm.hpp | 23 + supergameboy/nall/any.hpp | 74 + supergameboy/nall/array.hpp | 120 + supergameboy/nall/base64.hpp | 90 + supergameboy/nall/bit.hpp | 51 + supergameboy/nall/concept.hpp | 15 + supergameboy/nall/config.hpp | 123 + supergameboy/nall/crc32.hpp | 66 + supergameboy/nall/detect.hpp | 30 + supergameboy/nall/dictionary.hpp | 75 + supergameboy/nall/dl.hpp | 119 + supergameboy/nall/endian.hpp | 38 + supergameboy/nall/file.hpp | 259 ++ supergameboy/nall/filemap.hpp | 190 + supergameboy/nall/foreach.hpp | 31 + supergameboy/nall/function.hpp | 102 + supergameboy/nall/input.hpp | 386 ++ supergameboy/nall/lzss.hpp | 81 + supergameboy/nall/moduloarray.hpp | 40 + supergameboy/nall/platform.hpp | 80 + supergameboy/nall/priorityqueue.hpp | 109 + supergameboy/nall/property.hpp | 91 + supergameboy/nall/qt/Makefile | 55 + supergameboy/nall/qt/check-action.moc.hpp | 41 + supergameboy/nall/qt/concept.hpp | 10 + supergameboy/nall/qt/file-dialog.moc.hpp | 392 ++ supergameboy/nall/qt/hex-editor.moc.hpp | 173 + supergameboy/nall/qt/radio-action.moc.hpp | 41 + supergameboy/nall/qt/window.moc.hpp | 105 + supergameboy/nall/serial.hpp | 80 + supergameboy/nall/serializer.hpp | 145 + supergameboy/nall/sha256.hpp | 143 + supergameboy/nall/sort.hpp | 62 + supergameboy/nall/static.hpp | 20 + supergameboy/nall/stdint.hpp | 44 + supergameboy/nall/string.hpp | 27 + supergameboy/nall/string/base.hpp | 136 + supergameboy/nall/string/cast.hpp | 32 + supergameboy/nall/string/compare.hpp | 72 + supergameboy/nall/string/convert.hpp | 153 + supergameboy/nall/string/core.hpp | 133 + supergameboy/nall/string/filename.hpp | 61 + supergameboy/nall/string/match.hpp | 76 + supergameboy/nall/string/math.hpp | 164 + supergameboy/nall/string/replace.hpp | 103 + supergameboy/nall/string/split.hpp | 56 + supergameboy/nall/string/strl.hpp | 52 + supergameboy/nall/string/strpos.hpp | 60 + supergameboy/nall/string/trim.hpp | 54 + supergameboy/nall/string/utility.hpp | 169 + supergameboy/nall/string/variadic.hpp | 27 + supergameboy/nall/string/xml.hpp | 265 ++ supergameboy/nall/ups.hpp | 190 + supergameboy/nall/utf8.hpp | 72 + supergameboy/nall/utility.hpp | 30 + supergameboy/nall/varint.hpp | 92 + supergameboy/nall/vector.hpp | 240 + supergameboy/supergameboy.cpp | 68 + supergameboy/supergameboy.hpp | 32 + supergameboy/sync.sh | 2 + 505 files changed, 75742 insertions(+), 119 deletions(-) delete mode 100644 bsnes.exe delete mode 100644 snesfilter.dll create mode 100644 snesfilter/2xsai/2xsai.cpp create mode 100644 snesfilter/2xsai/2xsai.hpp create mode 100644 snesfilter/2xsai/implementation.cpp create mode 100644 snesfilter/Makefile create mode 100644 snesfilter/cc.bat create mode 100644 snesfilter/clean.bat create mode 100644 snesfilter/direct/direct.cpp create mode 100644 snesfilter/direct/direct.hpp create mode 100644 snesfilter/hq2x/hq2x.cpp create mode 100644 snesfilter/hq2x/hq2x.hpp create mode 100644 snesfilter/lq2x/lq2x.cpp create mode 100644 snesfilter/lq2x/lq2x.hpp create mode 100644 snesfilter/nall/Makefile create mode 100644 snesfilter/nall/algorithm.hpp create mode 100644 snesfilter/nall/any.hpp create mode 100644 snesfilter/nall/array.hpp create mode 100644 snesfilter/nall/base64.hpp create mode 100644 snesfilter/nall/bit.hpp create mode 100644 snesfilter/nall/concept.hpp create mode 100644 snesfilter/nall/config.hpp create mode 100644 snesfilter/nall/crc32.hpp create mode 100644 snesfilter/nall/detect.hpp create mode 100644 snesfilter/nall/dictionary.hpp create mode 100644 snesfilter/nall/dl.hpp create mode 100644 snesfilter/nall/endian.hpp create mode 100644 snesfilter/nall/file.hpp create mode 100644 snesfilter/nall/filemap.hpp create mode 100644 snesfilter/nall/foreach.hpp create mode 100644 snesfilter/nall/function.hpp create mode 100644 snesfilter/nall/input.hpp create mode 100644 snesfilter/nall/lzss.hpp create mode 100644 snesfilter/nall/moduloarray.hpp create mode 100644 snesfilter/nall/platform.hpp create mode 100644 snesfilter/nall/priorityqueue.hpp create mode 100644 snesfilter/nall/property.hpp create mode 100644 snesfilter/nall/qt/Makefile create mode 100644 snesfilter/nall/qt/check-action.moc.hpp create mode 100644 snesfilter/nall/qt/concept.hpp create mode 100644 snesfilter/nall/qt/file-dialog.moc.hpp create mode 100644 snesfilter/nall/qt/hex-editor.moc.hpp create mode 100644 snesfilter/nall/qt/radio-action.moc.hpp create mode 100644 snesfilter/nall/qt/window.moc.hpp create mode 100644 snesfilter/nall/serial.hpp create mode 100644 snesfilter/nall/serializer.hpp create mode 100644 snesfilter/nall/sha256.hpp create mode 100644 snesfilter/nall/sort.hpp create mode 100644 snesfilter/nall/static.hpp create mode 100644 snesfilter/nall/stdint.hpp create mode 100644 snesfilter/nall/string.hpp create mode 100644 snesfilter/nall/string/base.hpp create mode 100644 snesfilter/nall/string/cast.hpp create mode 100644 snesfilter/nall/string/compare.hpp create mode 100644 snesfilter/nall/string/convert.hpp create mode 100644 snesfilter/nall/string/core.hpp create mode 100644 snesfilter/nall/string/filename.hpp create mode 100644 snesfilter/nall/string/match.hpp create mode 100644 snesfilter/nall/string/math.hpp create mode 100644 snesfilter/nall/string/replace.hpp create mode 100644 snesfilter/nall/string/split.hpp create mode 100644 snesfilter/nall/string/strl.hpp create mode 100644 snesfilter/nall/string/strpos.hpp create mode 100644 snesfilter/nall/string/trim.hpp create mode 100644 snesfilter/nall/string/utility.hpp create mode 100644 snesfilter/nall/string/variadic.hpp create mode 100644 snesfilter/nall/string/xml.hpp create mode 100644 snesfilter/nall/ups.hpp create mode 100644 snesfilter/nall/utf8.hpp create mode 100644 snesfilter/nall/utility.hpp create mode 100644 snesfilter/nall/varint.hpp create mode 100644 snesfilter/nall/vector.hpp create mode 100644 snesfilter/ntsc/ntsc.cpp create mode 100644 snesfilter/ntsc/ntsc.moc.hpp create mode 100644 snesfilter/ntsc/snes_ntsc/snes_ntsc.c create mode 100644 snesfilter/ntsc/snes_ntsc/snes_ntsc.h create mode 100644 snesfilter/ntsc/snes_ntsc/snes_ntsc_config.h create mode 100644 snesfilter/ntsc/snes_ntsc/snes_ntsc_impl.h create mode 100644 snesfilter/pixellate2x/pixellate2x.cpp create mode 100644 snesfilter/pixellate2x/pixellate2x.hpp create mode 100644 snesfilter/scale2x/scale2x.cpp create mode 100644 snesfilter/scale2x/scale2x.hpp create mode 100644 snesfilter/snesfilter.cpp create mode 100644 snesfilter/snesfilter.hpp create mode 100644 snesfilter/sync.sh delete mode 100644 snesreader.dll create mode 100644 snesreader/7z_C/7zAlloc.c create mode 100644 snesreader/7z_C/7zAlloc.h create mode 100644 snesreader/7z_C/7zBuf.c create mode 100644 snesreader/7z_C/7zBuf.h create mode 100644 snesreader/7z_C/7zC.txt create mode 100644 snesreader/7z_C/7zCrc.c create mode 100644 snesreader/7z_C/7zCrc.h create mode 100644 snesreader/7z_C/7zDecode.c create mode 100644 snesreader/7z_C/7zDecode.h create mode 100644 snesreader/7z_C/7zExtract.c create mode 100644 snesreader/7z_C/7zExtract.h create mode 100644 snesreader/7z_C/7zHeader.c create mode 100644 snesreader/7z_C/7zHeader.h create mode 100644 snesreader/7z_C/7zIn.c create mode 100644 snesreader/7z_C/7zIn.h create mode 100644 snesreader/7z_C/7zItem.c create mode 100644 snesreader/7z_C/7zItem.h create mode 100644 snesreader/7z_C/7zStream.c create mode 100644 snesreader/7z_C/Bcj2.c create mode 100644 snesreader/7z_C/Bcj2.h create mode 100644 snesreader/7z_C/Bra.h create mode 100644 snesreader/7z_C/Bra86.c create mode 100644 snesreader/7z_C/CpuArch.h create mode 100644 snesreader/7z_C/LzmaDec.c create mode 100644 snesreader/7z_C/LzmaDec.h create mode 100644 snesreader/7z_C/Types.h create mode 100644 snesreader/7z_C/lzma.txt create mode 100644 snesreader/7z_C/readme.txt create mode 100644 snesreader/Makefile create mode 100644 snesreader/cc.bat create mode 100644 snesreader/clean.bat create mode 100644 snesreader/fex/Binary_Extractor.cpp create mode 100644 snesreader/fex/Binary_Extractor.h create mode 100644 snesreader/fex/Data_Reader.cpp create mode 100644 snesreader/fex/Data_Reader.h create mode 100644 snesreader/fex/File_Extractor.cpp create mode 100644 snesreader/fex/File_Extractor.h create mode 100644 snesreader/fex/Gzip_Extractor.cpp create mode 100644 snesreader/fex/Gzip_Extractor.h create mode 100644 snesreader/fex/Gzip_Reader.cpp create mode 100644 snesreader/fex/Gzip_Reader.h create mode 100644 snesreader/fex/Rar_Extractor.cpp create mode 100644 snesreader/fex/Rar_Extractor.h create mode 100644 snesreader/fex/Zip7_Extractor.cpp create mode 100644 snesreader/fex/Zip7_Extractor.h create mode 100644 snesreader/fex/Zip_Extractor.cpp create mode 100644 snesreader/fex/Zip_Extractor.h create mode 100644 snesreader/fex/Zlib_Inflater.cpp create mode 100644 snesreader/fex/Zlib_Inflater.h create mode 100644 snesreader/fex/blargg_common.cpp create mode 100644 snesreader/fex/blargg_common.h create mode 100644 snesreader/fex/blargg_config.h create mode 100644 snesreader/fex/blargg_endian.h create mode 100644 snesreader/fex/blargg_errors.cpp create mode 100644 snesreader/fex/blargg_errors.h create mode 100644 snesreader/fex/blargg_source.h create mode 100644 snesreader/fex/fex.cpp create mode 100644 snesreader/fex/fex.h create mode 100644 snesreader/filechooser.cpp create mode 100644 snesreader/filechooser.moc.hpp create mode 100644 snesreader/libjma/7z.h create mode 100644 snesreader/libjma/7zlzma.cpp create mode 100644 snesreader/libjma/aribitcd.h create mode 100644 snesreader/libjma/ariconst.h create mode 100644 snesreader/libjma/ariprice.h create mode 100644 snesreader/libjma/btreecd.h create mode 100644 snesreader/libjma/crc32.h create mode 100644 snesreader/libjma/iiostrm.cpp create mode 100644 snesreader/libjma/iiostrm.h create mode 100644 snesreader/libjma/inbyte.cpp create mode 100644 snesreader/libjma/inbyte.h create mode 100644 snesreader/libjma/jcrc32.cpp create mode 100644 snesreader/libjma/jma.cpp create mode 100644 snesreader/libjma/jma.h create mode 100644 snesreader/libjma/lencoder.h create mode 100644 snesreader/libjma/litcoder.h create mode 100644 snesreader/libjma/lzma.cpp create mode 100644 snesreader/libjma/lzma.h create mode 100644 snesreader/libjma/lzmadec.h create mode 100644 snesreader/libjma/lzmadecode.cpp create mode 100644 snesreader/libjma/portable.h create mode 100644 snesreader/libjma/rcdefs.h create mode 100644 snesreader/libjma/rngcoder.h create mode 100644 snesreader/libjma/winout.cpp create mode 100644 snesreader/libjma/winout.h create mode 100644 snesreader/micro-bunzip/micro-bunzip.c create mode 100644 snesreader/nall/Makefile create mode 100644 snesreader/nall/algorithm.hpp create mode 100644 snesreader/nall/any.hpp create mode 100644 snesreader/nall/array.hpp create mode 100644 snesreader/nall/base64.hpp create mode 100644 snesreader/nall/bit.hpp create mode 100644 snesreader/nall/concept.hpp create mode 100644 snesreader/nall/config.hpp create mode 100644 snesreader/nall/crc32.hpp create mode 100644 snesreader/nall/detect.hpp create mode 100644 snesreader/nall/dictionary.hpp create mode 100644 snesreader/nall/dl.hpp create mode 100644 snesreader/nall/endian.hpp create mode 100644 snesreader/nall/file.hpp create mode 100644 snesreader/nall/filemap.hpp create mode 100644 snesreader/nall/foreach.hpp create mode 100644 snesreader/nall/function.hpp create mode 100644 snesreader/nall/input.hpp create mode 100644 snesreader/nall/lzss.hpp create mode 100644 snesreader/nall/moduloarray.hpp create mode 100644 snesreader/nall/platform.hpp create mode 100644 snesreader/nall/priorityqueue.hpp create mode 100644 snesreader/nall/property.hpp create mode 100644 snesreader/nall/qt/Makefile create mode 100644 snesreader/nall/qt/check-action.moc.hpp create mode 100644 snesreader/nall/qt/concept.hpp create mode 100644 snesreader/nall/qt/file-dialog.moc.hpp create mode 100644 snesreader/nall/qt/hex-editor.moc.hpp create mode 100644 snesreader/nall/qt/radio-action.moc.hpp create mode 100644 snesreader/nall/qt/window.moc.hpp create mode 100644 snesreader/nall/serial.hpp create mode 100644 snesreader/nall/serializer.hpp create mode 100644 snesreader/nall/sha256.hpp create mode 100644 snesreader/nall/sort.hpp create mode 100644 snesreader/nall/static.hpp create mode 100644 snesreader/nall/stdint.hpp create mode 100644 snesreader/nall/string.hpp create mode 100644 snesreader/nall/string/base.hpp create mode 100644 snesreader/nall/string/cast.hpp create mode 100644 snesreader/nall/string/compare.hpp create mode 100644 snesreader/nall/string/convert.hpp create mode 100644 snesreader/nall/string/core.hpp create mode 100644 snesreader/nall/string/filename.hpp create mode 100644 snesreader/nall/string/match.hpp create mode 100644 snesreader/nall/string/math.hpp create mode 100644 snesreader/nall/string/replace.hpp create mode 100644 snesreader/nall/string/split.hpp create mode 100644 snesreader/nall/string/strl.hpp create mode 100644 snesreader/nall/string/strpos.hpp create mode 100644 snesreader/nall/string/trim.hpp create mode 100644 snesreader/nall/string/utility.hpp create mode 100644 snesreader/nall/string/variadic.hpp create mode 100644 snesreader/nall/string/xml.hpp create mode 100644 snesreader/nall/ups.hpp create mode 100644 snesreader/nall/utf8.hpp create mode 100644 snesreader/nall/utility.hpp create mode 100644 snesreader/nall/varint.hpp create mode 100644 snesreader/nall/vector.hpp create mode 100644 snesreader/snesreader.cpp create mode 100644 snesreader/snesreader.hpp create mode 100644 snesreader/sync.sh create mode 100644 snesreader/unrar/archive.cpp create mode 100644 snesreader/unrar/archive.hpp create mode 100644 snesreader/unrar/arcread.cpp create mode 100644 snesreader/unrar/array.hpp create mode 100644 snesreader/unrar/changes.txt create mode 100644 snesreader/unrar/coder.cpp create mode 100644 snesreader/unrar/coder.hpp create mode 100644 snesreader/unrar/compress.hpp create mode 100644 snesreader/unrar/crc.cpp create mode 100644 snesreader/unrar/encname.cpp create mode 100644 snesreader/unrar/encname.hpp create mode 100644 snesreader/unrar/extract.cpp create mode 100644 snesreader/unrar/getbits.cpp create mode 100644 snesreader/unrar/getbits.hpp create mode 100644 snesreader/unrar/headers.hpp create mode 100644 snesreader/unrar/license.txt create mode 100644 snesreader/unrar/model.cpp create mode 100644 snesreader/unrar/model.hpp create mode 100644 snesreader/unrar/rar.hpp create mode 100644 snesreader/unrar/rarvm.cpp create mode 100644 snesreader/unrar/rarvm.hpp create mode 100644 snesreader/unrar/rarvmtbl.cpp create mode 100644 snesreader/unrar/rawread.cpp create mode 100644 snesreader/unrar/rawread.hpp create mode 100644 snesreader/unrar/readme.txt create mode 100644 snesreader/unrar/suballoc.cpp create mode 100644 snesreader/unrar/suballoc.hpp create mode 100644 snesreader/unrar/technote.txt create mode 100644 snesreader/unrar/unicode.cpp create mode 100644 snesreader/unrar/unicode.hpp create mode 100644 snesreader/unrar/unpack.cpp create mode 100644 snesreader/unrar/unpack.hpp create mode 100644 snesreader/unrar/unpack15.cpp create mode 100644 snesreader/unrar/unpack20.cpp create mode 100644 snesreader/unrar/unrar.cpp create mode 100644 snesreader/unrar/unrar.h create mode 100644 snesreader/unrar/unrar_misc.cpp create mode 100644 snesreader/unrar/unrar_open.cpp create mode 100644 snesreader/unrar/whatsnew.txt create mode 100644 snesreader/zlib/adler32.c create mode 100644 snesreader/zlib/crc32.c create mode 100644 snesreader/zlib/crc32.h create mode 100644 snesreader/zlib/inffast.c create mode 100644 snesreader/zlib/inffast.h create mode 100644 snesreader/zlib/inffixed.h create mode 100644 snesreader/zlib/inflate.c create mode 100644 snesreader/zlib/inflate.h create mode 100644 snesreader/zlib/inftrees.c create mode 100644 snesreader/zlib/inftrees.h create mode 100644 snesreader/zlib/readme.txt create mode 100644 snesreader/zlib/zconf.h create mode 100644 snesreader/zlib/zlib.h create mode 100644 snesreader/zlib/zlib.txt create mode 100644 snesreader/zlib/zutil.c create mode 100644 snesreader/zlib/zutil.h rename src/nall/{info/snes.hpp => snes/info.hpp} (99%) create mode 100644 supergameboy/Makefile create mode 100644 supergameboy/cc.bat create mode 100644 supergameboy/clean.bat create mode 100644 supergameboy/common/adaptivesleep.cpp create mode 100644 supergameboy/common/adaptivesleep.h create mode 100644 supergameboy/common/array.h create mode 100644 supergameboy/common/rateest.cpp create mode 100644 supergameboy/common/rateest.h create mode 100644 supergameboy/common/resample/blackmansinc.h create mode 100644 supergameboy/common/resample/chainresampler.cpp create mode 100644 supergameboy/common/resample/chainresampler.h create mode 100644 supergameboy/common/resample/cic2.h create mode 100644 supergameboy/common/resample/cic3.h create mode 100644 supergameboy/common/resample/cic4.h create mode 100644 supergameboy/common/resample/convoluter.h create mode 100644 supergameboy/common/resample/hammingsinc.h create mode 100644 supergameboy/common/resample/linint.h create mode 100644 supergameboy/common/resample/makesinckernel.h create mode 100644 supergameboy/common/resample/rectsinc.h create mode 100644 supergameboy/common/resample/resampler.h create mode 100644 supergameboy/common/resample/resamplerinfo.cpp create mode 100644 supergameboy/common/resample/resamplerinfo.h create mode 100644 supergameboy/common/resample/subresampler.h create mode 100644 supergameboy/common/resample/u48div.cpp create mode 100644 supergameboy/common/resample/u48div.h create mode 100644 supergameboy/common/resample/upsampler.h create mode 100644 supergameboy/common/ringbuffer.h create mode 100644 supergameboy/common/usec.h create mode 100644 supergameboy/interface/interface.cpp create mode 100644 supergameboy/interface/interface.hpp create mode 100644 supergameboy/libgambatte/SConstruct create mode 100644 supergameboy/libgambatte/include/filterinfo.h create mode 100644 supergameboy/libgambatte/include/gambatte.h create mode 100644 supergameboy/libgambatte/include/inputstate.h create mode 100644 supergameboy/libgambatte/include/inputstategetter.h create mode 100644 supergameboy/libgambatte/include/int.h create mode 100644 supergameboy/libgambatte/include/videoblitter.h create mode 100644 supergameboy/libgambatte/src/bitmap_font.cpp create mode 100644 supergameboy/libgambatte/src/bitmap_font.h create mode 100644 supergameboy/libgambatte/src/colorconversion.cpp create mode 100644 supergameboy/libgambatte/src/colorconversion.h create mode 100644 supergameboy/libgambatte/src/cpu.cpp create mode 100644 supergameboy/libgambatte/src/cpu.h create mode 100644 supergameboy/libgambatte/src/event_queue.h create mode 100644 supergameboy/libgambatte/src/file/file.cpp create mode 100644 supergameboy/libgambatte/src/file/file.h create mode 100644 supergameboy/libgambatte/src/file/file_zip.cpp create mode 100644 supergameboy/libgambatte/src/file/unzip/crypt.h create mode 100644 supergameboy/libgambatte/src/file/unzip/ioapi.c create mode 100644 supergameboy/libgambatte/src/file/unzip/ioapi.h create mode 100644 supergameboy/libgambatte/src/file/unzip/unzip.c create mode 100644 supergameboy/libgambatte/src/file/unzip/unzip.h create mode 100644 supergameboy/libgambatte/src/gambatte.cpp create mode 100644 supergameboy/libgambatte/src/initstate.cpp create mode 100644 supergameboy/libgambatte/src/initstate.h create mode 100644 supergameboy/libgambatte/src/insertion_sort.h create mode 100644 supergameboy/libgambatte/src/interrupter.cpp create mode 100644 supergameboy/libgambatte/src/interrupter.h create mode 100644 supergameboy/libgambatte/src/memory.cpp create mode 100644 supergameboy/libgambatte/src/memory.h create mode 100644 supergameboy/libgambatte/src/osd_element.h create mode 100644 supergameboy/libgambatte/src/rtc.cpp create mode 100644 supergameboy/libgambatte/src/rtc.h create mode 100644 supergameboy/libgambatte/src/savestate.h create mode 100644 supergameboy/libgambatte/src/sound.cpp create mode 100644 supergameboy/libgambatte/src/sound.h create mode 100644 supergameboy/libgambatte/src/sound/channel1.cpp create mode 100644 supergameboy/libgambatte/src/sound/channel1.h create mode 100644 supergameboy/libgambatte/src/sound/channel2.cpp create mode 100644 supergameboy/libgambatte/src/sound/channel2.h create mode 100644 supergameboy/libgambatte/src/sound/channel3.cpp create mode 100644 supergameboy/libgambatte/src/sound/channel3.h create mode 100644 supergameboy/libgambatte/src/sound/channel4.cpp create mode 100644 supergameboy/libgambatte/src/sound/channel4.h create mode 100644 supergameboy/libgambatte/src/sound/duty_unit.cpp create mode 100644 supergameboy/libgambatte/src/sound/duty_unit.h create mode 100644 supergameboy/libgambatte/src/sound/envelope_unit.cpp create mode 100644 supergameboy/libgambatte/src/sound/envelope_unit.h create mode 100644 supergameboy/libgambatte/src/sound/length_counter.cpp create mode 100644 supergameboy/libgambatte/src/sound/length_counter.h create mode 100644 supergameboy/libgambatte/src/sound/master_disabler.h create mode 100644 supergameboy/libgambatte/src/sound/sound_unit.h create mode 100644 supergameboy/libgambatte/src/sound/static_output_tester.h create mode 100644 supergameboy/libgambatte/src/state_osd_elements.cpp create mode 100644 supergameboy/libgambatte/src/state_osd_elements.h create mode 100644 supergameboy/libgambatte/src/statesaver.cpp create mode 100644 supergameboy/libgambatte/src/statesaver.h create mode 100644 supergameboy/libgambatte/src/video.cpp create mode 100644 supergameboy/libgambatte/src/video.h create mode 100644 supergameboy/libgambatte/src/video/basic_add_event.cpp create mode 100644 supergameboy/libgambatte/src/video/basic_add_event.h create mode 100644 supergameboy/libgambatte/src/video/break_event.cpp create mode 100644 supergameboy/libgambatte/src/video/break_event.h create mode 100644 supergameboy/libgambatte/src/video/filters/catrom2x.cpp create mode 100644 supergameboy/libgambatte/src/video/filters/catrom2x.h create mode 100644 supergameboy/libgambatte/src/video/filters/catrom3x.cpp create mode 100644 supergameboy/libgambatte/src/video/filters/catrom3x.h create mode 100644 supergameboy/libgambatte/src/video/filters/filter.h create mode 100644 supergameboy/libgambatte/src/video/filters/kreed2xsai.cpp create mode 100644 supergameboy/libgambatte/src/video/filters/kreed2xsai.h create mode 100644 supergameboy/libgambatte/src/video/filters/maxsthq2x.cpp create mode 100644 supergameboy/libgambatte/src/video/filters/maxsthq2x.h create mode 100644 supergameboy/libgambatte/src/video/filters/maxsthq3x.cpp create mode 100644 supergameboy/libgambatte/src/video/filters/maxsthq3x.h create mode 100644 supergameboy/libgambatte/src/video/irq_event.cpp create mode 100644 supergameboy/libgambatte/src/video/irq_event.h create mode 100644 supergameboy/libgambatte/src/video/ly_counter.cpp create mode 100644 supergameboy/libgambatte/src/video/ly_counter.h create mode 100644 supergameboy/libgambatte/src/video/lyc_irq.cpp create mode 100644 supergameboy/libgambatte/src/video/lyc_irq.h create mode 100644 supergameboy/libgambatte/src/video/m3_extra_cycles.cpp create mode 100644 supergameboy/libgambatte/src/video/m3_extra_cycles.h create mode 100644 supergameboy/libgambatte/src/video/mode0_irq.cpp create mode 100644 supergameboy/libgambatte/src/video/mode0_irq.h create mode 100644 supergameboy/libgambatte/src/video/mode1_irq.cpp create mode 100644 supergameboy/libgambatte/src/video/mode1_irq.h create mode 100644 supergameboy/libgambatte/src/video/mode2_irq.cpp create mode 100644 supergameboy/libgambatte/src/video/mode2_irq.h create mode 100644 supergameboy/libgambatte/src/video/mode3_event.cpp create mode 100644 supergameboy/libgambatte/src/video/mode3_event.h create mode 100644 supergameboy/libgambatte/src/video/sc_reader.cpp create mode 100644 supergameboy/libgambatte/src/video/sc_reader.h create mode 100644 supergameboy/libgambatte/src/video/scx_reader.cpp create mode 100644 supergameboy/libgambatte/src/video/scx_reader.h create mode 100644 supergameboy/libgambatte/src/video/sprite_mapper.cpp create mode 100644 supergameboy/libgambatte/src/video/sprite_mapper.h create mode 100644 supergameboy/libgambatte/src/video/video_event.h create mode 100644 supergameboy/libgambatte/src/video/video_event_comparer.h create mode 100644 supergameboy/libgambatte/src/video/we.cpp create mode 100644 supergameboy/libgambatte/src/video/we.h create mode 100644 supergameboy/libgambatte/src/video/we_master_checker.cpp create mode 100644 supergameboy/libgambatte/src/video/we_master_checker.h create mode 100644 supergameboy/libgambatte/src/video/window.h create mode 100644 supergameboy/libgambatte/src/video/wx_reader.cpp create mode 100644 supergameboy/libgambatte/src/video/wx_reader.h create mode 100644 supergameboy/libgambatte/src/video/wy.cpp create mode 100644 supergameboy/libgambatte/src/video/wy.h create mode 100644 supergameboy/nall/Makefile create mode 100644 supergameboy/nall/algorithm.hpp create mode 100644 supergameboy/nall/any.hpp create mode 100644 supergameboy/nall/array.hpp create mode 100644 supergameboy/nall/base64.hpp create mode 100644 supergameboy/nall/bit.hpp create mode 100644 supergameboy/nall/concept.hpp create mode 100644 supergameboy/nall/config.hpp create mode 100644 supergameboy/nall/crc32.hpp create mode 100644 supergameboy/nall/detect.hpp create mode 100644 supergameboy/nall/dictionary.hpp create mode 100644 supergameboy/nall/dl.hpp create mode 100644 supergameboy/nall/endian.hpp create mode 100644 supergameboy/nall/file.hpp create mode 100644 supergameboy/nall/filemap.hpp create mode 100644 supergameboy/nall/foreach.hpp create mode 100644 supergameboy/nall/function.hpp create mode 100644 supergameboy/nall/input.hpp create mode 100644 supergameboy/nall/lzss.hpp create mode 100644 supergameboy/nall/moduloarray.hpp create mode 100644 supergameboy/nall/platform.hpp create mode 100644 supergameboy/nall/priorityqueue.hpp create mode 100644 supergameboy/nall/property.hpp create mode 100644 supergameboy/nall/qt/Makefile create mode 100644 supergameboy/nall/qt/check-action.moc.hpp create mode 100644 supergameboy/nall/qt/concept.hpp create mode 100644 supergameboy/nall/qt/file-dialog.moc.hpp create mode 100644 supergameboy/nall/qt/hex-editor.moc.hpp create mode 100644 supergameboy/nall/qt/radio-action.moc.hpp create mode 100644 supergameboy/nall/qt/window.moc.hpp create mode 100644 supergameboy/nall/serial.hpp create mode 100644 supergameboy/nall/serializer.hpp create mode 100644 supergameboy/nall/sha256.hpp create mode 100644 supergameboy/nall/sort.hpp create mode 100644 supergameboy/nall/static.hpp create mode 100644 supergameboy/nall/stdint.hpp create mode 100644 supergameboy/nall/string.hpp create mode 100644 supergameboy/nall/string/base.hpp create mode 100644 supergameboy/nall/string/cast.hpp create mode 100644 supergameboy/nall/string/compare.hpp create mode 100644 supergameboy/nall/string/convert.hpp create mode 100644 supergameboy/nall/string/core.hpp create mode 100644 supergameboy/nall/string/filename.hpp create mode 100644 supergameboy/nall/string/match.hpp create mode 100644 supergameboy/nall/string/math.hpp create mode 100644 supergameboy/nall/string/replace.hpp create mode 100644 supergameboy/nall/string/split.hpp create mode 100644 supergameboy/nall/string/strl.hpp create mode 100644 supergameboy/nall/string/strpos.hpp create mode 100644 supergameboy/nall/string/trim.hpp create mode 100644 supergameboy/nall/string/utility.hpp create mode 100644 supergameboy/nall/string/variadic.hpp create mode 100644 supergameboy/nall/string/xml.hpp create mode 100644 supergameboy/nall/ups.hpp create mode 100644 supergameboy/nall/utf8.hpp create mode 100644 supergameboy/nall/utility.hpp create mode 100644 supergameboy/nall/varint.hpp create mode 100644 supergameboy/nall/vector.hpp create mode 100644 supergameboy/supergameboy.cpp create mode 100644 supergameboy/supergameboy.hpp create mode 100644 supergameboy/sync.sh diff --git a/bsnes.exe b/bsnes.exe deleted file mode 100644 index f5bcdd7302131be656db3ac7c4634d99101a61ae..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 690176 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 S>UwZeu zS@|GxV%HZPtt7&1bf4uQ!@*`7fpMPVDQ8REh}PIW=uBOm@Ha-tvBlG)wc&d~3h{#tJs1_WDbh^$p=WW8X*RzQ;ttz#@@qgp1mt zZU`62WXO&6??`BAqol$HL+SW2NL-vmB)1wcod0^Er)x^3V6m2}j1kAeF5 zTYiZ#%_&nAil+#y0$(o(yx@t;4f&-m%eayzm3cwHgHL$?aM#yF=e3?GCg$(Nmd<#t zzU^RdpTlmQt|^l-`>V83rQ$vzb10=^fE?w~(2RK$i$=bhJ-x6zlvZVX zRMOMm4(T%t(iPppqKLW#UHg09FokkV)+?l--@&Bf;?U=DB1ODY65O$3PB@#C%MZUO zTc5v%x8u=9gm&2Ms-|2v#ONjOrSM1(5%3`&4)4@rjZ@U> zbG`RnT&O1I@3Ad7qxi6fp@)wlWqY%8;fRgqb|>9(vh8(*M{4oc7)dY_`C~a+KrqpY z&puh79vRB@SMA3y9_EHg;^iQPW~l^8Xbg1SF+N>GdwoK7LQi5`E8W+8>DlUh%TvC^ zt+u5_cuKc2NXBvlakszp(;T-;!oN1LYjVZ5h~e*RT7Fvats zc^;~JOA!OmzRBPOT|U2t4EwYlejla8Mal$xu1<GRlOgs!r-zrBP6NMV&$9D=g{N=}_c1CpQ=6UH9Jw1w?iXg z^VcGC#b=w%&`ztEYbHnHahHNcl#Z|Zza=XPA;U&sk{I_KhOSE$^YYE^B1LzRKWSPkIj)@JQtMu-|nX2&5fB- ziQEa8oBR?(0tjHGPt3&QH`fF{zG+B!yd@>W6XOE(Njc0dMUpAy?_&n3A;+DSN^Vvl zvn~ZL39Ki85N~_Hhr5YhwQt?7M zn8IDfgH?4FX%RSjOY5La`-h|B?1prF%xr5#UBG2LZ`GXgB9Oux?Nad)4|!#`QI7(L z97A-F0YU>dq1WhLmO)BZ8>=rUkYSd=I{#c06?6%>q3xd zy}@%Vj3}`5A5Oj<7UTvpie=puROigR>e`5c@@H~#; z=$QD)EPa_05E&GmE-9f463k*ceH)U^XMhfCimiy?(I zF2iWXh*=SiUm8+;Nrs;8`TiFl1CrGl(!5z~T0Y`L{Zp;oUg9AN7ezI_>bffEdi8p( zcz^E(yqIoUp|qxwC~kQSx+Ie<2Uu#Y5$q1`7O7?W%`Hhky+`|UBZN>b^cyc=)a}X7 z`-`dWKRfdZaBN+2K{%bxKq?W=_MoP)y?xns{!DICAHUO2R3Sy~$FD1Mqv1U3%65p@ zc-yrhxPGuE;czkosuvGI1^OFc^s-Rs-3yOI7C=u4vCXU~G z8PaOv1X$RA^-Djc7i{s)Smz+}!ci?Xr{UBPLEHu}TS=0VxEm8NS>2_mVOIt0$#eKB zvBNU>a2~D@)}swAkK^!3NXO5KX{(z?Q%zndc=->Ac!5u~HIP@6j#$un`I3+}Xj4@+ zok@te*^}pT>fnq@D!q7|r^Z0!z?aYI9%PvY;G52imcQ*LHvPd)d3e~jyOUYr$h@a( z3U(r9HiK86V|3Rh6q=BF@ertvgRDcEnJoH#e0De0AcLx&t$Y+0=EFfDHNoD!{!{vi z!Cu`mqTH=CR234T|J-XF`|tPi8*s)5d5rIW#LUv2q;9w?p$+sg@Etl*?m zoNdw>+gQ=J%7hjnO26RIEvY+3A7g~5OBe8|RT`{c%IBB7kf|9dHOneJH8MgKtWRhr z@_SEdMX_uMn~5AUM7*9$546*)jbENUC`^)FkhKKE4z5Joqvefq&8q6*Tr;mJeU?{X zm2&P|qDVtXE;eYtsHTpH5ygsPOp2bHw$Dt80+mJ8e6DZ{V1znSKzxlNqM;>@hcS|( z7)<5WljQwFqQaHWXR&HNDTw^|1ivp#nj-S9*LQAyOIf@vE^ABs3ut% z^Te0+f(wJOie^K-GT|su5nWI9qWI&d?qGBAaDR>Jj5j=w(^X?qgJYx^C8Ug;cHd;^ zS8l$P+t`~aFQgr?$EWm9fu2?Kv{KZ05hXO|3T1~{@NW(>I z%JTh>z1uw5k}{O-rzZ$vJUz9>Aw?1cmu-Q24unV^fv!)5Y5Kn<%L&Tl3o-UJnfbRJ z%_=jVQ!K!CuH=Uc(j00zUBWXVxaYAm#Z<{+(dQSA9n6)4s{NGeL){Ged4A=FbRUkk zPL@0b5h3fqI5SqZO8p(Y%S_&BSsvY@T zf%HcMAqD+h3pvyZ$Lojqj526<3AePgxolI@wqx~j!xhRfm!|dt7_=9fveT{@E(xw1 zdJ`w~ugcBo5!&oBY2Pp6pkYk0isod2k42ATB)Va}SE6nEf^G6PwPArDQ< zxMR$%#VB(k;005<-2DTyqt<>H_%XE4^`;#grV}>;)s(pjXOsX&dKQZ;EE#yxMPyIb zmCS@hnSK?Ji%xQ(>ZyWZDN2X!H)hA?dz!&*Gdr{@QuFQ1M*Uq$9!BQ-^lYT?!`i#8 z0xjE`@qxZ>*p6sg!T8~#&4+yLTa>Nu@AQt(zA}-S^ zEhS`JF6a+LLOfF<_RGs}fM3|41A)!I)FAYngC}JCPo(MX{S-IU^Y-h@i#E7sE;BMr zaYCaFUKzd4W}>ZbAom2F_8;? z=(&B->bsA?ML)xEB}Bng3u4naO6ahXy!wT$IW9ro(W%FOjsk2%r5GFvIojxHUF9Br zdsV2IG^h*mtQL8OJdmdkt8(D0R1U_HqSwN#GOC3&Igx z{<8mRreZ7awyM=f0B3%QT2L!xBs)g)YhDMjE!S6JXxs20{>+MSH3KJcJfC=(8@fQ) zxhBBnn*O>pge?ES9&OAJV*g~MgNCY5hu?#vkE{>a>y!rQGIfk^n6cX z5q5e+%Q}$fv64Ods@J9)(+Qsa0Kfw_`LCXz0urzg7CR-#;^ym665;XT=1HVV z0#q;(%{>mBnh}s^0;?=KTIOvwx|vfzACuIM+bOlcU%<4P!#uIwCZM*ETeWDMay~ey zwv>Z+8hCPDLlI1LtifC^{AiKy4;}n-0DZXbs_(XLjR;gxE@WmR0_p zpmMyBZrtjlgAkrHC*7cG*Z(So=9%4ULh_0$mvvoE+;C7;yXEaZ86{eL31gi#xOM?T z2;;FlE2}l?@THuCIfDRSfJq;Zl?6MJA@a4Zk~>-}^{LJ(oIF;TXSP-#G!}BohmFS= zQvmn=)>qyKijE?a;qs~`6aeEot(TvY8ugImly>9#7xZVa*B>$V_>LQ0L$8*DdU^Wv z-~C(MBX)S|J~CErt3pJ?Kc=mU3NiD9b=ZLNuZ!Jh^^%HQw&od@4aU{up zrgL_S>iefEo+`&$8FPnJjyI}U9a{A`HK;Cm&MoS{TW=JTbnH%kXbd@2t|C|Dl{bql zF_D`$!EW$s<(`R<1DeW$vb3)*ayphI2euePzq2$K=zm&zjegZhz5V$RM6xtm)`YWl z!>cW;Et}!oL)QLeG_?37)G4`nR6=evV{?IzIZQ#`svKV2upGVNDf{iOMCm*qv$FQG zj2QYVH9?}51K>M!-%okt{yfHqQvY3n2qa5J);Cp>nFZy4*oAJ2+BetW-G|M~KMk2h^eF@$#R<`Zt&k57~$Zq8y=0_A{s)M+!YF{)_`m zPafYF4-o&?`1s2s;{O_dEB4Y$R(ELX>rt^OHb>8UWg^fcaYFR{%)iFFB%MWP{xv?o zi~QI4kjVVfe+gNHySAgsF3N3ywB#GBi=5tB_Xw}a!xX{bFw+L2+|D_PwSci@5hp`r7VM-FW1+xsh*N*^m><`WZ|-hNs=0kA71n z_ZT|sz4tXG5TZ>(IKA$w(w&@dN~=Zm{R&6*_D9tN^o@5KeCQCz>UlI8tx@byjovMq z$hdB)eq*wV7vqLssGRf?!uUz*vgIFhlz?A&H7~+ur*kpAZm!Z-opv&Z<<#2v%-^ZD zsOGC(%U4xZbyOFWjK9XC`A?^&rY1H7U#&?J;klNtQKzG$BY!PlO-JV;XQ;h%x%KrO zt3w?1^@pu9L-g&qx%1W9@{Ns+B{`amjSc5Hno^-yET>8}7K^RpXj1;eh2ylMfy0$@ z6yb21hUV12;%pU%u~llBzPJd|Pl^t9kbyjk*9#4 z(p0K!y)x4deXP0WyOrwtBKinS)+}J!jP0hI5~K{e7Q%GkR6HyYa7Jys=E~E)?*TWc z`}>jC{kUMNPDgN-myT}_(YR4b#&)LMgU&p>@_e$YAmw>od5#qNlWly9<8J$DT?%Rl zgTYiKvH_S}cegGqXLv8*RfTf>;(c2!a}{AURT1(vCg(>$LQQzo9nWWLGJ*4WO--nz z6;$)O&Ob&+=O3eUSN|WQum6wHe`@@XF*g3k7!PCrF<9(B20M-W$KY@$q^zL0uQx(C z(RiFOl&WU}ZTQbLoSW|7wM5MW`3+&azL{Ki>rT80db4mk`t@NcpXnWIp0z6?wWcYIwIILlmgSZgJj&_***mE@+mCHeYr>mGDq z&r@=LrHMSZ%~!`xmgRMZ3~SlMo1JWbq2XMN6Q<#T#*e+SSS$BGZ80_LOllLuq>v0z zT<0imI|L|S%iNUt>NYSDBW-fLl=u&0+YW)Os&M2UmN#@~yPPSFiLFj=E35NIfb%u>tGB=*ptf7LuM*+RI~g4)viHOjareJO5r?|NKK*8Dg<| zi@|JQpJONIshO(r~hnkcUWusXM0ogKigYq z_5W~Ug#LlSwg+1P48vkoCc`{LRt#P2E90)zrVw{kA!YePQGb z0>@QdxuJE~&RbNJ^FPIjOX%CcGhWppID=A%5bo5Wz(j@&N;v&iD!J~Fs06-Y*H+3I zBG1yY*nWU+x$MdN|7zZUSfWpXJ$hPIW#B6TBCr@Uv3w(w^<>X85cX) zHj?dg(Un3G625X=?ax<=)_7z?PP)WyEzeB;h>M0FGW*eD>4)i3AQi^@^1Vj?n1RLx zWM+$jUjA7Zl;A{yuQ-l<+lyY{%R|PkFQusN+2+PqY13lGn7h)JQ@4vBeg3gB{|55p zZQsb5c$7mL*llx=N0KU`Rv^xI79HI-8!Lo;S8*Rnsbts|r?AcMFF0*|9zRyCtIg+s z{2M{!q@d_W*4d&6$y*PX8i&w$0-kOo;~A3 zuh+|*3rtks7=_YoBBGTB?BlwXt}NfpGbefP`wLzN4mDb>AfVBHCl^whtl2LI-SX~l z&?XO9gLbqD)-o53cI1_^{;M048DO;O%g4Xb%kV-bwl88Rb+X1>^@|7R2AK9V4OB0~ zFp|hp2>LFU?fTLxu|4}#&rIX>oZyCq7vfXhPf|Pa@F)H)7EuzEsAj@2>npi2){u#} zsA`WXIViD5OLM8+92v5M*j~?kQT999L73Z7ew4NT_&CG~yQP@^h~|1l3Y7HaVw(%c z*T%Vua4x!8{NPm~H;^S(83t8TGUB+`{CMt>F^V z=WX6;hX@sb2E}0M(W#ikP{UH%TiWSQ8=ZLO360p4zJTmd(s?2->gooy7XI_oob2>~ z-r41k!%LCjC0__A9`X0XR<`Cx;_;CyC1c&^cqBoR(FyX*)cF|lD9(U+;ea~{(K&(v zU3$^n^YX^gXGp4uV=T`_-9KrNygorRrLUNgrVoau9dJWJnbdUqjParobY_MtkPOtN zWphe$PM&N%l+4WPE-CAqm~%GF_o0mlwg@iCC8)U(Q|I;<)26?r8r0DWFT=!*buP-0 zQb-IZORJX6ht@cnj4wgNy5IEdJEK>H2`du+jp4h?SEjaZn`&1BilHZJr8)3;Ra`}o z(jxFRz)D3;!zrf5%GDcR$Y^VF<=qkvL@0OJojM9k7hQalb4<7T{`G&yZdKiT!Z~)W zoZRp^J@kRx!$A)GC$6xf>GRn>E0cNV zT&3Z2a=`}>&*;rJzEd^tqvHEhQ6D}|Oss%2Zz+z=MSXUiB{~5M)zeYo1Al_pvOT`=S6$QfeH9vWZPk(Y_JB z2A0jHXxC($I8r{{fFhLXGLTQh+9*Bh;^!0y@5tPzM)6hqm8KSb*tep>y%f_}#q4bQ z{j`OfGk%l8^=uC{3Y9;)w{htW&OX!^cU?XE@^Yj6H751T@<~26bdtGv2#0**QJnuB zC87Q8^Fu$1)Y{P267HQx^ICSAW|d%TqgJ7AtOR16cdAEA!d9=|{nJCed{;nwg$K?o zeNq!J6Zf6Otp(8?q7Zm^An%@DEQn${j(z2#I7*Q9<1<|-ww|uXwqBVKKsV>Aq@N4H zo&u~97GvpU+XBKCqkc8qq>0rJe_C(p&VeM%NCz~8fL!6~lVgX6h@k=woi%(Q@l$;5 zs-f$Zq87XktCbMu$Q7uf-3VC9&JG@eM9KPxaN?p-oF^HP9=~IX3hYz!x@do>J-05K zeNGvpI;5nis7_Q3I;2ZhS3O3SWYQJ2G>I`g9wuR6e0m>9*GX9O14Kwi!BIA2XN|hs zX9^;t)dPL{%0T&{j)7(%2*@#zjK+gL1EbOTp%BnrASd`HToG@es}mA3kJC2{u(i`_ z(>K%y>k8dJrg+3Suh?5FAQmegS8oC3WVjtovoR9;&*4HkK3OJx@~W|u%u-7T(lXcPQ&!84u|0pltB_@A(!M~w*~ zSLvI>EX=SoY9T}UTsv>lLvBhEf&0A6=T}WG$w$x%0RYy!hmcU6pTK%X}L%=1KO#IaKD!Lu=A(NITjw)6G7H3)T}C@du&&@#?Ump7s(? zC(l6aTaO+CpouRvCU_+p;a%Zu5W)Le2Yp5-X}{|~&hI*xgZI^?v*AaM3&ir7l0Bs+y1cgbG#vL_ z)yC*MXA#azGIh{+nJQl~;9H)LKZ|`PVT^hn(yp-D)6i>3;S#|^q6SC!bqA%xB;(UU zxg7-XKBcnXU3md7qKI07I6%;=o$@dZAtO&%LWpILE`b)?Q98c*#qm@DQaCVe^=7SG6j9E1gi@#uBwwfu(n%yDfCf8{#TGTZ0M zT}X?#Wqm+1B~BAfvFL00{sF%ulV)ds16VGOOA7+?*@)7jKnxFh|?c{)5K}(FEJQJ z>yYrI_r#mc!!I7rs+6qUk(e(fTntGkGwPTsHkaG*A7F1#E@`QDJn9@V#x_vX^k)gI?{@c?o@Z1$QU+5(Ik}n28%H%zbE&p6y`xdn>i) z;{MF@L65ZpB|Pd-dt6cSosXi`bLf=Bpo4t=@UEt$Ban;(8M($u9OAS9HUo02SegkeAow-2K_yUt z3)Z&HA>1eL0H)1J#dXaY3J@~9Zw+_uI!=)CTULpq#RBYC1i3N}B}VUB2a()(a|?xF zabIje(8)|AdyzAHb^(+Q$!HCL4auk7sR0mtcym(l^g9T^y|C^ZtBA^blW3# z%!y-#sr6HZSC_wcvXod02T|Up2wmRL`3hf&tdWH!d`rS0B=te>yv2jb z;#t&|0DyF3{9@kHE7} z8sM|EtJKMv))|Fw%f1=5Efh>qlVLm94M~79%)&1>UN1P9=z29TJGr_+_R~@*P}?{e zIr~8LuecRL9Q)J@l0j*4?f&Guo$MNi%*~*f8v|1Z#>2HnLLppx;_m7^W(Q8Ml@B9z zz+Cl*0v0}s+!3P_5+YxrwVK|ZdO`JdIL%f0yX4r!*hz70*6qjzZYRd1)E?9xN zbr=pVsVGT1Q}2(dsfpE!oP_u&l}f;}Nj6vSuc3|<4dNOT$Ios>ivi=Nn9efFpWobc zDtdqyHG2|R^|>Pay2+4a}%>V3s59E(NUhqRWL+q@bCLUowpwO3m0jwK(+0x_(di8@hO5-ca zG{TA>3(7g{q@pz+WBfI3_5f7r2xU3|K_N^__{cVp^IZ!G0I~~saeonjmVC>=9H3y} z5SusX0HDVMLubJN+&uCs5)=r84bU}>y|@L2lvd0GpP%O`c7JCh@xHwV@A3B1_aZTg zft9OjJb;o%rX6btiDGSY39)lm`U%CLtK~#HveiQa%JN+^QKXJDzd8c*P%At%?ny+_ z)r+vJ@3SuJo-n4bfxHCP;jHr)b4x)|4?b$K!QbC2v!osB0RDC5vE8j7 z&Hcr2C_i^Tg4Oz8k3%o|D)EoHA^2st2%b#oGyP2zRa4e}lX*{JKI7GWP4rQ=IAi*< zzLm;4>?RhrjEr=+U|r^+%aj6~j=KGX^kPYm`W(kU%RQS-J@hBLU=Y*M7&nLJz5N#t z;Qw&{vpDZMEh@k@sUVcn{`KTweE;i=@-B&$cJ2S{k4^N$EVzO9Kh$<3IiB#wMn0bz zkKoM`;#CldPuar5;>E>IL;P(yRgVfL!l+@zU&>hmETI+g)IKS6BW1davo4i~gAWk9 z`YmkB`E_z~Vi%#Q15%VFn|WbI05eOIvsftUdib^x11;O}AS&qRL3wSMK< zAaWaXqySU^`laQ;7juL;x1y!~D+w1V3~j>qA~P3$Ri zH=I?9wZOkHLK|B<`+pSg;OJz^p#*0aSJf|;nF6|Q9ztHarjlviGQNIS{3YXcb%_Ci zDM2j-meAl3($UkBAyeouq1cEBrX@5!GNDI>3e%0gKu+2yT!a0&q$-_!gwn%~h)+92 z6-due;bt z%t}1sR_!P(T;feX`#1xy6fDf{MWB_fy{E6R5DiSHL}#c7LRAJ9S5++H_HR zyPZi`&0(~!2c4hp*&Kenejs;VEoVRbk|Q7NVcLzQFE>$0t0TWhoIKg;%N6y_B0yTfQbESu1sjq8;3Oi_ZnbU|z*J=e$` z(RQz*%0@EA4ez^N^y|Z)K32QY4|UAI5m$lXS!*lT6cew@aiHi=DSWm`4hL#)=)24m z#BpVFH!4QbNld@6Ug`<|V|@e>;~;J(Oiom=Om;w05*fW`6RT#$b93OY#aUm|Bn$0S zZ$-cqZ1H@^oRgAcODvxMSd*C2+q%FKt6mwiHz_X0wf(^AmyiK!8viuj2}@t~Y1^>3 zLju>SGwVGw;=+AjxM!ERlorwpEDwZndRNy>2~$*KucG0F-1M6})^6OXyBwdh#1|w+ z0r~Onf$7|Wt7v>~VSyFI5#Al1oo;1sWiQ?{1EF4#+_|x2#g}a_MzeA<=()9=)l?fO z>$q8IEL!Av&=d(PPWy>>orO`|xKb};3%2FYCs`53fgX(!eb^$S@BY;)J!F#an9A~R{?%k{9!G~A#`w2j+(gX>IN{;EAtj~`P_ zak!=PCyB=@8A$!6g#b(=D^9<8y)G(j@VkeieMl;Qm)866+!w2kvb&2RDbIv+svTn= z9sVWRWS;tEV$fQi6ZL3e0o}KB=Sy3|u72w|iuR0St_@YpyWR7?=Ut2M6E^3rJb};` zyUG$|RnRGiAo9FAlY^_K@d$f(lLtX%Re5p=-N^LMDQA_zZbGyeD^r491s9Q87An;7 z@Rd%c(O|#6U8Pysb{ednWPx~g-JH4Ac-eFAeUkf)%4OXlOm zA~A1X>SvUUk9!|~+c5{`nxpI$LR>=(*yIYTA*LZ_WEaM6SZB~*pK#E;l=>2JW1IhW zT*_t-Fit-xyuuoI#+I7@@C4>Xh7shUS60&jB_7;d@cc@^? zfiYL*K90@?k{bpS2T4k1NK^X8rH;nASO@Bixc2KqM@l)E7`c%+(4Q)Im6FE<=6Su6 zrim!hdHyp7K))WLK|DaKs=SpfmU%N^3!r!2M$cvZ=xZItfHECzKYk_2xMK}cRY0Bz zXxbz{Zxn{b!>y4uds&B)^KNJ*i*>bQnrfsT;BhHLEor^sPK#y(6h5K~GYC)L=xy>z zXwf`XFe->+ovo$)w?fG+e(uh1ttbs1;>75xfY=jDl7pyK*ryu<=bD(8|D&MTsr`MN zy*74l4-3YLyzB1jOhuuo9qnw+si$qN#qs>QE$-lfX7F*V0{thwN{l9$^Ql;RPa-1? zrn_RWeWtMiQ$Uy$t$*~e-rjclLEq4YST*4MRTv;KhwElUDPki3zYWK%*qPG|HYY=h zR=#3$L54+M&b&e!KAUD;$Yf+Lr@^Q&n^9_du}0z(pGW;_4Y+6}F#DnC`uY43wEc0? z1(F=fE}T(7CAn_%P6%&w_M8bI-_&KIh7!(2b4uIzC@?}?H&U9+&geC!o=&muVn&^b zeciK|7=AvUbB756iU;t2zD z9Z@spWN8Llnv&p0(|uO4V=*uxElJ$g_+i$GEIHwFx&^&2o6>4=^3(gQ2O+dhgUYkI7~j|`bFPts+7o)W8Enq45zV* zUDlXv=_UWFw}*LJC+wAHN-6l%wc8(a)4@3gt36VqchG~~gq7(#u+JpoIX_=GS%2Gx zy3XwyB+wW#chOQVud_RzYzGXzo*TO18@M9+t;etY7#Ji{2A(5u&p{*=uD^4|c^vnm zVAX-diy%8|n3m3XOKhNClH`G*vfn;3_?VpZIv>528>{dT{D5|nTfH#wqRia)bT`;W zy!IU)So*IcLb}Odv~FOwsCchzs~9{COrkOleLhnF*9j5>R@{H{QtLTz+<0O)QIJ*h z9+-9gq-49=ZQTKwLFeA9gKE`W(fr)|p6e4wVIWgG86-T_K?{$iPI9%v z`&6}$t!qkU8H()-edoM{aAhbm9Zzdu{J@bh`tPzHYMXp_+uyU~%22!Wq5eF-zU=(IzH|$$vFW7zOJtwsI4K~ zFa2ATwacAcW#pL&5?7_a0#K&0UZBI za|c?WxpXmCtw9I~Q@#!b<^gBI92OuDklCRprdq!zzNrKAOUHvNRY6cMsI}wh_1I)z zDeg0f)T;175cbxLkVnwwwwXK6yu!7#KfHW+t$YSQ3?2e#6MzfiGI4eg1ZVE3782$h zpBE7oy8@QvI1#8Z<(W`q8>h0Qlr)nuuYi)_Y*0XD<#f|FkG50y)zTMv0UC zfT^s)r*I7Yiik2|c(IiaAZ&(W@BUQ}L;1}ENkD6S!&PnY*N9&~3=ChDMl5hQ+0nPf(~zzFs&69^ zETu_vUZ6i9Kmzx8WM$w=ePRpg0Q?chuXT2{#AM9kS%RvR>uE(p(P*LS!-1Cd+lma; zGs`4R=`ze7U%R^3ow)HtRb>-JS;JD~e~72|mrD!Y5CVYXJHjlmc*2{T$aaR-@inPr zHK47u!}K2V}+AHCv#dXr=sT7PM!{m%6{(1yELd5T=h5x!nFYdXlW>%?l+P}G z;t%zNQFirzCBU!H(XGD&KMTZbGYdrLF#1ayxgIou$wDD;=ec%K_&B{cvon)=b^uYk z49|NppC!9?G2^n0n-!?|f$99^8c~6zYrmLY&xE-J4CIa^;8izz&&*ha@=>?AZ=!}; zd+9(|^A*v;Y4>eDXe13Odp)wURVs2R+3kHr6bojko^;6Z?Khkw`$SK82)p@Uue?rl+0@YtC(Qa=G4Q z-?C*DBbNPd8nok?x8qEMFJkd0pn*u$@XAaUlGp6F4fkhi16Zfp4txu?%jG$p#V{<6 zViw3p4)bI)I-=a|?aageM7KLtJvsMFg@=xyaTX(W%cQ4moV)PZli?#JR{9|! z&~wMG^O+FG;U$Do$;6K@{QPh>$Bv=y{#k|&TiQ@rh&x~J-*V?a9~HYFJ(zankp7sI z#?sFoga6G#`yuM1Ff##OsPMb`Lq;Q0MYhMpJvcK)@w`U8zl{MO^LQA2U>F(=MqTiA~=(|wB9knCC^p;?ZceV*818{9XrF80$28qc-GzHPadJOUmS##df0Wy7?Y$G>`SC0+2hZ{)jAkRXB>X^>_7!(AuqAgNZAXhxnsC@e4c0HD4j?3 z_FHrRrmXph-*Cak$a5|U`GJh>j|&&i@eSYnCB4hc@gsYfgj;eF`V@??YfR!5_IAn| zJ-Rlf|M<9Awt^``eCJtN>oz7tfT|7Z&8`TlQ3@=nNDNu|Dw#e)Uq&YMBe^2YQ;5T+ zgogbo#muo8&jwhYrk7$7OL)ZXRbKP9r;l(x7riHNj5i z4v^nX;%&L?eA3hF#G9VLTnD3}H!jBw>H6&o#~-@V;uTyCT+W@PyQ=bqJxH2dF^O=+ zI5Hvv0BFVHBsV+;Ga|1t8nl8!VzRKuU2w8ev4^r?3TT3t3YD0y{oZOg9eb6_?>%zf zB)m);+2hZe#8#SwJ)V5kc>z$Pdpx{|r;7a5eo~&KqH?$V`wKqifyJ-Mvd`oBJKCYg zg>rs6g>jy7ubHM=FMHYA`7*lyZj2Q05VIMu;A|@VB%=Uf)}*iKB3v^5W9ZsS zK(9^As(XJBhW};^l?Mxa(0E`vp|5mBAGoX~@OAvL&6kkpHZ&bjt~^t?fAr1UdRlCy z%{K52NbY5H0*?9JMNzRmiCNZz)R*F4w?Yu~Wkv;b38zAV*t{)K;U~ykO%_lK0p`Vs zK0PxI?A_Z81`oTu5bAlqjHsZV^w^lSekOXO2$=}}ZMP8o8|b$mu6|O)2IEEVwX6oE z*bpgL1k#*aGWxbYA1zcyOStwpwpg>nrpMv#SH4*X>ocbXVy{fZ8hjP69Demj^mLH= zuK{boMWo$AwQ33S$8YdMz|za8aKJIC%hi8O4v7LL%i<4^l8&^cpKD_tb6V#}Yr-p_ zL-JEzbC873Z)EDNs`Eni4vdLliLl#O=`@9WSYhqe51lx^J;+#?R^956hRY-Z{mPz+ zz2DqMy!@57scoAMbFN9!e?sl3@};>^WFBL_Bw|oSm1=&hPD2&X(XvZiz>PK!`8(KW z_aiVKr@*krH|b&J*Z$Nj*;EX^mchI#j<|M373=TNIlhFg9eES&8dzeu%$>onb3GI} zbMQjbT65$}C)5JxcJkYv*b!-_>V)&;-WTO0#OhQ;SGU>C5$NAj5?F~`tV!2fPGG7S zaebf^k?GH~HHh%Y;Ct40MN8IZ^R(7juJ2k%i+{nQN1}Jf$-o7{A&O+9MYUXEaKU$8MNHY&<2Dfv06@!4;iZa90<|MYFXbq-2m zL!2}>gCs0qMtkr_kJ27#C=x5<4S(=B??Yf4-)rwOl;sl@~VH&AGaoi%{u$k~@=;f^ruaoGtVMMacnjAv{fsumUsr zbLe4{UZv=}*p)lylr-4nin=?tH8MsSU6hmlWiuAdwvC^H4zseAC_9j8rI&ho;)^-R zz_8p%J1ET5#2#I~pV>O1i^kF}izK{zRrRwr~^4U;oGHe`>U2)QB1o9 zU8_v{i?Y0!693T+_&Z*}biqWb6yY*b{)eVzdWb@f zD6khFaWKx}m5Mev9~n`0c{^{h9_|PX#)9eyjddrzKwg4^bSF;?F~wd#qrXi*3~v*{ z(?-Pj>fuq+UMWn4AUEe^lItXD$>^4zreln}Euu)@@p^^~SqPXCYRs-7b6kSx%t; zGbGrxVE*=SM7TUAk5Xbc;aaGVaq;%Ffq4zN+t_#w;jY?nJ}?^_PT^%k&(5-V2`>=(47%c09Mvawh_063Nk!LX2A#vo%#UC-mk;44B5o_5h@@d zyC5l>af$I5GbQ3{LrQmU3pgAhPSsVNNdsiq_~+pY;zP&xz( zfvtlQRVW)t6%9^?qMG+Lg8{e;aRQh7cazWAMJ`t#I9)cXX2WSLi(0l-%{R*;M_(5uyl zFBwlo00nu!+7ccx>#ZJS@quzR4+pLH8ZGOo2D2VG>EJ=hM30Iqu}F$PN_&!R+ubm- zV8s_rYtnyp4_YK(M(J=CX}vUlpedyRGw^xHY^!m*91=pil3{m&w>>rW^2dPb%GAPC ztAs*+&x+5&gKotkLitYWAwcDs_qR=LKzLy(@TeP~rUQ<)EgWsHW(!D!Jrg+4bgs~T zqw)b$zk!bUbpI!AB&VA8hmJJX{SY8B^YZI@2U8JFeS4E)HRok#GFY;6LJoX<<7hmX z3(mRJKklRabFhyRzgXE^TGSb%su$>weeeAV&9lrCoJ!f)fXXbmp_3)Dy(HX}Jl%*& z;G?=kBByX85;=tu@d^wLCK56B2r`klWXq42IBb+m%L4o~j1DG&3n#$52I;A!^M&3X z$4fEm2jhq9`yNTFb)+A2DHg8I)8{HP{{R6C9h%!IapbCcQYii#3su~lo?=i7y z+SX5lzple`z^eo4;O`q)l{FX_d~F@@L#3Wf-uOvf!We{I<$=XD-+_z!4{>Q5g~#K! zlew_yKv4Sv*+Vj$N^OmKE6wo}<|J&kdLz5OP^sze(guu~s9R}-*<4;E; z?}8a%&kU7Z*>k16N>0WOEX`TCnU_MMu7%`Zk@ZK(TGoq8qK?W)w!zp6Xn!pj*@SG* zlv2hFah``&)rpK8g08B%>QU%C+E+$Pv%@QoNW3VEPnRi_u%^vYIoshxJ`{-Kk;-Oc z3OF9AIC$EVqcTTIzq3yn2Y@9d9TjFiMgm$vm!yAWR1E530v7hV=da}tw&OWouKC5- zG;JAoBsOjS8U$2`;+!b{C;wGg!_qfG<0eOw5}E>Ca)V(ycx@ng_^fz31P_oCG*MIw`y2fY=6;O6J(LzF|N6V|B5?Ddf*V6)daUP?FLDP$uxTF*yy}mt* zA{W*Ll8I@M)#vH!l&3EB&>(H`-X<{23~BtbA zUu8J+X>;lqd5DeLPHOZEo7q`(`tEc37D@P4eEMyk-^IKAcNc$jsqga{>C+$3nZI+? z1edz&`Sx?0%xRdTp^g{aoe*F`3||bo)Mb`nJKp<7>h8wNwZ`Xv3B7`I5YO}v-q|}n z(anOVyl_u9T_=P1zGqC??C@ky2dQwSyAfdL*Q>zN-TMSNZ42*XIgqC5?He2z*Wq>| z4-}%U{V5+53wC=&Sx2x(*3c_`+M~z`$^zH;xqFzF&`LY=(?2sFp&1=nw`x$=Kuc=W z_DZTDE(~38tMNoF{a$-FPMno>k8y9XePk0LQG&E6lq`@!*Mz7YWm;2&>#BYRpXU1H z`Z!X7f2Ok|&txP&+b;kxw#|D#_^Xpfk{dxFS`TY3AImkPCgQb5Uz_(Z&blT37uzTl z4)r1;eF5bon^Gs=Mi=R??g3>DVu!~LsKW;dVYd%oeUTq6G(4Mo-HY)COv^m3#<31*$Cn!6>AyqB}K6p?_fq$d#^^pe-(6CS^| zqif?%I9W_l7a`~_B~ZtkT1YNd@vJfBY%XDRF+Wn~&@+xh)M=q&JjWrjHwf6?8K9|i z!r>daWkO98z7%&gGVzj4rFG_iy4|lXw7xa`n!565#{cbh42H9qkh*ufD)#MGUBSqW z$XRB|Evp+>6|Wi^)a{u>#4AcVX({gQBp%7oSB&>^LmFk=BMMiU$le>d3{}>9$w+6s z+we5L00t_iYtZq336p<)1zW!Q_Vp?jq!=ecY9@EL6G)1WEuCe7Rhz|iys3p26N@-G z4#GISO*s$M(cbQ5ZomiSaXFk8JpCp5R+Z+v^3Xc(fB=DYKMuMYR;X)bEs0l(NC8JxKkkS>l{%{u5g7<$p=6$Y|Gv-rU+cftyPoyD?^^#^%l+BzYu|fc*S@ZO?Y-~AzHAx3MSS6d z9d8#uDR%x=G+fAf$*mN0s!DU)6`~rI;G+Ao&t_gu zk^5{AG?wt)FhDG0d-FT5jKI zR9AVe<$W9ObzDLRU-j16-3u0;lwl+?^C&`GKqLNBUOeBd^$h*vkfYDp)< zavB`Ot}TgKDbd8XYtV4PoyOeRpRdd;MExtxl5gF?y*wR>h<~3XA!hRDT=ct)Z}yGj zs3ajFK*VP$|CQ>O`&o@jTvcrLJS_dE1nic*)_au?t~&6A2yU(P#IoPA=cEK-*P#_y znyRMho+{)?Mxow=#D)rmx-SI<`-=@*8d4v7mV~KAbMDn8o?ICg|FI&Tm4mOixm;M2 zaiIDJ+`1|;TU@U|=3txLN12|Oa%;a(`=%<`v6VFQw&RcMa~tS;3Sq*#NuKLd1L=>W znv!=&9*F&xT4jLhj1K;|@64zl_2JWzbkgTX2`kxts1=ngMJ_LZ@b$bW$EZ)$SGFSu z$yY4*rR_ccwq`A%K+8DVTK@annVS`LceU0A^^VzW%&&8%n@W0G^)i`hW?OxqSqBCp z2wy{em95-&XSvAFWo_T^$I<=ol>6>{0hvxexm4%N?Q~1NrdV?Jl(%nyzn&+HN)PFagz#J@3aq8|& z43fpis%dLnLbB3yu^@6sc^&O2%}nDfBcVFk4R+~9;(2aFwOU-)G2Px)(9;yxMe+)A zM{E=qZW_ANnYsh|0~{L7{dB-HLS+#}#U~cDm6~)E-kLYL1XXcrDZ-}Z%PY+L*YrETazRolKy!FOBT6>Ru z-}!v72+{k#Ww-Cv^BK_!M!7*5JX`9If^)UO4C>Z5-|NQBA2n?Zjuf*zc|||;GJ`58 z+{g_Vt@4+59rBLxM5&5 z{klI{Us%%o5=4a9K4tE{-*6?j{?ei}f9oJN)#9H;pZf>jtj!u5C4Ko~+VTAT(S4(H z;#^hLBD3v>ytG33(i$|&U@IF& zjfZMOO+t<{L!9wVLq_(TnY-!UYqOgY6EBeI+&h23b`Lp6%KX_J2R6&p?#u?ei}4Rn zD+eAKIJt4)v)DcJ9MLJAdYTgr%uZ(w*OLBhlqvp_Ju8m?p7jK5tSZO(n>8o3=KRY- zD7{i*_tn^nmfK|Jix19ilgqkuH<5jieN=k8`;z3%^HejG<@b|=Pv?Twf6Ds2NCHv7 zPkOnD?l}=QQg58@oqCe~5p3&lS|m+65o|~obo2$-8AviwU#?E{9!yH~$$`E4X@g>j(4;&S#gyLxrf4t~5xbI_*;f}MH=}hy==J|cOPnOp zf%UsDR%A~1cLg?nTwtF0yUggEQk(70N-OoW4(bdrmfhc~Y!F7$FS6mR{Nk*A*y(4c z>yWPEw~`j%U21tr`3bgUuq0Nf3ja3#`ML%BDgDbM-LlS$cRDo{wSDy-idQ0ytxaN; z*6*8j5j!CvZa9lu`uco)f^@y3*=@~tC8;xhT7&Ove4iaDXytrsHs%;iFu%PSi(*gV z7+$T5ynl*H1!d-RvM(-pp()adz2@@qW1Ga<%laMeX^C&03ccCu3kngqgEZO`tyx4lH*C>UrR=dd z!lD`u2U{;6w}V9(;2&T0iI^*Z*J4Flt+=A#q4^-TImE7@R!j`0?kZ;R<~yBlIfe<~ zH3v)Mz`Iklhji*e{+;;t25d?HkMH6%8cSZxH6+cKg%b-lqZL&YrZ~a&!w!Kh53y09 znX<9i(d)1uo9yT2#MBxKVH@j69m%}S3v<@I3Svk0QqC8vBqaY&@UK%DWU)gujpTZ% z;JkI7)3#%xMO|Kz7{X&tfp@T(W_aSB%%!xRpu?o|Z=Sg5yysY^pN5@F5;<}8UXTEG z?)zV{Z00dq#`?CNNX5yMBQR8$_rSWpf)$mqIh^@@%rE`z`jWK2XUm_8TByw=-i_{% zLjR+Q!O#TJUYlr`_3X9CJk{GVIddwT=-aK$Vd_;_+FwE;&q46pZd11KQ1nO%fqFf_bAG6%bRNXe0++%zzcnKP2}v@MG*w7{@CQM zSYc=Oy?9a7-M^{3(wZ-jEMb=Mj!F|%F)ossBT`d+`+l?M8j+sYY(B3}l6tcjC55Lu z>3H|W$&@ofqV{m?bc^NZ!<@87=}QS*Eh(w$ zMNCEhUF&G$6B@yR7aet8Y-qjPeI1QJQ5Nl;H5aME{g{P9F9VEE{ z1HK-`pG{KedPn1;o?P6746H#`J?qUC$%x1)vlU&wpDSa@lkX^+{raZf=Lz$b9ozS8 z(TNwoxOaVT?$ikG-hoHC9{HNvWL?5eC9CsXxC4=5nTND72a?K5l)k<^UGWER&vU_0 z#3SxM7r+kfPSG0hJ6FB?ABGb3;FR}WB`}C#2OUcU= z+NZAS*ZrxZ$W8~htj84gdwa^Hb#+%LZLi1fI^7*|>&jG|&saNi4v|{PWyQs18hdL9 zl^Ge%8Px_fpU*bV?+nT&*rml6O4Bu_yj#=Lod@x9h3Ud6npO1+ahK75a2 z@xAh_#FIHI&qdzfczx-Lmk7p+&OAAy)y3a1fp{ar@49k9D^YgrYSGE6hk+vP3UYV1 zoXtfBd{|$GJLT^FRCPflL9_BP*z@zcIdeq#=VAnsf=hf!RE1SOZu=~vE62UaeH?M# z>uF+|ucm*())%svum?ENh)C@Ao&%}9XBdK0Tr4-$azj}NQtq^RT=5=}b6maYCEUxb zgYqIh508r`SlrZicjQunHhFuBDjBGb-o0!pD0DHC3QH}= zu#{Vhn|a0N5#sUp?zxDoSakf8(NLj%rFO&3CM{V8f}|wsE5g5DXeh#zYhKD)68Xhd zd6oN*QD_%Zma`g6$4FliAN?en@NmS9l~U-qF+Vil|7)MU#yplQeH zB5aBI$Xs)|^1qg`x2d@{R^7uSho@h5tZPY?o@68D|MnoN5-BIy?GwQUH4Ec%w{NlW z2?=Qx^w*TaDkmGkW>UQ+81Hq0S@6K;BIl0|ZT5O@t6`3@{#$#ODP70lrms-paSe1R|7BO0sB-N9MVk(J^(&pyFUldAK zHoaOzUyKN!2wZ4e!X!{lDDH7ytTD8O`$ek>mrK^~>x{po$n=KVUm>eXw!D%&?Llc5 z6fGT|VUFiA52NSfmm&fW#kYJ!oYtShMJyqtw>|i-`>2+CZGUya@h8)$1|77Ahg6$E z*23XWAJVlckH)70l5U@`<0$x}x`MmSxe*;Jk}8Ogw>!E!rW6KXvo=u%{a=~x-_0YQ z!P&AA6hArq)f~0&or=Tna$%n`eP9k)@l(7$9C#OqpGrf4cY)jtYFOZ1Ap6r?$`?27 zz#6R=5k%2bwUR+{=cj)8r~61|Bwx6QKOH2vYlRyyv9p^Rgsp`ZiXj>h8WU1j5)~ya zBf(1%AZ1aa41ud0JVhWXK+A*kg$jyFU>6{)vP!FndaJ60ng9zD8i0_Zi7^9Pjc94> zP|PTv2rMYY45tgu3-s{%1v2Us1A+`sh9^ygN-;Dt2E}7dOhM@qX68sxyr_jGREm`~ zD4k@pPEWm+XKRNlbVGq?;2el0(t4;c2ePA?falF|a>j2;xFN+#3tKL*O0186j-5Xx zeSyu(yNOlOmNrkgHa|NkXz@KZ*Cy>U_aHb+HZm?Ct>1b>Z`Qy{+CbJ6&TV1cNjI!` z2C|nA+D!~&h1Wxf!6W#*9Jpva*(K5@WZlP%!^?&eu?K_50d73G-a|fYG$X2IkE2K& z3KwTUSzqCU(2J825J^7LGL*c+3rJaBfw4MD4qgbPL(7Bnc?yaWybA&)Wfd$#;G-&W zK_D$qQwQe@H8e4WFac3Zn}kqr)j?nd#8y$9E+Aac!(RpMsBb`^pjpz>`89$Abqo9BDE%I$QY96MaV9>hyf@Mww zX8pV%%TbWO?4))r=ldfSxc=vY>sAZ$OpMVkxz8RVKRjSu%lRE$aeCR4uft;^3?+OG z>yXOp-W-$tBRl%B3TA@NJJkESs8vMq&2Rl0{ej#nZ$-?e_H9LGCH<8!U?AcMWyE=;Z6qxgvE{7DeX&D#*6$Pl&dp$RFL;gWRs8#3k+X> ztCy9Ht%fA4)s}U{kbyY$BS6fhg1{4dnf0;{ex04>_fKx9JFmgRMa_9dTsR;E-!A>o zJo!+@_dA*V>9zRF;0FAa?_Rq%WblNrA3P*KFlsPqg5t9y>qRr}om%f@k>iLFo528I zHdCyZY0vqjD^jy=mV7UQv1(ci|GS2K>Y)IlbJl45rkb=q(({Wo$%f1SzSg=^bY96` zwMKM4Q-gLJ30@zlNmR0-vk&UB$GRzIVz)P~_|-c57PlhTTY?u(SdfqXJD#agwDKVb zJE%YCyOky?VwT&*$C?Dk#eB+pi#%-gECJqaQ)$7wB+u1VWY7ZnU2!a)B9$ViOyE=^z<~#QOZ*p zv&^IO)V#MJRN*93l0Wu5OxM@6`vnU(s@3ZaQFq{qDy;9mE16?&$Ck~Mt2$e{?YoTw zUNX?tZb6KUP+!L(lsG8=I;1FD-Og)C`<5&s`M@T&B}N!V2H~=v^|$3tT{6oZEs+i! zNwD5`NOJJ{0fYI4MdSIdf0yRvlXG8}<`itlIdH5xg;k@?-Z!-Y0T!kj|E^uE9gg zriZ4TuZG)Ke6pW;b&GUo2u6!Y301tO!APpedlw$kmpt5oiTJ@HNc+iv!x zt9Sf4FzkgFI|@e<<3ffC^4@Joprm&&;A6YbkHL0!b{sYJma)w`+$b)4?6p^j5T3t? zEEXXQPd}zEptgTor51`uIaY1cEm|JyOdi#dLFMMb$s(x}Q)!dV{&4T2KJ(N)mz~AI zW7V<@aUY4@VzDHGX3bIAm)@{|S*LKxXsMcohfb2dh-v$qANIZQ4L{CJH}6n>!|jb6 zkx+TlUl*WRehPL>Fz!(Fg!j$NjOeJ_vRg*OZ`g5qQ{qBcsrgMub2neUy<^Jlq%w2z5Q(^XDh#*iuxtIWnWXpbrw;2&9ZZwx z{xJ@|{uut5GOm-V#RNxBt78h5$9-pSM7K%K&( zQ7Y~5uKKX`>8@&w3xrRkGpRvlD+i9UKG~}| z$%_s6EJ@dsuh{#r?88mpT}RyA<-03zxI^2ES#p^7^y=^5BEyr9ZF^Im{`j4~-rS$X z`XxAPx62WyY3n`uWcP!{KkI@=`c8Sda7Fb49^8#mr-|P9RB92I%rfkf-k@|T(0==8 zoq#7{;I=Y#JapX?vP`KE~| zn|qg;s2tUd8*3)kdr(l5yumD44k2=mvnyrBv}o32AvT5BV7f206{BC4mFkmb)g4E| zrL9YMHS_YXb9-fyu6O@~+{@gnnI&1BrxslHuSaR=W$uwWw&R=9jJ;n7F*~1EkldHt zOO(zIFo;g#<;d3a~Z`TQ!1Qy$QLFE875)QG1?IE5(ebvYJMEuvRg79}OA zCY`UE(W-4x?OSX}r-U$m33>^J`L+-XZIa{RbVXfyjqZga<_?(_ikc~7>G?3KkGHspl2zNIxp8xd3-8Q>6 z8&aX$y?ZxZ@;D@WxCPjx(e2F#2)T(t#&45_cl!QVuU9DRbzm4>h}3xfT0krp zyng%R?T>{Gl8fqj?}$&;{o7Ln?}$Zv-tU(%O{2f3joqzpW9IaXv|8z^k70?|MJe-w zAMzOkYM+R8YWan7sY9QLk!t*d0(rq_N43uj<-Z$bN8CW&hOSdX;@qx~a8OdrVizE0}1NuS5^< zCui7=$eyV%PvJ?L|56^G;okffnRbDFX_i>PRX$w6(Y-oLr0mP#_4m%H&MhqPeA4Gh z=G{u9Z`hjog@Q+B2D)nMdlvp=U0r9Mu9CH6IUIL2J~?)Dp=R3&yL|XZ{59EYi$vCV zz0JS|Ly*#5*;|9}RMn7~fQdYgQ=Pi_iyUZbD2O5%ey{wMwRv|<9?bXNj-t!4%WpPI zr^G+s-l^{A6ZL9bz2h4a>XMq`pp=ImG*mo*280J_?jCJq<)7lXjx*f zKm4&PWc&CfWRCxzZ|pb?daO$7bv!=ho<{|&FpCe0A+IW~7M1Cq_CXFH1ci{JUE$UT-HBUF(Q{?4@Moz1434M zUhl_TUBuWr)pk|piK>VlBg&C-@JWG?;#jh)*5G{R6`N_%Ua?UeulBPfR%5`nTcQD7 zeJb&{d5K=Q`i|??(%Ynx_gbiRAf2Z#N}ah{cNt&SCpVZW^8SutQWUC1#7ZA!Na-TT z-daEr9Lgl59vbAK(~;QCXf(ri@1zsHmv)m%q>#7DRIo$SUE9ep(?_yWGFujX!6b)b(ezg}mL_%|277 z)*g-SeJyeCq3G=-Z)>_^LBZ&F-qYg^=&oZTZ&I(WZy+aHxHX7MNH{rP5#TfMPR_ci zv@z2kIO6~(>vYjxaf)-FVIm(-ai%yMKvg9zkul-cM#d( zs(01RIdv>7#bMV`Jp)OW>`@}I!!35UqxJ&I-I=xN)wh?mOK^95qnWE(iK|6RoLk2{ zF2BNcyy#b?PqQ$X)U6iz^yMZ=}c`A8u}z zZPqKu#fO&P$L6O>{ib+)g8j{^r)f*g5x?O(H6u2C3*JY#>Y`KSapo_)5uUySo%P2& z%hi*s3ggs{5HoJA_R~%0&(P$;q_--0EuK%adJofs#?kqj_ zL7jnU@w;xEmyx`X=KRGC>bLWyEjvnn`VwKMFES`jKfWx6{90;B9xNiyWO=_qYRYExMxl?t+L@vq0F4)mM@j_ z7{LSHpC^ZYefe#Y(zI^B8Jz6m^i3&|&3Ym%65;>A@Lbs+gd}9tcHde#;pVN0>=%B2 zNe3cn-^mgzmWaWMqwRdeyfT)TtM=t(&>s?N zK3FZ6HAiZinglZFLcT-&N*i9v?;Vo9mTY?oFG_i_`_6nhjZU*2U&E9O0X$P^9$On-h z5b;Oj51=-S9+ZO{Z*b{&=XO&%?zqph@Mi{I!@9Q5pI3j#5hg?@WDRA#%rH|S8Kcjw-%XG2J{o>Ua<>oBTkFukn(oKd z;7x0@Clrt3G_ev4kF0jWeoIQSoMC@yxzGNdkSk)wiE^$~USup)f)^RNmuvRs)QyPZ zMbW6%D*~S=L($qhXS+&Do=3G7N@lMa@7?QMz!2G6xsEP=vk2x?!t7-FxLI+TTJL_U zJU!!`!@3;naVT$o@4ct6#@PGryU0%hHt$keFQfT!K9hFC!>Xst#RPU(F*RjHSIY5Y zyJMs4V$E3UsR5haDbu$`6)hWdY4hu2#AOZ2&)x0y=XynX=-h5krEca$D;+5$>`{@r zy0+J&mAu5-=aIX$bNj=7WGgSAlNqxjSWqA^dOqK7kKG(cL~2c>9gzjz!fe zauYfy6_3}>?~jl8iwe!`Wd@%A4sX2S9eE>{qbb&d35aM=VOjDM;h~lExf_0n}`(l z6Q!4Q|Bo7+~Dja z(V9MA-_X>elQ;Qt-|sDHAC&i(_)PXZw5R^CuMm8i@Y6E3I=XM;;OBZzgI}M_a}&2| ze!?EzLDjsaFrhr8maz9Dtp6`hef`BT<67|E*1qL?drPx?zsLsfJlu{`f4B42o_!xy zP=Zh6W`;%@XR6%N{l^}qkF<`wyxqv;+!5TbELJIeM!9_N`Av@)nKLe@Yj*K*FXF}6 ziBfUV#n)R#MBU2eo^(zppDa}g?CafAY5wjr&p5@hZFf@X=Ro1-IvY|vHsfiZ`LLtl zC~S8|K-AM6f(HxF{dP)!{_NvI%~K!1N=q@)2f9B^_v=3~Xtm|v`!whx)>GY*?l!as z-t&nkhdCD_`N>Y(%H}+;ccM2+Q&K=R8s_s;KO(|<^?TVL4ZAv98GWK+6-oSb*kdJ= zI36Lyad}MXpu3j%Uf6x_O{$|9ZW zYr1{=6Uz$MY}kxfF1n!H?AszyA;xN1ej~%zAqZMCn=H4yv7*(HcE7(oc$r;vLG~ZJ ze-gXQ)z=Tz->tv&+TXtyf4TJaKLr(JO8V;tL2p1ZM|={crI@alz*>2w{Cebg!&4Ng zu%_ms9o{dc_u`1r1wo-0>jM5&jUpd=P3hG|IHC2@E|*Iu#11Ju=r7U}j{cB687y-S zleq9y_KsZ0=;2Y&R4$A!=gyIO3ow>%eTm!Fb4rux~Zh3)DBuxl$Mo&>3v3d`6(|Jzu?sA(>Sl^xHI?92*8g06%~pv zem#b<H6zQh zmCOa6|9YmJz$cH%3_|PAZK9k-WykIZTV}<^M#TZpkBb9yIV6k4!fv+0ve}y-a*^zK zLj2cMBsP9=;0diD|6_Ar!knf_WQAnV zF)I;c)79#JebdLGNod%XEn6sNBwS-}aC;Ywv26(yj_pMBL2;a=a^hW0g-KnJQom+XdPW%E3dqmRPOme~D$kv4p9|>RnZmLhI z221_5OAjdizq1(Msoz|EXSDN+}%Jif2f`OY66bHh5k$V=4AWWRi2`H_`Jv+H*4 z4A~-%&DoC`eI;`;S}sG^IO8n;8@FOQGg7W8|Lo}N?7RO&ZdT6D%&T}8D>wCVBrE4t zoD2smt=fILHaYz=wjt)(f3Nf{z>&6=!tE ztYgxpSp7^dGHN_{w@+20+A{S%hj^5c`#k;3(S{}lvGx36rB;Ee`OG<A+l<0nE{b zAKUyRaIY@R8>;i>zsKZSbR^%RL`0gtyy5gNwS8pHA$doF*frd_q`Q>&buuYC@MXx? zZw|F|yE(7EX>-HkOb6ahnUs--%m{*U?9G=%mwgpK#$NgCvifLu!C0MF@bSw;g8nt` zL_l>;*yHQG3n6@k`!mHzKE;K8BVe=y$#-(z^Bi-~RPaD_UC#r(v9vvtKUW`)%8-6V zx^Ar7eSBRn(p+#a^0ink&#+?quYGOm8;(n62K_1egMSbz)fOJwT;j(zt~?MOhx^HX5PE6q*6w||Wp@ig99&tw76dEn2D=-> z4IakJcN57kj1%Aav$qvCFH-H*a>zWzwnnj)dp5*%I-lV2{_z>!X|qe-(& zp{^h}dGDI-7F4UiPD1v|QmJuA=PUba$)ciD;)YiOWwka{HEz_Y5x?c@Z=2BJu=vB= z9Mvm%W_y@XOcN}ufY*K~0;^8%7_GS?S zF4_g$wl6Gkk?osQ|0Tvknll{l#UJKL#p#<&l;S0)P|RmG0IJ z3K3&r%!`^*XhFv6B>j>s&!T36YW%?D?9$ASf;Is%1K};@&FT#t+unK8!qN&?=2bSp zKJ-~K(-%R@dTG%yVE5VZ#n!7L-rtfoRu5-=_54i|6hHWy6Y`GcEA}-@ms8*(JFc8P`uWwy&>CDLa+rr!68I~P#T0)leYE0!+kbp3DhQ9Fp zjHEq}hczG~%1&R!FHQV$C|&VIKhBu6|HZ*MH|K|h11k7=H+PKP zmltO#x}A44Hvb`R;{}`s&++bPKAk=NLQeOx7|&1f5pjn`{t%+@^y*2+AG05BpxLI5 z$+Aw4GOlCqijzK^bVbcJi#RA5dlua08R(cHQ*-G2Bpf>VwVGrG>lE24Ehv^;H3C^c0yuk#kM+rN2T1Uxzp(O7Mo)0EJleS>lu za~GS@5TwA>%ktvc4#yIi9Jx_W`*Q{P2`~DM;zBoibLQ=D=jYbAO6G6wJe2c34*cucka@6{B<77-b>9-xZ3bSt}hF2^Jh9+b@7T-PxXiDOy+S+ zb&mM7qficA-6)%)RD%#zzio8PJXvj2VL`_AgNRz&POlWl-k%0FcjNW*A39*Qm#d>g zQ|`85TtZ$d<&yWHVmIe*R$DN!R3M^7!t??@PvoDl}SU%$`t7f-~$ z=9$?|Daxmj_80d%bIDwIOV(d?g_BW-V9fHTU7lB07#S%GzSPW^t$_-HP=v^IDJ*9FU=@|A`QLZy3LNT#g=1|LY?+&Y^^XmJBstuhX zL4+-K)4UH@ZnKVWA)&sa*o89R>(?S}Veu4kOM3I2LY-@A=NDtgx1LOO?0IzA;NpX% z)eS=(5%^HB@eRM0s;w?51ivtUz6eJZ_DMoQpiUuC@h+NWGy}Yd)dKPU5bLk;Rh|x% zYsfIEo1v1eF`p@KmB*wOp=vz zhwZo3>8izDx5fh{ZT;{Vlli2cU`?4)CfouByXo!ZA~A(Mfk#_eVNI|w^;3fDaJ15f zbY1}&?V=9GaS^z$NSHqh@lU%Dcj!^4 z$cYMyXXJ^;+(V(S&P&9c*jFzRQ}H&9=U*F8*kpF8EWU!>U%R*}6o$8qrbIcXvQzIg z`EQvXG_0|{bZrnLMEhSaDhq73?+I^MORH$(ko|5i-xJv0up-X{wA^osB@7xq{)j>6mDlFjVp zbTScK$NYto+f7NOZRn~)C(u8aq)#g2$=Ptx2d>;4yU^Ozx3OE1?k1@_Wlze^-ny=M z1+jRoj5Tq(zZzw;@kwQh@6q7)GO~^H?Oc7YmN?k(fCyTLijI2|bOwJU4#^EL^*p^m z&r$M5`uQTx+8%h`*##2`59zQt2(KjIh>)vB~-|6t~S!C0A2-l0D8k75c z(0kdsxHL`a)k7l+>|-fwW4xx~(+Y+aOjjF; z0Fl!%jN&L}C97cp{VZ;dO-RUKm+T7QJg?-YZYBLPBxKIzyy&5hAEfe%5rgF{>P1$? zU{%dUkQl0K7~)-JQ5&0{5?Tg91oyUzj&ABKsisGBix3XS5HdJ@*N4D23ZrimQLW^t zF+rx;;L@(O4)uVUx_MMWp%K9dFXr(AkHKZn4io2o$%_)OTp!(930)f~WG&SX5IuM)uHiA*5 z0vez`ipfxo17f4tM70DC5Ib?6o7$od8emgq4p)PR0jc6{h4PA@AeEz7Rb#QRiuvPJ zM`&2>_iKcxP3XrhZ7ZOm@EzTZS3xxkBmw_%!}}o;NIheYjlNw5DU(lQf|<`jo9a0e z!miVmGvo7Kf%TZhiRI?i4?Jz-sUV?*!s6HeFpEI8%%CA=mGE!WFwP(~`4)?}x$O^5 zk%=bE8VgB#ASGqXngp(F04MSIf`n5~>3~GzGQqdyFC=6jEp9CsDuGTEMlg;8amS*V zGyS zLV~&~L3C=tDVlfOCked9yQ{&u8zjMv)k_i*O|=4$l#pQ7NvJ2@AW7isA8P)G__kg` zqWJQ^MBjfT2XXK06yOa&iEWTwqLmuXs{uoH!!Ryj&w&IUQ$sX)261j-lXKA?l7Z}g ziQ+zv&~`);sLsA6gV%lJ0|~SUgq zsZ&%kb0&ejt-RvLvgMHE6>L;h4P@#Vxw?jKATF!1iHUB31aDK4@8||iu~IJNKSqde z0oK1T^o&0W>GEx+#F${^3H+ggu&1JOCV*742{FG&V0TUMmREUFLIkPiPqIR=G!PD0 z{}NUv0Iehf*}obFM!-U*ik~E!Gy<+mL@jI*#Oxsf$P_qb@Ou8BJ26H`@U3H_61iL< z0)bBA1(0s{&;yOHt_TB!8>a%2vSUpO2$`;s;0}ab0F7edfmWhB zfX1m&!zhul2l?Y9{9~dC@CM_ljutj#r|Y2G+LIS4bC5%Ff7U_uMRWRaC65AWgkS*- z=sB}TCwf_9aH)saD{)7CXXHBEPold+Bz6o^5 zaV}k5dwecnrmCrCL2DE6q3*YqaXJ&E)tNPE4r;1hq4+iskPLP#Le-;{uvgVUH$h?e zSUm6wD{WP9&Be`k`k-qdduF%v*Omp>(96_ltCk-Jp&% z{XMVW#9rEP`PyJX`<}xO5lv|8T0zTN z*o@la9FL>Onf3lc8OXuNxcd=&`83GR0%^~Jf+0Q7$F@W;zzL_Btx?S7V2BpShHwAZ zK-EHkfdZNdJu`l$)4LNs8Yn_5nTR9n;L4l6$P`I?N3 z3?G|<1b_#dp927Uc8ou*0trA)0slf&@B>FH0N}`n{sn;j_Xtt{XYv&NTh}vCRT=KT zYLSIFk(HH&H~}CFaRLD17Ql5tm6Z*sbi32%E(3<7IN4BO-GQ|EU{pOVFMy7UV9-Dc z6$NhBZbsF|fuR;ME)E;bO&tf8w49m4k^gs~jY5_WZ_hqh5igX5erH}1qEKPeBo%^9ix4#t z3_>;#+-)<+9dADZg3ScoHiM2rEl!{?A+{N)rpZc1J-D7d6~+9@uf=SFL<2qIAg5&- z5{){}nE@8^?O)j{K9xhXiipouz=Ki8@q)UBNze|-jm}LCjlYKzke91MxynHTX~e-bx@uF`t2puE!l_bBGDR$qjOdhTPP8PTnzSlEgLQVTOZH z#W+HF1(+f%j8@g8g4cOSHFXUZdX12$XnNby_5(g0DfX* ztk48D+yp$J5u(uq3~0&J)$btr3d4H!b^(p%%rZd|ji%4_On?EeW}Z;jfK6$7fq^nX zO+si>y4*2{8yxIzhzR08W|V-|i5hygcN7OwZ<%8&Vcb4Jtnj^lz)T<*R^#RuS^t@I zpwV$`dEk$63Y>T#5AepE%mgrL*g1R*Rj-Dr+Es~#>=IwhAew9i&VToug-s-3`!qD> z#O)EvgJ=jY^78WFC;UCF0cvdsY&mY9SrM3y> z2bUNJ(A{M`rn^73!n6+mC#uaJ5lux`@vufIh%t3mT{ObZqd zxxa`Oq6wn)Uw_+Z&%XuyZSb9-H3g_UD<~*Hjs$=Lq!$2?UI0LP0r3ALkNH_5h<8{LSIt{5+Nl6J(D*#BX0FISziXxjZffun$PCs@@a2EkOck@h&w&D{b5%8q zz|R3UUNzqcxy&Zt?=5Ygp%Ja48(GGBg;@})sC1uE0p0|o!mdzJf&2>otcuFBv#eJ+ z(Ly^7?-eW+vyu7opK?6)+d^m%1d|s-}Ql zBOoTg%oX_-tpo`bwWx-{|Ad6Xf>i`0lu5%c3!69;NG{ovZOmLG>UG!D)F2(Jfghw} z0Fbo-fUFGwWNiTck9ihIDh##C?$xxFjieg><6NN{G^(oQsB2wSob5 z!BnlW;9p`}p_&@tnmJUYt{^BTK%EJo zO=!hmGbW&39$)cC2p6vsW@f;Gw{u<kXpsr6Y~n6Ke%!)Fsn-7Tp>uBJmNgBCQ4I|ZsHp)!i~@j!0RRaD;QyHC z1(-i+5Zcss0FT=w1eqbn4ghk?(6wOFi(fL=(10BB|E6kcYC=>1nvl~506A>{hCRth z%>eS?;Prh%g1exZMui0MebyNWnGB8mAedeQ3D+ILKt76sXiRob97JQkc@Jcw58+ge`H0@3bN)!^IeAX)?OUL!i{fLqc6ZR$G&LdK0U!Sl zcPvsbR&%9M6XG45{tp#{fv6bpgLnr3@eTmW0|3ec;D0C&Xv-ALs^x#HMN3Ny;sgN1 z2>^%_01zhtAWi`M59Ps@6(P0OZv3}ew6(P%P5?lh08rm6Yo)C%)P8v+PaK+ZgFedB z)+V@v8*tMVWz-(uH~;B$fi&{lV90CG=OY-9*Qj|H#SDHAxyd*X4KM+^2f^v!rm`Vb zj%DT$Kk^_|76-m8hg4Ys2XoREVBcnS4a`@K&=R}}`?>}4l5NjAx=oBPs@L2SYHOQ; zyp{pa+|5V9gs1PV`Ix|G{Aiwq5CyXin6Uu2hIsRfmO^c4RjkjMboJJ;uTWi*Eu8K049F-qZqg(F~h5d$JXE-!Y1rP)7&&TbTggC`~XU{0BqB z5Q6EDdu;@(BrPB!6|6VoAivM9&`#h$_ev=`+*HB8cAJU)*KT>Lh@o9vuH@93V{L*s+FSjJ;w%+NE)RZNb)t&1fcN7auB82f2Ln@!`L zVojcd`6{5UUW(0wcy0kWJ|z8X7#5%A?dU&^(nEjm4|&g;wW8 zleZSo;9*m>&z_7N&R%9Pg;*@4U@R63DHs4`YX2Vj(0`CQg5Ur5JpCC^IVg#BkmG@h zfSeN+hl89G4u^xB)Bj@Zy`!3HzHs66gqi@Mw}enauhM((9SMTeNUtF@MNJ63h8jUs zR1gplREkJ10#T8sh}b{_7F3D_QD3?7`~CZ^yDp2ho|$Ck%+8!SlkC}hKbsBzHYYax zYr_VtdkozIi#`0e&i_^a4bFytgR|kEfNc0DARGQ)+Pr1!pjd^6&NmC$2T<%^%R4I! z_LrN%i(7|S>_5d!|2Co*`{MRLtqqH%bYt0H9K2ZB9vSv3-R7TRPi1WHzZJ{YS~~ux zmx0x^6-h%v{bynjk25 zH^eD-Q!e|&zNt7qA3gl;a^6gq;@S+Psd^#p@WLf5LA?l^ML4q0FxfG zjIPTsHY*q#UAx%WiX=lK2Dk454X5I70VnJsVxCZ7O}Q`E)bWy|;%r+7?&o~U_B6GC zO8sYRrBbOfFas8q8cvhTe9-zHwn0p#-T+JAxyyFKD;O`f&=Mc#oY8^Mtj>;UEX8)1 z4PtD118?xq?Q|aEIHsbobDs0l0*K5^i(zlXsyH;51pGP<3Z4{Ml=x(*0Gp8ru;YX$ z@tx`Va>dBiA7o|+Q&;;WK>|YKOj|b?Al7av0MB^01^clkfS*yB+vqp$(l}=x*-Tlg z+XzS7Ygg8jFChVP0l|q(rmO-vJHOtUq;-y+!;f979G!f;r;A~(v^O7j{8T=D(fJQx zPY$(L%OX~=BNg-=T;&ano!!~B%3vZn-B5tC6qKS-C->HZhfRpV&9};bRLXTDt_+lY z)>POm_Ax-0cR+T)k0~pl^$n#DIFD6UaCWZapy0n$ko8=Wy-V@|g`jMH(qGTX^IV2v z^W4&@NL3AowNm+AWU5UfsJNP(2k4U@ouUw}X$USJASl=OiKK49`~Aw1s)73JC7BnX zU>)6Kt7a{-a$=A*j@h_pNVrR*zZpb2J*F3A!iDKI44r{5Tqj?r-Htdx4h=`) zk`n@Fj#%wqIsZADQpAbQ_mSE3qP5jB82qnrSX=_}DH5}dvP(`2NhBpkp-8IT z>FWE-pbtlXhs2V#FR5!Ov%sG;4`UM&5(B!6av$nVm<>+0DN&-c^g->Wi0!+|iCo#1 z^xyT6knts@3w@L&kj|Z7S%ny9=?(5N+;+8fA#!+>s~ZlbRX;*9q~yxz&`hWww4YPd zyj^>ymB=ab9fI}6f*WZ7gKFnU8>k{niCAxCV1BIv53S0`9^_;|GTCWK_was@Y&owP zA75iZ62)1x*kccou@8CyYGZRW^n)^M9k0=yG13n35&!cIfX{r0D^<|Qgi@0sZT2(q7|#oWNbId?v~tqYI{kiEVOWBCV7#3uL>uixyw;d&4{&VUXNSf^sp#axe&OdQMNHEa@=BZ0BI z++JmD87Kh#2~i>fGM+%lc1%TcJFaN7whDd!Q@nf?C-VAay`Sm2nFw2lQ`w6zlO(uT@AKB=GIYT+90n z1hc!Y0f&G@FN_;DJ|R)hj)}_x=}CYN@N8jXZ5jkc67+=gSfkl#r<@?!_v~j%+}QFWioH<p!E4*;g3c3a;Jy-m( zyccRSP$rj&d?X@%&>}u8ucUhLhtjCok{Ub1-(;TBsM2Va1Qv`#0LDB>8{;JpupdhD zVwFY_7(2)a*YQ!O7kLrWHENhR&FPW&Y&r+1%-MTzHR6t1SyyZdj#8F@WnDSm(%EpA z(kEVqo$kHMS97FH2@{tDoK_caQ&pSZe+rwqT&6Utu330GOs?#(A_ho&Xf|<|&s=S= z3=Y`4&K7}U44|_G!Q`=k{{#X94A_XZ2QigK&7}Y{|6XIa1h5f8k*HwTc(Hk`f3GTy zvV8U&l}0rT<(gNoYQ>jnZZfaN2mC107>AZs*y+GjK%Gx+RCuW?1(qop_$1!y)IJlS z_VVWy-=lO5b9L-`d9gNL`z2vwsMij52_x)jz^$!r{e*y0nhaz;|LC8F#iH zk(M~^&j)EMZ>wA0km**3yAD3S!573{;3WVORTF{njSW1sp5YUJ5@a^WGCu+e0&_I8 z>1Ey{)bAmL#vGN@x%kFxgr6di5*ROLOHR8%PC$|=s3LoTT?^d;q zc=l#B0&Rr)u-~LYtDM=J)%N3?`4AGNIMJd}D}`iyprk zuJVC&Cy#7Wc21 zI9jCI6-+q^4qkUh9-28S9I1D7HObQJwex20k1}?Kt8ImCETfO1(%&#TRMMp>aJQ{H z@V)b{7|P{abl5;#*kj#0TOu|WSaH1CWGnt`JU^TU_5gXWzZw)s6Caj??Lvl|%d0T} zOP$m$p-VNuyc{W&t${!Vxi{0&N{L%-(m$=bK#oN;GUYD-^;EpzL&$FvLXE1J;!J@u z?sIeSG~GT^=-36aa$2HACc6B64)7&AEgK*Hn(9PUbLDoy*xQtfhZ-+sc5F51*MXrM z82AgH+ZY`UUitu3Ss%+>14WsYygt|njlK0~Rv)Z0dt26+MD~T`PiGx5Hs-bdGdy;@ z@m5k8-*;Xc`imKOY;O_8;%GN7*gq5`-)DS2E6vIYQ#*@bM^qL%^Jl{eTO}ia;6I6U zo<;Hs0D_}(ms{SDT(Tp-aJiiEQ=||d=M?^Y${lLygC7>Ca*rzVN>CIf&QDj9dxF>F zIp>g9y>u)E9YOMhzBu)Z+mM+J5DybX&FOx*HwW)_ENvJ#0T4}HcsbL+DcZQ!OMj#y zi?~J!X&7mkd+c69l)V(&RS@)Cy3h_Jb?su;w_>4`Yt|K1_<8;{{O@m2%23pa>~w7h zmt~zxO6jTg9IP`eN?z$l}dRi=>L&xIXW&}=B>PkLL1ms2} zs^p(VADQ}(qK+!!ekxk11w;zj&kcehW(>sIcU3QhtpWMbP7WNCD^2nR^#Wi{Z9f*? zKc2hAQmo!eRF>_*P<(rre#sE{=8r~##OxpsjL&8WPXaiW#9(77 z*kF;^+`ZtyT1-4Te}z)bB_#sZIP$>S$;0l3m`b7BuW@4@n)*30YilvXNsUt%SYwsC zrO|@fo{?V3>Sm6JHH73;2tZ178MWFc#C4O&FES%z%(2p~P!d$<;QI&>?S?V^&!YtjfG(%&%AKP5a~MaCP6jNAV@OAbeXQ$7iv@A{0Lr;iB883f00sUv^; zo#W8xu1$5kO&?n1QN(dh3jGjYyuY~!TzY0Q+RbGlY<(pBSX@$8opxI^q}Q7H%7tA~-%F3`=N1c3#lkWu67|m7Jyjg#u^8 z4cdmuHYG)OW;WihvtBz(f3JkWiAB(>?Pw&)Qg~9oIOpMH%tX=9YSj(pVjbJW%samc_aJT6ff0te3Da zL!%izjs4E(T`hZ{u03drywPup>Jb>i#Os zSM*MJy_5fp#c|KdAmCA|dr_X8hNU6NxZqXlNN)EAX(a{dKK}js6fT8UxC{8k($XPs z)#_T7x75GS>wRV?=GMtiLULRD*3nCMru{T=K5!i^m48L1RIM)PxSz_OdoHf;BDcIE zr%E`5$`#SE^v@mQvJm2Nu=*$#mR6hQXqZ!R^d65w1@D!lS9xbytm_?RhZFAG^{Pq;Cy z23dXV!Ny`-7L7)83En&H0}#j$e^Yhi?CX>)*AHwTb$j#a#qv+A3rkH?6ij9~S}tc# zRYE)Kt{Zlw&~B?YoW8}Nhf}$^&B&GzAx9G%@q6=qJ{HruMH=&kK6Y7JAy0fOvM`l@ z+?*%_mG&6KReB_wckK7(FUWAg+E#y%qk!;;w_w4JBKi5xaK-s>lN)(T!hnuneloB4 zV3THp;9{`I=vLjfRNgKWI8ND6Jyq8`-Apsi*WMU*8 z#q0uwYC;;#wFLfG6)Tk${%d} zJ|>Lz16W4OPii*Ea*@|9DUxVnklV9M>_lk4uRF+)pmKxB{I_!FaT~}b!qz-aij*;v zK96JnSFGg*q{~$(NBJ&wJ$_tUp;-I)@ni8!Y3`iQSW8z(5sE+YFd+Wnlr4lHA%GCI zbiME)(`CgR<_CrNcAN{gvud)1DMmWMWJayhpI!tzuw76Ps|ED(&36|uuX7fC5gXtb zu55{TWC+d|gPT2!k#ba4Bs61b#@GiI9FRy&i-i@cWL5^iul7aE)pP$*lCGEGA!HckdK2P-QkExQ2m|baTYSBZdztn?dw1CYSEYm)fQUnb z1IaU^>XxvhPv!lqEXO?|r!F?d4TsZ}<9CybQ_BZY3C`|aau+aj_Od67cfN#mCwz-Z zVs79MVqC&qmC7m}$h#g$xEVMqxve7IT63NDliV*Sl(?4-voALw;b9r$kwG$WjPu;* zb=dIm^t>wW6;N97pxx`J_r7j_eX#clgm9o|Mh?fP2U~(Zy3VE&V_$u!)p`MAZ+x~c z2_ALARx6xB9UTOGJL5!1QxlE}g*@#@);f>kZmrLFEOadVxGGhLdCWZ^?vtmL;a4G> z(7m|}SJpI<_Iy@`+A|#Lijp9c9Q|q(cv%s-_=ZB}hMX1(3I5{YnSnwEj|j#z((hM> zO!9_E6jq`y5>*7FQh3o^=3-Zg%*N@;2R@Pi@u)_=qg2zlZbS`5B-mFv^+a7#8i{wX zfBHK4GhZ~Ozq^3rkQ5%x&LcSHbe=Cpq;$JwD+bsR6Psu3c+B_#w}0$$zN;*w);LN` zoE)R_eiW^Fr8H*>$0Yd#8at{b$@4Flk}8o% z>z1#i@d~8FX8KhHWX2bA8Q&0h@@!U;135j3gn?^CMn*>MToDsK;4?~3nNUPpa7!P0 z);40aG9zD(U2nX#&z1T6`~#;A%1d3;=hIyJf{R>RlocelI#1_bmK0Z(1WSiI`=Fj5 z11Dz#TC)rD#n%E1%U53>X@U83nJeWtjLnB;Z7ITvq3K-L<_aI?*JDM~_r~T|VIaU! z@J$E1XF>PPTvY}3Q`W-kaxZKWN4?uc}g6kd;>f(#Oce;<7u$Z(P{s8FShPKxN96TUsPbwOdpCF+@u>*6rJ8Ir{0_I=8#K``I^B|37q3&&>xj z9J_p`y?#&11xURx)G`>lqOtt#4j5YPL^`Ow1GZP-bJ&TYV?x0<+pN9hfgHd8tXK-I1AuEN`_l9d8o&!uh#O(}rm1wnbfzsvT zRHl?uAl(rEd0EuxH_9ht-7ma>{qfDQ#5(Q51F}Y&piG%OzymQg8^Ju-r2lnX4t9Y)m@1W2QWykoBNEXy$~QP-KAfbsD`*p()G z>&Gw&=Hre@y~)We31<1^`*sU>THx~+xa77y zwD015j{rW9Jed2{j6(0l2jHQqzxg_+T{b}_C;#kNehRq(4NV~S$>DSJ>kfh=-+S1h z%nnL5x(f$fV~6(oQ#MLrJ7$h;u3ori6d)3pn)ey}kRiwVxLBUrwEdRz@}ed6#5 z2Ua8hqK?ua^rK>$K@5X?KXjg`U~n*;;HZ%2W~sn3-TVHSgqgU1GUTh#GI&zvv;ukV zZs&EcRe2+~5cUt~fjbISLN$yBPmkSRPzs(U`$X?fR06}}DwB8n5K=QuU#}|S@hA2M z$IJs??(EL*D5l(El{FyZ%T^v*fPy=t zca3!F7|m14k1lMdMhJiM}v_{^D{VutBKE2~2mjq|tG6gx* z+5fQJU}z2&ATrzm<^-0lEt>8Swz_e-U3{_=Vwp5QTkX!*KiqXi8NFNT(cIz{<>iU~ zmA7(7BaF{)8+>h|&?715&VaUG$Q*rA~KRt8>cFD%b_pAoK3WD(n)h~$0OKH?9{ z6C&y^W$d`cnK$-fi)Wi27~IkcN;(1r(w#N@eZA@h90x@zo|&pUAfmFsTBp!Wv9=o& zDxHHD6U$$FwApTND15TVC%iO9ihagK29klPKrRr;F%2l7R;D`^qs}C4L^CrhI9T?X zXOQ9G{FD4OKOnLfeem#_Br6+I01nV zZ)o%fnRQ&1XVXaJ=NE>nupEotnwPRX1hTW?bmiLIyjV7U)gmzCZo+gKOHO+74!9qj zP&|U?_B*1h(Bc3gVU_6ZPw`JSRDih!cqM*Q%S06~ym}dgDWak&LO|>pd3SfwmecrE7GWJTZbz<-U3`qMO2>BlPb`R~^ zDCNuHLJ0&V9{U4~j8h4|_!#$FY4(~D5(ta@HoWW$IcVeoDZzV(pN1kzkGCTR`Zs#H znO&DVy|sH3U>R6icl@v0_rAs0&$fB@^I4sLAAYRTOC7JPsBlm~0Z;*|8}xFspb&6u zTug3dc!`_+-S6%>#^YBtPvn-nu~*%gtAeNc9FCxAmrGVMTQD|Do7i5&^f z+6X0cmDNaSX=qOG8n0q;5|Yn@ZWOZvfm>QJIBDn0bF`4!D%)@Yt?C6Y*ZD~x(R?9P znV0EsMsN@?1PEDE`c`d1OtyO4T`}}6-T7@y|A=^JHzPO8uyOXCP z*wK%x{Qd2hBtDtH%2gB*hqL*qs6W1Z3l`Ja^F8z~n6!#Twm)ZS>$*^{S+<&;FP~R- z=(7`Mst_(md$N>+`X}b}TM0Fhqw2`7OWfS{f)yW=jSj;Q7nUCpy7XRwNtjOYpc}ivF>oWg zPMVkeM19v7cwk9^pQ|A&fz!|;aYcj3*?T>Wap*IDokhvJ&bjKzD7!2x-^-xEFXm1! zT5;dG3%Ob1#qAlDOYzmgRGBL&z%e@U5^S$R9eaEGN^om^fJ^{NlwO_q2b@SD^LD>A zny1#kcu}craqcplq{TefPHLB!VakqLjgl}5m5WfCzVh*?+o&Na}xw^S~{6kMKZyzjH1^t+|vFB3HyLl`i z@1d=sTX2XvF|v4&D&r~^9mA${Esl+gPhdQ={|ZS=;vrdSh;Ui=Labn55J#?8!HR4G z+g=N1tGfS;$80FKf{C&rhqQQs#x|G+#;UyMMw!?Gd0Z7_{U`(7$L2L%Bp*(?PCblO zfc4jRXRi;?XuSR0Bz7m-oS3IAtqHGV^@>x)uwYdT!*)Nq*w20LCjiTKqD^BRoo9!W z%pDm-x6t3w-{pU;TBP9SKE1hF6)Q+5q-gII8(SaKFeBpTW+Y@09QCa8+?q6+Mp6IC z5BfwPlwrmt=E~nExPOEwcZzS4Tesne=VWfM+u+Iv>d=kWaC(HEB;n-s;nRnb9H$)9 z#@-_NC&n^1_x`SNb4<`4U?d#5gq#=;Hb|V#94-h_e}I}R%pDS-#@O~K1ABtJ+_HdQ z7mk75#tUEqXBwy222`4_cR~ydIJZLB!=u*Az5n@q#T+>U+F?dBPH_;+e_LmGv$X z7~v!t2~d`mhR41f8Imq4(zb{mE2*j9cZS0ch?0F{6hF;rsL zd#;j`HJ;qgq4ESrWYag>6M$?%PbA39J);5c5m)usyMF3nWdT_fvDJDI$aYvczCbQU z8&GXexYCf3e;B}cj+qkUiY!e$+%Y&hE3jhMs3e2i%^{o?OW$OYN$(djMr}G!9`rk6 z2)agAIo#-p>=1mY^@{+ws&EuQwR5BTiIGaJtCz4o;(a0BJTen2^XrwMf?RRag#*h% zRGl7AnV9jncREHTuBk5q>bQ!3kRv{FFBj4Bv&xGpX^nm;qC~tmo1!7&17>kkXcMAc zp9}h-y&WE+WNoi)yr6t#mg*WP)kJ5EIr7qn!_5{PrBGy1S}PpgRoPlnbd*x9ru`175~W5v=LVd++eVF_00-z5CzPlM)h)sPuGUhHyw|&~(G@r> zj{8&XJpP2(Xcr6@rC;TO>}H__O47Me#Q{4!#-6(cw5)?T&08@jiz|l5o=`U&dsSF zPoWD&%a=N}6gghD?g#L#f7a-l4|3$@SCJi2`y7#2yFJ0pcb#8)>O#=LWi1w)Hc!u=MJjr8vP?qVtGW>0 z=s7LTmXx&NpVBtMh~)-|f}NoK2HrD;btRZk=22{agVW0gBV10Nv9?G|v2zdRjWM75 zxg&aCsouUQXwsz~;Ac2k6THwsaKZl`WLyXZtE5OZX>;P}^2ZY9;q+FQ_vN zeYi#NUCwGqi-^JyTc&M#^Ad{1uXCqp&fAmuZR&csC+KrCd{~jtJEBYUASxY9@3GMM zF;-N_Wf(;kcx@3-?(F-pdv>`6mBwqYGZiOr8n622LBdW+XO)&=XiQ+@{9j{W_@%R7eW2bV3{ zhw9AUdbr*f%%iZXZZW>Yb!h8=Cb!RhsrlJngSuAkGPvA$AC)_kC32G2Z@ilK>WRvT zmw9JJ5TYM{2#I!a+mYTK!=O84y8X(^);YMbx*s2W&gZ@mEDkmjRP6|90-F-ga}3&C zsgv)pgsf9v%FAcp{)sE!gPR zX`9$WV`arPYmw~yfeWK9P~0gISpp2+sL-Toh61O*rpIU$IY|sk`-l0TB}t<5?`wV( zDyM}4g4V3L*Q#>r!){|Y9vGRFR7!)-iHwHf*DSRJ{Z(8OeYn@YN&ClN>8?y{v=Aj* zd7jUHF@e-@?kZFV)uX^7sB8O{m^pS7LXEq~E$P3_{B!^^^*QekXVYe+zB=*_ag7(3 zkMe~Q?cJUcIKE8bV{VvOSO%6z1@k$W!>qKW#Km~wDwnJiM<^_w{|YJR-1~&QcUN$R zE1HTc!+&1qCaMLGExmEX{wfeZnad%a-1i)1diO5q$b#at=L5GC^9so2jPhLFY`plN z(eJDK`3p;pqWPYKe2El>>e#9Na>e?^j-s}ktvp-9zZJKz50>dYRK%a?HGAwzZg=0b77Kc~)QgFcWtl%Su8|d%fy@I)dC*}r?X_Eyr2G4(f z?#^HClR&o50~bCXpNr-7gG&XH};@l>lw$`7Dr65){nqsCXtg<1r>?5!p+JhXr$_JapAw}FpO2W zxe;|v7oTxknOpPl%m?f})YdoV<&CJ}>!n%{#1FWt0;;x=W1o=)zund9>^bjyA53h6 z7bh<4B3dY>e&*OM9KkiIyM4asW>NjN6J-3RvkXu#%7|Wmt{#_TZX4+8k)2;GF%!#@ zrRZhLMcJuar=@3SDqnE8ST4^FDf?EA7F9=dj}N+BuiMr=t8jh_g_0L|JPfxJrYAC`{W+K4QBQp6T0=ln zKHz_@98Ed(IjS)WYWMlLx#5k|yLzx4*leB?onG-14rfh=%C0nZORQ~uepUPyR#-g8 z`$)hencM+`X)AVDmY0Frv#Z9P+3Hc+<7{nRO7_JvUma~PzHw`Ba-}h@ zQZ6!9UXfi0CMp0gzr8w%@~Nc*xgs}e#k+br%N#}s&u;>dWoE0TlE{GwhS@YIcC>H0 zdEkw4f~j*r|J>P|p>fkU&BjWj;>P-&hBCtzjv0=aSS7i{v`ssWWg3<)FV2~HJzYw= z*k_*^`wANJ-q8Vi2-Ul5>hk8%cU69%Jn!rR*G)Ol3kw-8?I*uxSLYi!{SUXNIt)*-)nY3(ri8R6XC+$0>NIg zajT@0O>&$KVYUeNGvC+^hTxWNXVuD7>l?pqs!;u&*N}0MCW+t%gB#zfAo83gue(lr zUxuI{uTvzOLm=}ID9s7oXZ_tmhM_Eu?hJ!QAv!E@ebz~`9m;a%>QhA`obk?b_IL(c?eutpNeU`6@MP27z1taFFO5Qr=k4=vWIES7`8e%Isk`N9I``+8_@Ck`-`w_C$85A;a?WfM3qz_@Zn;_HVSfT28P$0W?!s900u~ zPb5P0&C|pR04Gwod#>96)@P1+-+Eq4FNZmz&_okCRnf@aQRZhBd$&NQXVR&TbRNp0 zsy|m^3x--Q(O!wRlniU1K3PSNYH1VVz2j+a8yUHakc51lFe;M4`(VTye+SWhVk-q$Jr4RRW?1-_w!~nYYVQGDe=10l@o}MqdDx}is zRHV8a+;c>!)2FewuG>h__cn*T$xFR@-|iMFRNl(7=ZM3Ht#1E;xRSa^!Kp*18UN~I z;*Q1M(F@G_XEIxGw)3Wls#8;M-M6A5-^k0J33vUR{9S%6FE#nPxAu8%Qk_r6bUXbW zIv|7#4EnU^^6cPrT6vf!J1UiOGr5J%&==YrHqX(|R!#F_dY&}?&uAD zve4&Lli(9@yw9n=<;l^jdQQI(ar0Kb{+|eOB_2-x8-zHYK41UT_Fmg4Cx2VpTs%9e z8dq*IAd0IC9MJ>|o;Vx|0MbJ_jOMz;C`X<&0h9sDW9oo55p^@U!J?8>~u{bYK*rfID~Muvqtp(a)QqC zs1Y@4=`D7p@Ox^o%pPp%KD>n6Z;a}#Gwn{`J%`)-P`Y#Y*MYwz`zgJ!BvH78AX4Hk zN{idk#FW^JMT(`1-w}8H+Kt9bpd`gcrEuAwISHDDy#iOUnO#R<&2T625D}mGNK;|i z*XJ|EMUFwR=$R8055Oo8cejEA{-?f><@vFC_~-i)Fb94sV|lP@Ur2(H!!kZ?Igh z<+*CV;6%5}#TH~J9$;l51wrc(@!>Q{CJ|3OZ58gT#T+{f4-bEjBVodiOG|S@A4o@H zmy)>stzXGSZ94kv=R`-P=XMl|hsKbiL`0>4Uykzz6f5$7nOjAf>Ljk*L&FkV_N`I6F8enQuf_6|J_rfre-S@>~*VFGd|A^yz9&a|{1Wrtot|i}0 za7>P(Fk47NC%fXS(;OT8?|RIAt4kz97^X`}hx4>L}hAVwn&DXK4ei^ow_Hyq^+ z5M`N}EJt2eX66|K_QCF%GiSj4IpT%n>#&*1fY13^MGD2VHKZ4j>p_V^X>zp|whSIn zZ_Jq8I!E_j-mR?sqT^mJ4Xt3OEU$!A#(=8qRCm^8eOIeJQhc5Aia90l(wWmlF67dO znmZzTs^^^} z6ekW)qXf!>e)TY@E4t;`@5(ekG4j^ni#caT7QvnK+`U4<+rA?Gmi3|uMzgcJK|wm5 zrNf%Y5zQ11YStHTfB@GCFiDr?ny|K7M4fb-2*VSw7)kGyr5v(OYYx__vvAHtbcmoUw$aN?a|-p>)VU|V?u zmJ8qO8x8lKDwwe^e8?53NJ^c9z;nQ5b#>aK-6KydQU!>%` ze%#z3+Gq|Qd6dMl>8823Ptex=X^iN8b}*$a;WkGu+Y_2kJV;KA5#c9m z%2@qWp^i6Y+^e=_+;o=9dk2`tOytU5J1@MJz95y7Qkg$q=jazAGHf}FkC!%b&ZLb9 zU;GOOT3=baKiguU&JA7ttuj#q=eT7$R{Va4dwz*rF)sEk=*}B-iBU||8(PvNYb#eO zFMto=cfea3AiuhF(U%(Q8O}*>#h=y2iy~M9F5^ephhGQsW4!5roHsXh)X5A5gYrRz zTXKg_0_hBA*^Vdtj>{PrXpOVFD5ZU^#&B`xn^SHU{e@QgMFaXViNgG6iujH;NV+SM znqB#Vs}D5IUsemF4nOD=sN*I)k!xlz-PC^rgR1^A$qv-bmyJi_h0}{<_#~2c@;Pkp zvRM(9nem7TgYrWfsd^|volRU~9&C)7A3u5^4cGxFrCj5(i#&XXp0zf;^ujQ0{LzNh zD|^#R=tIC|khJ;oN874!3xKFHaxC#x1rlBG%a(1!y+PMBRVawnmbF2%Rb>$Bc&@_k zocbqf<^lU6?9R)~Bq)TUCzN%l)+wV-uI6aue7WNyD(G3kpk2QgX>HMgQ6b^<cJu)a(5b@g>Cnxw!SBkmhVJ8oggh>K|-!|F==hX0C5S zIB79In&YPfNK7nys|e7arg6~%LRMpcW|VB=j*k7LeXWcmaGk1rWY;?@NEdn}L1N0y z%@_T!E$JZyW3e|C9_*dpx)dBIi3eW&L$%@jniZBY>8@ z#QK)4vnmw>MDtL_k1JdX_0HS8KUcZI`4J;_iHCFm9L?5>G;KcXv}djrY;Jn#Jc;z= z74zx{1yOSa8m{lgVXl@zTayl;u3JU77Qe{O6AfKSv`%C*?y{2vUPJ(LY584pmh!<@ z-^%c>h9r3R-rsx&2v`|?pOyO-RhM@KGN^!iKK2%!e`)lpFZFjYGVjI?MD6D=;iIBh zQ*CbwzW~6^!YquPb;fsOwgJ*+Kc5$YlvBX@0uFb&uh_)p;dt_-Wo4;v1Khin0ByLO zry|wSiRru=BIO~4c9)?HwO6HmwM05FJLkwkk_YL>rFBf9U|e3`Y9WiYy_QGNa=+Jy z5x(3M8N#;Tvneg;(OER64fBNHqFG^&AQO|<$8MPadY;{GHLnlFZb%4WFny@Py>Dz4 zD29oAGgRsC*5wTyd42xn^%8TVc}}qFgg$^wH<~Aeaf8zj63>z$;G-aKRubTBGP@%p z(l}8WQ3Dw>J$yOFK%?lWV2pCvO(j$jr?fO(!BXChJ>7!$9AOFDBo7j4gfc>6BV~lr z5*p`Gwk`amMYqgtRKN#f#jrL{0eT-=CjbtVWHquweSF|F=>(1AfFO>r>Yn?!8<<2iC>4JM+hys9;RL-)M+E#~L>mFkgu}Z^!S9e(eVbzP?Jr)PLAM zwR+x_TVVIl+5Jumn|pVoHB*g%-w0?~&QO~RrbmvB_E@HTyfh#ZyDoA6qn>rJ-U0jR zP$zx%IYRLH{Qi5#^<(~w|Bm@T>0v+_cZJ$N@~8_k(L8PYBcN^{CWT>pr2VeyzV7>D zPZ%Z)D(FZ@p61P_=&92TG^DLAN1cLZGW9g9eW+qsY5yn1H1#Hi;n5>u9SQ_A5T}}7 zYBM{ea0F*=p+0*(=OaOZp`rS?$BkhX4@G~^8RJ26MqiW#;>(s)jkm4Xp(d(XYVV?6 zS*jQDYHERVzYb~(9nqf|jkM4{p^<)qOWIm;9SXSoaO6$I?JmuR<~#b*LPA`dtR-}J zcZ9-sIsoI|n}fBJC)mDn+!O|K2L?TfAK(ke-;Rvpi7sKAB4gOLNVes& z_GLn166pfBk!UjcL25e8K(y8+<7DQU;)k}blq{a?+zcE}V4q6M(FM)g)-~sW^9y(i zH`)Efi;7E-INa^>(lT~V+=`FAIk-xEhfrfvPY_<(Xxj?chN~JH{Q=0XRn^9YZ@-9YO=Wqxj%rakVmM@a#U7G&xb&ZDuK6M z(eoR-w1eGYhp$-QP3+DxZ8J%^LH*EJHs=tT{i!5-X?MkP6q;RPFRG9()|_$CDN9To zEg`GuZNAINIm5k?dPoPFt3=s|yb?St5a^Je1}V5jriYqQFClo)4Ad4UcMi=zoPvSfw61_4(F z)TMZ16d${PI}tKnFd(`$Xr%hSqVEXc@N!@m65qr-yQ2AU-Zq45qIOW7Z3cTMsQU*X zPpg4`$Thf|WVRF8_L5}q^5Ah*L&uo4X^#_(Rnmj(7S+0a%N#~Dzu9GeS2(Wo-B0V< zj+s#yvAs?EI?oJ+yS1g6hX>dwnsY78lD9D=xUo@~$>V1d)VM;_5A2%Pd?vxsu#&Y5 zYq(^!cVVEX-on(P9&w7Dq#1B%G-4izcCHV)uR%079ApmWm6g(A&??lmlA%c9fnw>R z{pUL~sJ$%BmpVHbb-M8DhwCi{^xD9;*}iAa(84NkXU;@aVh!k-nVB~VL}K*bAz7^| zO4fcuOrQDj%Jk|C35dXxlLM(?sc(Wdr^q-`Wj zK=}zB2|z2LRe4r%cZAc4WB3fEb)t^dvfEm+eL~h{s_`%2&uDu>@aK5bkQjiQZuqm{ z(YUpg%J9Q5bJRH~xrWmm@2Fs=2zHy1dot_3s`ajG1R$cA$f!tXE-j`<>fKhIda>}+ z>GoeQXsSva5`0^Tbg)!p5nfPK6exCFHnuINXi4Y1FLxB}Kpc4Ru^MLQ?og)8IP9dm z`s;v@pg)$;S?Rh)xT35jA&J&wBW}NQ z3tIpCgIr(!m+1;@ZKJ%w_ngp4pBuGkcvl(EPpGlZ_OiEVJF3>4y>mp+QSf$&h95U5 zfiF*Er{>LGGgP8R6_NV}&!##}iaC2K$`{lnoXW!DZog)x2^N^JHkcUjCXuD0HDf-r z`Q8Kc15Zjj2}>#vEyX1k@u;=Pq3(_6SJ+rYDSzn{*^-7qgtM$@fO8?J$_6}ZO%JAVoR2=~KUMKNd&a$@m4m+S zZopm)?(Xgvy^pH=)mJ7gDxvoLvswsvwioh9#Eg<(v>!VlrabrC>|yB&eQLZ3_EXt<9*jyGr>%!wJs2L@L@@E`?x z2eDm}?;9!vsVM~OSSq>g$On_?CH$07ypVz}^w!=r`A|Hu1R-ir3w9zha0;T~JMy38 z!hgN+#86+B+^9R2^{Nyu=R1Srycmfed*Xjz`a+1@w_7!D?&!pWqb|fW?ltc1%SLC& zMyn|>uExbE$DGjZ&*uPz5gNxBkvC$K!t-#Qu?`y&w5AMPT%Mi7h8`|11Xt$cnPDHl zB@^$N0eNkcoZw|4ld$2i;iD~+xQoSB+c_kP%Oq9%I&3sylMb;Yb^-fNn)J+J!=YJv zJ^8$JvV?&PnCvws?IL{~i|Z$+lAfKuR&t|Yy8M$=D)nlFiFk`)$>u23`-t9Ny3B5{I?|yPlhPp7tC~$ek*6>F@IIEFB88=fCM^% zZl9TnMM&vN18vLCXiek-PQkIG5UE?klp*nRqnBBAl$+w1j8fxeYJxbml7S}Ii{Hq? zYp7Pt;l#844@XxX2-Ww+XJ5>i84L!4v5sZzvQ8QM*oC49l`JJR##U(ThA{SaM4?66 z>{*LJ_Oe!%2uVq0Y4e-!@BMMlz4zVs?!0r~eQ(}9=X^d&x{C#APur5l#HB-z9>ZrT zhe+i0nX4pch$pv}@6@=2r#z#+OOaw+p&O-s7yHC0bmM)B6tlN(e+^H22TMoE%*uEL zrH?fcT@1g7vxV3(pPN5*jfQ8u3nEbGEL<{t3Fyo)rI>6fLMEMHtAY)(xil(zNd;To zLLp@NiDpr&uhTt6v)_p@;d$1VvfqfD%-J9{UeC{I7x}OCZBCRhXDC;-B}iB$mn@u{ z@gAX|E|OQn`;9aBynT6nay#Mqu37l}W0>64jf;2a8;l023!f#s+@`XZ$OF>2N zmQ<>#RYFC*7A`T|VnxB?m~7&Lq~PG?S%J$^&}o4E^&y1dW^Ur($twbk>q?Gy8~~+V zFgba1mRD(ot@vs$q#AdqLfD5?JLB#Y&0YQ`(0Yf3cDo~ll#sFSzihPmI1GIsW=yhf zN6d1CO@nT^bH5{Z9w8f=DmE8od@vkj(@p9kUyCiq8ySO1ackL!Y1J>F!YRmB$QjDMJ)pjKZ5m>GHbKPkA5)$*G0-^Pyy5)ER z>O22SW1fCe5ie&#Gd?grcTb(_e$p>fJT=?ZW05bFlh-GKwG`NzUJZC8th$ z5VW}|Xy-{yImy?I!8Y-)X>RxDG9ZX_>2Hs`=bt1IBJw^d1Dz`L=G9r;;o^B99AU8T z=N1VB2r_yXJ^&3D&#s?fFPg&)^Oind=g5h1J}$3tfQEh#S5{ev1!{?~z8#`tweGBtcOetG^G^yCq6)iHIlIiu>1c(qgNqFHtE41p(9?%qxB zP4RNPe(xTSehVFQ>^{R#EbsnlsRuFI@}dtsXGM9!rG`hWuD-}9efZFeyX3e3cx^3@ zm6f9|`=~u%rty*V2N8w@o+Wt<_g|xmCr|!s1U&WhDfiSOXA`3MY_6B%_3Xota3en- zP3_q0xAdd?&u%T%%k7VG6oPg`8j_vh(RD9^bN+}lsoE~N22sLx)kIqyRp2p4Zz+nl zzHU1H!cZN`EB&*dgphM0nckI z@_~kpJuSc}nM{?L?Os`V6VKG_BV47)LTb$W?OvrV6Byso%>cPUeX;svuOUypyRPqjmxq-^TW!1T;6XKC5s}F(xsSUw`mT(iJ2)JO z@aJu_E&<%8N`PeAIkH)!sri&$nYQe4B|!EBZ)sS3Q8NIyJNe^8qgfBdwP--XN3imp zcrHT@LgkoLGK@n#+U$OrRC)C(xF4+!@?t0y-;KWK&gd;nyI)kyt9@|0rjS?r^q~T; zva5Dffmiux{&6p_a^uOKU!FVTDdzXH=XrKDl692dF$%87&NbKp1J9MO*7{noBz z>)nfLSL#&aphPyil#ts9DV*<%+x(kppKHN>FBMFRx-XvSm4WZ?30rmdfr^&2CD-G= zr5Qb$?W5pV{59HME-h|GsFskgmF_1^s+!9)>hEt_l6Lo#9O@|M@~4}5NxaAdrH6S++&3`8{`{zAUsHER0a$GDO{YSH#99%A zN35$~Tut$i8Wg(x&zPU%`eV^`L}2y6YFcN@G)aT9nlnaQMYc@hu$(u7IF9*+!peq~ z$~!CVNblh6GXfPRPtakzO^1#vJCpM-{jq>q1*o?@S?0@L;FK)~WCk7MSrZ$UFFkv0 zow_(@te3_3j(XRhD^-$#yH}1Ov#-0wlg~LNVbZDEBa6)^$!P)R`JsM`saQF3ia9#? z(q(-x=Ik%GCU>T)ckwL0M$!oJnnAU4bG6pZSRV*JpEk#O7XhZBav2ls?@jK(ijp~61%=NBzE=EI>=H8}*xiL59-kmp$;ikksn~SCT(bePP`{rnh z#ncg3~w{)J4v8(E0kxmQSQYcp#r-)J6E>rew80@-^AhFDbu&Da-hT6_&v*`?NIKGdAff zwkR)j;?v{_d*eLdNxy)<*4m^rzf=A|UOjHgziY9M6W}SA7-ojH3AC_on-Dc#be?@M z;d2IYWs%;x7jVej8`HZi)0?!jHa<1Z5X&<^mYY*1D?D=H7+KXzc~uz1@OSP?ToLsi{%~Z(t{*mefCYihV24uwz;o8e6>7wr?#fK#UvyF46%li-% zanGUhrCEFH5DgIF%|w3MV6Xb7Sr38S`l|7I?5u~=1#%zBF3;RNqW7X`h>)GsH+AM* zYy$u25O`icM0|Dbu9r4VcxE2J!{zzae>iMuQ?Zs*A81@9NM(rj4FV>SJNb==#TTr+hN z_Q5*sLwNPAV)1V_4?CECm7++3twpm}qZ-iUy!uL@$vVBJ=*B^>W@koRfz~60&Cr`~=Hd@XLe}XsHk1f<7>2 zAgIS3rgCRLaT2+278x5V-Gzonn(^^Pq3@)T2nY;@>CO*#M3c8LsyXQOJ$}8skdBV1 zup!aA_3fNU%zNt{tZ1Bp4@-Yul;{i3+wL{BL~&pqDmP`W@AKg-Zn@V&_U z%Co343TGSx>4HJ+7h-P0;0QkdCwmTD#K>xf8A^$pZpJ5$8`)`2_G>}u@pHNmQ0h=H znspJ26Oy+%qdOD}kNGmOx3}TEu_KMUr7q8ewP=p`~E`;LiEq&0j@#oTQ7fv9lAU1hAA~jM0eS4kTRmrOWj<1 zf9uNEDj<;N8%^~v@`>+Ve^BO(8Q*n8%eMZcuRx?QCV&cDhvr7x=%6tkP8`j3 z8rV4jR<69*$di>EXGJF-PxXxQ`mMV{!!*qwS|ji>IH3)iXLQKl7FQcsfg|4+)?$(6=Z`kKfz2+0@cQE|czo zpbTwi_o%S>FQpX$tqB|%&K{7x_jhm4#7ls$5o8l5mgt)+-Ddt&$&e+}{xZlz#wAot zt*QEukQh2P>mGrc%?Au%-6mDP@P4Oqd!@C~lLRSslDrNYw9 zrM239YJqW5L7t8wSV?KSp4^+XfUQOyfJj1K$)6v>xMi&wjx!=F$;#Hq!1B`Cmc<-4 z={F%_uD-YwC#>v5x2mWQb%Q8sToii*xB`HJ9-=fDyqVP0p)3u;5gwu-l1Cw2&eKrO zJOqMgUiL!p*fPNG8DG=Hl<^8aq1S4i6n?o%_wT$5ua&Wd=%Mwqw&pI}cC9RD$K0qM#H%gRWfJ{s#QuwYGJzrWD~k!n6-+cXgA5=-8EokpY(qadz+k z7lcqCjc^tAW7z9#%T6QXpiU9bdwdyir#p0tO? zrvAZ9)cAXLitk8|-)sJm=~kBz>o9UV|3ytpqu6u5)Xdlf)|WMC9~Ujy~pfT}k-cF9Y|CKJ68xXH84q@NvtCB^0V89U8?2O`4s*U)I8 zb-oj06t8ofnZ1A90A?j~Y^&nNiSU4iSD|rqo*uJ5Lxhf$@^l$%j8}u5w?Jr0rkL>@ z5dKMER=^it*Vgc(Bfl(tgTtk}*NIjrM{q6$+Esv102D@@gsDZp`)7<-R=l#s;nIMr zk3m=b@@%eyQ+cT(Bn-++uN;M0@KQgxI)azx5s)B0dj{9$^Iz1sV+A@N?Xyi^2oQhj zAnG_L>K?4gE_v<&+aje4EISJd?M3yZj^R)=p^iBMvg--~p?2z}{OyJ@qqKd}#;=BMTLH81EDFRKX1}I;E`|iQ@^BU(YnkFgAtppToTbl6 z98jlcVRIeCalIEY}e|2 zyYeJ+kqxinnRCCKnM9Fm3t24CUK2vDZVOwhHa)1me<>`(GIL-DQD-q_ z$?S91o#vm&7PSd$n=~W-SVj7SZ8>fBc0WLgiDK_(jaqJ9fPg~v!jgQQ5CGRPPZAE@ zB3jx#^t%?jHpU6;)CiMa6SnYN>p0v)$A4h60&A=uwjJ*Y>jz!Pq^r%9X8kkaAcg6` z91LXigr3=il<569cV{Hd)7j0Ryp{mBQUp;41`h8T(+;66aAVYgF*@F!;5@*~5#2~0 z@?I3MjPJ$Xc0Po8DnBpwXYwRr&b=$t886D`Wu}h6QnGYCFSO(9WN~ObkT4ErO%p-+ z;H&HVaXv>i(YjEB>EmO;R2aRdUmOosEUprwvFIm-wT8o3lymN+Je!$)qa50MC(jzoX_b^FZ%lDxbU zCQa(COsP5W_k=GlZ_ zP&(#Nq;yBg;UGbnDh3`Qnwix56fD@#v4R((LV}{ORm1`LItx5WbW}|mjq|+@IjN*d z;!l5?*%g={rekd%QaTQGsEc;*O86jP)aLbDEa4LXI-i9#g57+Z`XlyzA<1~`LwC6^ z>9vya(ly+f;n{@JWm6DY()1RB-uJO5K84nP-E^k%z@z0xGPlsH7mq`S%-G0YPC$x@ zQvK%h5lLNUeyHqmu=*6P{%(S&MS(w?P~?L0j0rH=z}D?(wi|Xeo=Q@;O!Sw?$4zb* zeMIjaM%!_$o0Ex#2*93^1CQ89V?SUIW}P6A`?Uu_o)jBeI7a$R?t!{~eYylNsT72$ zfomx>v(35uimfjX>)>xBLlZxxqVWkL`w_DupFBjzfF`+jatne<@=~whqXMw-c+k+_ zz5>3R9uLIRbNjhdQ_|+_BmbaBE?6du(5|Ilkr|!groQ&vg*R=AbocaNNaHQHI7G3=LUQk&^vE9 z`1G5AuA4(*;#=vuW*On~EUQcgJMrE%`IC0y`~Bfzp5O5O>G%U9$5vU|dL6H{AO-Vx z_#4T>iA073Pp$C#ENz8T`kRMf>*j3G;MNr$V9zLzJcgeK*n`9Q+16Cl@@P03{cP{x z@dRbwUIJl1`U;OXVAE|WGuDm1SDQ^byz)MS^x??gYD(jm-e;PIB{vR{f9NWd1q#>a z%0&%UcuLkiQuC@{!b?Td#&Q;Whnld5-O(U42)UcAyH@`Q142Pk;<~V^ZKZ^OHVX+>P zL6$M)YoYbi`pR)bo*ZbO4%c%9uMdm8aJ(e2QN(=(u3Fb9T20(cTtSGtZN}Nd)Z4xi zw0R^fNCF>MrkE&>lK7Kdc0v*Ta(uRRr?a;_%~R(L$bIUtXzKeoK|j;t<#!LgVBOF_ zK2oq>*mvS6708SR$qaEANVrRW!@no^xJz*O3P&|ZZG zHgz_cbyGH_QoNb^g|_eDiix?)@X_JA0qg-G0>O{+%feiOYPkK9|%_$4&`r@QJ(~L~h_l&B*evfcY&EcZ_z; zYrR29bMWq}75ocJkd)Yt{qE~8exuG_I8DZ98MYEMewQ8gtxc^TfNHY^tiWsj_kzGY54qg?Mc)2Z!Hf5rX z`H2dVG?zwZHb7cF0-|2ncbgK0(2cpxsCL6qFOa-c3hgX%X!d8ZbDMvX+hg4Ru6=Nc zR&bdEzs82OYgx3TqvyLuM0ALVLy#{H2QwTY!96w{u}RZ(Oj7b6XliJ+qX=vd#`1^2 zY2mj_9aOS@%qvQSXW+&@p2JID<+Ggn6uNZ|0z5>z7$VX>DV;*wcfb{#K5PdUekW!u z2c8AovYgHXn2@=eJA%Wrz-l@8Z8PVUbSR zXrt&RR{SN&zswi=8qw8IRjz4t<%zG68KT1@I5jID7d+FPbV)9lJ%IH`e^#}P+Ez*D zck^xOUsLkQX4v584iUgt(PPTgvtAhw4|^+O@CajnNk zV6->qd{VTkNbY8A=SHNYmsiXQ)g5HddC*RJ^I-de?oJ$-hkyo2w*dI;;7=?)JUOKN zPO@e7yIh4fsSpGLgZo%+FbD+3=OV^)u7CnvEG0~;SBo24nZ!sDsu$0_3rJDYkas7& zaK^p&=0WSMxe@Ov2G-y5sMr! zeM{sr`7x6^M3XbjE0-LkS(8Y-oE#3vv!e4pM@VG0$=qwnAZ9Q@$dQo+bko{I0;d5n zL|YE3+hXf<--p2X0T^Lj@#vc(l2TU{zEeX-6=D1I_G_Z4LkBFa{QN2&@a$@C9#0ca z`FZ);NO;`Xt88g`GmnlJ<6Z)5TDy06!o_ey|Z;pR6icQG}tvAubg%{Yu z0>MliKfiL9y{>zDy%^z^sJ*aB@_K7$1}o0Y|E|!ev2aBdNh9&EZ&GEzyX{d@)bFDl zda@TY^lzuOr++b66uv3O*PYT!JY!)v5=KuAa07bFq{&+T4m^nORYgQ)0nTjm8TRi6 zLieL|U#=ViL<3@NGBTYr z^0TSw8|tJ!_*H;mlFGX+++BlHKp6;S$)(YYiO_X*Q!nnrQBRO8=5I3^eQEaIK;MPi zl5i=v4}d0ttNcNq@Cwj^eDotAEZh5c`-`Az0dF2n%!1u_t*t!G;ZF#s2+bJg0kV-$ z*cj>J&AU)dm%aSK3PI^)*v3;z0>)PPP=rNNVoQ;%7bs?AD(Q@eQ(!ksk0Jd$5EzpT zjyo@R$ALO>zR}?P%inBIB>UUNbF_cwhc1P_YYqKzZ|6JxK*X;wNlt5wWT<4Mfs}x> zlLD3)Dyw8aGfLcp9H_DUp%+r z>A%e6xW3iA=n5h)CSMN$p9Kelxky79HuyFeut{@F0k?r|s0XQX+^KJ80~ zBLsFOUClhtZt_|mF@C2PvQCWhAlt+LiC?B?@Mre*a(fdV7&Cc-Yn)_!;{WoTacD1> z%QZf0{LNmz#jJ3_W&ac|T!oWcX=&aKwN?PXYVams1&gM7S(NY#^_trE+C;x2VY8+7 zqUoO`&e;(|C9RFqq_Elb**(%{qUj&hVo1?+&t$#vz@Dy(`*w5NUM~gJvOT}UuR_(lMVlI*Qpp!ws%j$l=GTiW_mruW8IEE9ODyPd*I8Nw+ zQ9{~qMe}we2c=%hA7p_W5%o*Qr3*WyUlL*9-neqcpw#nOhtq4SM8o1CPRbQKAC7?% zyO5o}T<-9M^R1EN7GRJ-q#TH*!b5ifgR9Z>Q@r;btdvv; zghF#=VQ}kIUH2bSLZy-&iP{#0fCd1+Fv%Kb*a;Ty029R)xx(#W;Wn@zM=nYD6;rr^-g@h9sH+NX1 zknNXX6QSTeCh>uM1n#+&ddx$3JlzUH0dWKooFc?QI_Hq$}Ku8vOb}DjE zD9x9CYSA7BOD>$soqj82?k4zr3}oc~HJ%9daUWB6N|@(Id|d?{ulcmTYOn$PMXbI* zOmL3VX^is-xVEER^f!BIy=mBF&uPX$>UqTThzaR2=|-L$l4|XhM3gi{bP;XFlynV3 z3Zo|OR|yWz%6p(QuFJdKGqVLdLtpfuf=%P4Wo2YE7E2{2v?2e^bsLC0#2E75Tz7=1 zLP|6_)cauk{!znZE85+Of56ge{=3oG7Ok@42URAr(-*Hz%kZnGTLJ_#-T2f#ngJ6V zLnVLGQf;*&FCJAWB=Z6i)tiL*xrKx%=`vm9J6FTujJ#8{1Oi9KP;yTxVWSZT_?YU| z4QNX4D7NET+X4kmCu%SS%L|v3zKq-RyZzG!KKs7A-2(&+?-V6oQu>A*tF`GdIVE6= z37bd;88mFv+DFJQq+auN$k0B-O$MV?oZT7XgU4-43|5J&(1t=ym5%3Pk`BTR8nn-a zT39>{>FzT9>=6RA^Gc;v7khpD>#;9FVx}aO+`98G0?J~B0W$)Lzm^Lw-b%-ZXmLd! z%ftvt_ZpRD>B#~lhFj6 z1SL98jy()1H%Q!RfLYTJZr%uYbKiwk&_(>&s&+lnL>^*ER#`vrP3AS z(!Zop;E&l%V+$vw)=O&pdOX~Ez|K*bYbc$zkNhuecC?$U62;02wtSnKXEcj0BC~Y{ z#&y*zpqb(5sh&Mu)^2>1A1BKm8NarW<{MkSm8HuV`S3^H_2Ev0X{HZgGD=2yrILT{ z->G*k87Q>vEU*WbDa%HBia%UqheC$=4%9wUr21zNRi88qq<-C-TEr-sBFoyj|w*G)=1-qeDLk9bPDBY*iLC2BsjDn*q8bsiC*6UP!yA0 zNy(5MbO3Aw)?A1uipb7%-y{wHHWn8Q z`6+9Sr?Fva^%SK{^;;<;R#3`*v2=c+X`EHX!CR>GP~7kcN$ASmF>3h*U&>pG%s^u? ztp}!hhU+b;1mY z!i&rq5`t~TZ{1RVjewZ3gnHOtAMSb`Ig%UcRSmtG8?Y2t54lfzQSr&k!MQfM!qK$G z*PyOS2z2{O;i1(1kN6)mOTV6nV}Jal;$KdeomVS%PqLs+g4-{<=#!c&Uqm$+fy94d zlJ#zP<>n%AI71`3_2Vjj5Q5-g$RL6D=##K{irE`f%5>IbdX;elh8;VOPFBnnyfRh4 z_I~vM|HQDd#iPVT&7{IIp%Q)TV@DXzwdS0}qB+7NV|UKt1k6|6Xg zhI|D<#z9>fUM$F?T)o-}*Y>;aMZ%Uh@rA%u)7oQ^zfO6Po`qWs1BcW24=Q)&r;YO8 zn(^FJu^B&%_G}yR?52sIrp8ARP#pws)}?Dz8nQdY=#lwfNR1-zhy$6HeA3FpCp(30 zMl(IIXYzhM$s}FivK`_b&!$K7f!i|BQBf>e(Y@Et6IZ_kMpoSOT5-g1BwM2K&L<^} zET315fVcGh+b_enXk~L#7U%Tm$bl?s%Fh&5C9niP^+W&e;b{?K>>=dzi^9Rjb>JTg z^S>0k8gzIjmS-lXcyzek@Bj{YN$!@~ z)Xp1--NRcW=>{soK?!YhQvf#qeSM2-MqC@al~7u((R-&4aJvu#k>flcaq@dkO;(C| zW^Q@*bMa8IeV6=&xE7Nmrddj@AW_-RDSCXWhk-)H`k(tuD)s$kA;8G|sKB1gNf%9V zb6_##t>EGQ@R+U-TtVO~keIC3&0%nbb=75FVA%cILm|l~ohycY41#4Jd-!Ukrh5-? zXn~q(#!Szi<2T(*h5eTG(!C?CMU`d`ZO^KRbTE^R?-=~btQf_2p!I%QT5877mmZhq zWLm6(1qag~*h#ddv^-xC{D+gv3@L(qI&cHq^yBED@Q&cI+XxMR3>0Rv0m{*pgZ!bF z0W5dc{>fNm2*U+0hbH}9Xclwyy2|ex_?^)gfomM1!QU-g)2>U&|v5VWXy^XY(q zHPJbroH7Y)TfzM7}FcGmX=CU`nk<<+?j5dS?4rluzOWyji>`t%c{}lH? z=>^_?Jo=@`OzMPMa4>yI#>dJejLyewB)Y^<_@V`4dA{S=L^LZ9&EPI^lI_#At-}l# zFNttWAOu@U^@5z>Rw6^$;Os!Jf7{n{jUC6yq=h%>nLG zE>pK$H(Jv`uGm^7mQqe-;k~4)b!o{;f{M|>C698q z%i12-H-^n)NabE}76BCrv$gBV1IFuh@~Sv-DM2^bsF$9c!54(?c<}&TN3)fp$cH~$ zk)?}KihSxPczyJeuAFbOSFo~XiJr%nn{hB7^+4ZgMbg|7>CS?mQW6P#k(B*K!dzTU z(op1^rOGvV0#IIx1LyQPt-33j?7%6DJVm|N?9^=}G7PH>E>ZtepbCQa2NA~0szvCSo4O8-l znNXHE{W8g#rww`M*L5_|<(-5JF3B>5aEk)^fBl}f|LlD2RfOTFn=$$xum4Do zGkIR0x*izmoviovp~DsA43s_=JKBfcB%!f&%oMCyALQBVoq8HH;;Z=g>;vVLhBHda zxbpZD237uPvnsXvB|Ful_gKH`uxVSE|@=^B_QZiOP3u5I?Y#1J8 zr=rz2Qi5UET&d=?q?gIdBN3QFqjO$G&$7BxzWjNyRv>sy)9so=&1+5shoXR3zx7w} zp-$uUQ!8JZ9vvoup~WFx+_ri(Jt1ns@d?;5uoH>Bap%xDD6+Vkeb@T{uGj4PM#T4S z=oab6(PV?g$sM6T02|yLS8d2)$mydyo!9ixh9i;N6;>WE_)eU#zc8QRqDKR z{LW=tbtHUFK@lnKgj=w+jidD^GLuRMpKp&v8fW0dgKu53BgcFTjTBn zjJUZibc~>O{^?3ldni=KA~QMF1Me8b=q9#+9yvSE{gmNi-@?(`-!@DW1>4#vXXM2Gv4VBT04zf zRnggL#|fZ{-IFzSK}Q1qfIw5R460|FL>U4zK)z&Pb?nA^Cyd!6(%|BbtQZ zs@PB=B4fSTXbh9YkM?0e)3Dbt9<7+XfEfj^C<)(I^iH0}u3sxksg)1kio`T#$Z7DQ zkO;31nF&=mQWY^3*;`C$MQEOsIFsgerV~@lk5;XjmwAQ~h4EP<&AJqNR1t6=;|x2D z6+aq_QtdEL^5RRE4s^v*?aS|&YwTYXQ1qf~C@)E+_k5DxXh-4M`(B5X1B9fS09onE zce;*3$4XpD^H+-I6sD8fO>%4*x=vc{c-}WK9sQB;)B?=j{Guu*9%4p~g#Nfh zgDo;V9Q7Z-Yv^CNvr|TipQd-g)8OQ*q<2E-5X%pFsklozcZxnIWHd2D!3cqLPPu>J z!KsTB>d^P602q|#{)qJF4u0oDWt=@?!)Jg!i~~Qs`T`V8;Lqi4L8s3I|0j7UykY;z zbBu82e=;I6D*8VebN)i?e=;uqBFms4H#Z?MsomT8wN<5=79C}H#X$q8Z8gow2kG2R zD+pe_^oqZ*NL+q@bCGt6R3E|E_PnVe*t3*i2}qX5YT1kYz?Nxh?7Jfot%CYy*x}v( zi(ljtS07JcPZSWeuRGfUOWmg75D{1U3xaT>)>JDUP)^7Tj49UJ0uDPF1tWCNXc{hx zXoQ??PZ<*&N*jz{hrY(A_kY4E_TJtNv6i+zqlE|t29+x9MLjL@XDH3?m_^!~sy)E0 zaiaB5lL;ZD{fS@7k2B-aThcRx`B22^wRcucDcPx-B9S}BS4~-&87XBJP22U)zngkt z*>Y<&{;35*6}p9YMlosEQwvfI9Bx-%N={BfU0aO%QvUi&Vxn5HdSm2+1Z$S)#dzK# zhFx6j1zxItJ|>!%HbzB8+{896GQ(L-Ves+uSMYiFd5yM|KZb^!{jXW@nbS#UBXX$ve*4AcugPS;XFvjoI(Gc}S+D zs-WQ%L!A9yKAj29o?_#J)s%n(H7i<-FeNi>tJ^Oyi2Ulcl-`BgaY@xLfNz1O*ITgQ zKagI=itaO8&6(higb3w-8U40b+rNniC#WlGm|@BFTZ1vSSQ#k{mLE;#Z74n@;y($8 zK_M6eG^P8xs>nCrDrS~h`hCGVMFnlC`c=0e9`Nc7w~S2KK*{9efDGw0@0)h!U#@;= zq8h(?Q7baHjm!{rrC2%j=C-qweXhAcrht>+CE(Glf6Hn%k_&>QjWxqb$(so&9jIRz z7HHo~Q6(%1%#T#=xW5Cw;rc5+BdxJAXGxiFR)CnKAY0;yTpAClFhg<_WSfB-wRVC^E%kd z)G$*sjOM_F4#~>>tXV*$8u}|nGW{QMI7*$dm)oC}XGHUsV{a}xFdCh;Jxxq6meJlULo%Nz?1Cb)<|?7_O< z5B|Em0J5B}G51o(LO+JDscPj_x#PMa@8mb z2_Ket^z&a>j9)kawDbIDy3oh|La(6ao$U`V7kR{nA8ute=9MjeIN=cF$_=;ppb%r| zx5$Cx4Dfz@gRKkfe&sP&55yiI)eJb}`t_wpjjZntTK%>LCxOeJAZ{P$y99bKQ(#zb z1q3I?$k<5Hv#H@b>gY6Lt*A6Js zMwRCJ;{MW}_{n}RS*)hg{F}df-o4M+6I|D0Qk4%{lO$F&X7mvrE@}rCF=bl{rcNe; zE_A?zNQE2A5|uTaL+k;AkE6i|e^Sz)A@&Ov4IXbPiHpW8;$<-}@@=6td55+=ib>{> zGHdh6j39PLPVxi2OJc!*4~FK#VFX$KJCc6!y61@Z9{eD*X>#${r=HjCk8;`_;rqV+ zqhA631s{+nVLk@ktRt|u(Xb)@iL!T3*w8!R%9f;Jf4dVE)G2w7_us`H4=rpXDC6=W zi_g#S;G=^70$PZCd4J-M$?ViOB1Nh0wG?*jQ{-Up>ZX%4T_Xqs5wa159i?JB^1w@b z(FO-gpN3XQi5L>ECx|}2&Hk@>>J)1Jyc`vv!2SG%uf5w5D;4?>Z$~Tv#g8h2$zh(#R^r>d5!gwzao5*2rziWZwaSEm270LIy*s?Z*u zcWF2KL^%+e9vlC}=ng>x$Z9^vjBKkLbn!kPe5MK)(b1`25s>HmG11f|eeoA=gyz0N zi*Pf~KMV=GE4f;Qh#Vzl3O{(j=uLXEAp%O5`0C=HU*Vm9L{H2K(&;XJ;{Hjb;;l^9 zr$r#mSgJqbx5lYo5fiLkFcXSEM-0gQ@W@$XO@U4c-?JHZBTNjizR#^y4i(_i{RD|H zq`xEVH&?d*Mt55qB<69d za9;=j182`2>7`@F&Pw}##7V5CXU2MYw&Ip~KpE>0_)6^&qW@7bwGp6NL!@kY3%`BnDZ5b>F1wd2L{<365_Z)UH7 zDZZY)V>Pl^!Qf?egb@JS)UVoe+A?hPy(uOHVuupK@OehbGm)(n1F*(@e5tb z7H99Ez+vOh`{u_DHyC)f5@_((_V%puMk~yqlq|e10{rAxrt+fKph_umB?NdE?8cnwVkC4fn%R|7;_4qk)pI$_o9?~Z6RHmESMn$n)sN-&{*0%+v=mI~Gxg)CVbs8-nkr8K{0_=zoE(4tz zz1n%@65rJ-eO;CYAXWW}SyQMDwX-E!83^1(&xctvEj=Uu)(rV)XCuxWh8N2bE3)UaL7fMd>#i(KX|!y#GB= z`}1I()*E`s($IUuxi^GJ%)Ub6pS}fgO z+}uF;>-j%X<~o(H>0)5Y=lll;5MD(1sSytmer=%;-akt~(7#95r+UYi*~t;lHLINJ z;_GYC(()%G1|OUTVsdwrE~E1Iak2lNN6zBON1Osh-R47+k~Q>N@z(bVGS7z;^|af< za+Vt}pc4Ty1xb6tmB)uq4Ut{VO50vCRxvFy;L@w@D6wO+FK?SU;-{L)lh_}VnI7=Q z`rsLCmTSC7$y004H+gVfylCC1o~+4ojJp183C<|X$piO;^DGvX<&F}3+(#ae0@ry9 z`Q#roJHoH=Jq95ES%lC*JaSV&UJVVF(vcRW@9|G#^<)5spz-dlFM_BBFLgp6=y>t;r}*EOzvtx%#N^l4C(nHesnjFLUd zOhqa}_}%aCkNddyabBBji=&s<{aFRC*`012i)iP^?v+fA8q`~!UtSy! zQMdikB@&ITt|@5$3{Lk+AH#DNnD%+0CfisnzzsX$<(H20x4@w~xH+=aoN%h|(o_Xv zJ71Y`%&-&}MtCSeDO)^~UHXbCm-wZM^25$&*#+>+R@4o+1z7Sh2hQ8u^ZOrg1==ap z+(v*rkGO+M*v@|LLHS(4MT(1*Le=l3maM3zeH$?%!+cy=Dkskl8ugKI+gWfqB}A4Mar2NpBSk^*&?^t z19U8VebGPYt3_AY6%cab)XUf-;^gCNP-oaf;;T^Cdf6lVuH~V&Vz+<}3H%2f3H;F` ztHj?Nh1YNR$<7%MU*FC4p{h~zNGeo0ny6G^&H$3ug=DKvI3P_epsOOJNrldl#1KliD#AfV5?VWx{T`c_8XU zd;%l~8`2!D^}`gZC@ceE_WAbOF_?HyfwN!gJK#cBX1WH1v{VJnWZE>8+A=U|8ID9V zn+XUvoUI#D5Dh5^`~M{he}W1>vzJ%{-FS3EvdWtgU0nA8Z$qtAQuSE!lq~Pnt67nCU zDSWs5?{7O8_ts43I^0PWE|B5ynt*t7-{EzwvL>>d?**gbq|P}Gk<&1NJa0{u4;n9= zB_$t;lBT2kF{xO*x`1}cCtL?__G9d8-8Y9i=;VdM%Q_pS8du+NXvPlm$*L4d7FCtL zeUVyRZT8R3u}a~ffidj6)Wv_$to^_(w=lewDH%pnIwc zq_DpeS`c2(Sb8(7d1f~fHz@BW`vsBtd6A+wu-nZj(qM`2?of8Uc2o8Aj&U<|B6b=7 zo0pMJ7)|lb$ZU_Iu%CZ3GqY_qq#SdYF+euEp2<|aVcVaVFUB<8>VC|u`nEH<6FZr4 zF(q}tiM*S_rY%cGBugBm;60O8U_Ap${qM*G)%?<;(e2L8?Y1i@7Tw_EaFTHUj!FM! z`RS93(U(#a{nD8J>wnN}*|>>6G-nDcH#7MPRc{h)D*Z{!A0zeC1bdzJYY3ycTm;P@ zRxYnK?Gb1Q{tb8iUG%>RuNj06Ct&--NS)P1JDLV{Sd_{yWq$wg zJw%#ML&KLEo@S2j57zSUn2{{_9z1hS=H%NA8+*O?-(VAN%-7E(ZN53{#y>J|RptN2 zr;26mxRoc30^J4@x;N|30$%5}E7X&gHmt?iVozzqH)^}F-_Ywd-L7-2uc{)f=x`PP zmx*5i*ezm;s$tiMy)}Z)7vg{ZPy;EkW@T!05QM2JC8@d?`1(UYe>9JJMep%T0YW5n z6yI-1$_obAWqFF>DF69M!&{SmqDNi%+loOL*;$x5>e1g4Hjpx~Dg|FmdSl(I^i}Zb z0xNSSrDq;GpJCfT&Un_YI+No|R(xiLNy{j)l`XNr5s#EHxc0HMPu17W@iU14f$obj zk$M{mv%Q1K%9R$mc9@;(r!YbT{Isy-?U+RwSf=*_+9Ru;4t33&stwm#G&5y<(uVvG z%=O0&e8*#s`&;xm3uz^Qt}}OL)>C)xO==HXSKf_s#1*8_yZVoU4W1F2m$SHL3RAcm zeyL<3hIy0lX<3k04%G1Kq%Cg_)kyE~rd}YaZ_^gMTd&`uM4)JI zutyO`k7YG?RAkXKw3K{XQt*qr)irM*v*d^8A-nH(zQh1aLN2_%AGrfiCnt46n(uLKpYZ z``_5QgL>MKCYw-e2zJ?KC+*qKw0y{5{!r(tc7;|E{cFCvLZ4bfd3@35>y67>IST{4 zLE{syjeP1;!V{2)Mpvgl5vw~3Vv_^%i%B_%BJ^X!SXNGIIU^^n zC|#75pPG|Vlu7IF!bL0S4YptsrUUJRn769v+UR6cE&E?uDbUm-#uMJVnQ7&JS^0o> zw&b7@ur2q+smX{K-5)DSoY3S_PMG-@;@9xe@d8eWw??1NqUt0$=v{AE5ZJmgo)m-KCe?&XxWb#M}{tRJ?jgs6ZVT+pI%LX(6k|1PDw`>{W$`N+oyAdJK%=cTFu67Z6!61I zLs>#1CN5r(+&R@V^rTzH?clQH2iT|QxFbd*y|lvQ_71l#LMZO|6+})eJ}xx5C`ayN zR+jmbtaIN~#m$=h3hIp2JM4NvZ^)LSlh{GA6E;zzXi*pV0nsnfIU_wO=>$;(_#Oyh zdf%3*k|#V9ALNDX21Np?A#r_eQMJgO@P+=AWZh7VX`Pt$jx() zVSP-NH$BB{)dRTc4a+v?vV5Z#;vx3@fWvmW%!=!qHRdTP<>NKM^Ghj~FTT#3*N6HW z?xg*lv6T9;N0!(lBi(Up@!PaGpPL^(a=k$A)^`U1MjYv=5ML=#Qr{5<)m z`cB$iw<~iW_G$f$QWXp`H8+?vykbUC zE1r(L6Tw-)ue(D)p*vz%AnWBAvYn#^70I-8jq94D|D74Q#6|L&*;j2{fD# zc#U^VW7I^Gs1-XiJW{&EqZ-HQ4ZJTte8!AWCKk*aPKrBxF+F|>>oq(w{%jUTB1^t^N!N2xeY(o4*Fl_FY5Z4$CHJLDRI0&0~!`5Tc zp-}gHEoiS9I(OVHMB9q*nCY1cjf_*LbzeOqY1VRfPrSRke6evJ=I7Nz2r<&*UvzM2 zBzR4{)~r`_@p=p6>Uv_U{KO3t(%?{okNL{|IHu^KieSuiddQCPe&_6)61cFjVCHED z({Q!{t(sS1-DTC6y4njDraSQ%!-9TQ8JF|r;b(~xy*zJTAH5m>Jw7JH55{u`d4(a(cdUt;u z8akI*1dlB40bcwi-dj>vo-`GUp^Batr!LVwM`%B+wQi@$51qM~_Vd}}k3<7s@-+7p zZCRwhM>-VwfHK(f^8mPiPa5V>{0seE?X^L|{ddjZ(kr-COZnX)XnD05{1$LP`6e2! z7UNJ{#r#-gFpf_g-d%tEQ@o8e>h+}mr}z$vVwGh2k_-&6b63dP8g@#$Q$+}x7^VN< z$v%eAf(}_%Uu7J~5e56b+CI4yk7Jx(bE;irSsbgpbg)T(fycCek z37Tas%k!>4hx4;mphvYyuc+0=&A>0npLycxTvN|ZFzLP^1imEQH!%c(CM**0thDF3 z*zfnCCd185W$nbwe8V-++N^Bx?_$?>=3Om;5?=;`Y5VNDAQIYZhJYNI=p5emq9g;V zyWXFjH2=B6>~>}uLsbDlf`p6or;oM&c^&y*zj(Hr2*Ca4Eys|(-Vg*dfA)T+k*rr! zl~3QS^Ozpu9Ud}o7A-73p7^8aahAomPfbWzRC=BwSbbyz3ee&xK$D3j%9xZilr`j5$4X&+uUF1e#|Zb>qU~18};P>%mk|~9{YFbOOlRnX#Cr>@1xIv-?%Fz&(UAS0+L;z$ z_>A>~POejA`HwOix#4qW+&^X1|NOIE2ql_;8Gaz~%aeRmTZiNhVT+LI#H1J55MY9Y zchQB@X~QKuyHXGEa$2%e=Hq|KY7^(qz|)eD*It2?f@&(WxwtUqrYCJxHfA-pJ?RVC zQZdPkLkkb!_t4s;W)r3|H8!qKN-;*p6MH?Jc65(d9xVVaOr zBWX_RW~H5CIhz};)^5ylavwjI5O1^QjO~ThuQ}(@7g$xmGbnp{yz4oKANS8~0G{e?ie&<3?d4vj~RQ^QzJz+zHQkTLz z_(qP+UF*#bAhc|qqo@eonHPGiI8*6(zce+5)i28yHV)u>5Nkjp- z28+C&-NO%=k!YPEO_!*jt+Th&BM?}`Y-b^sT-5lMDyw|DX;1#>8}(tm+!4(8<(YtW zsurVLlJ+$<(=e?sG5tmfh#0;n7jpZApsUmxtz=*Or+?b zMe)uqxsbI+TA9AJD$xfRs&(_VF3vrCYi4c&tdlb$ANzAhh~rhuqA%m z@53$Yz?QYgN==kFI_pHgFP=i0rGHJ_D~kvG#lJKxLW42F&3yq+LnLC7@{!5z!x71* zcHu}ySW=(*-5(9;Jx(IbvdnSoWw0&0pg_uzO&zTP`C7f9*-FbW^W+zqU?WM^ z60s7H1G&G*L*YMwDOXGMPr}N=B^OUOX6J6fT=OjZKf&r@+hHr5TMXP=*fZFg3^TXD zDf>|=N2k8`hpXK@6v%~#**Lg{G@pBry)TE}VwNX}eo3D6@u4|P)*Y-+VuJNB6&=C) z66|UrMv#~SLla2|020e{!Kd>oG$;%F<@6SU0=5SWw*Y8-Ymg@YekYA&TY2CJMtoR< zC~QpplG*NkbB^1SVAr|SBN+G%%f%yZpUz*kpJwCLm4lZLvln@t7w(kwg|y`QU;4x2 zh#F5TYgVl^ZU-gHlNldm2JJv%LUu~~@#4VXPeYvUAADXV=Zyt%+Zg~3J6v9Qm)E9; zn}vDHTM?6`r(2J~irpaZw#;<7HE3q0e|CjhV>j-g*TEUV-$5LD_ZoVE$LG5q`m$wA z{BCJvg>yj)m3TgzSXLdxI`;FN*`oTH2h^@b zz<11o3qe$2N(mF(WsPkT&Vj5F0?ZrDzDOi-JT2NS;<}?SgdtuCW2!qy}(H-@T`aOFee8VEtsp>VCKF92!LAenzPNx&QF$(O%KC>3-JPefr2D zF0+_GCn1fK=ezXBl8z>CXzC^)1v*?EGyPL+pN z^)8@~ZgDa3vfyzdwgMy^5POK4YUu>1XK{?qap5i zuKG6`P8|VH$CCSxqs{Txzianv;mF8HYnrB|r;fNgD7dJI{f|4=!m-U9hX|(}Z?agY zH!ueN!zcIP#CAvqi$DwLmbjY^eY@wp4&h^o-iCI_Wvt?&q%`UVpFV;U}@3At{P8HrndX zI-QTeV1ZeucPl1l>p0FT;6UJ)btr%l3kL2_Lz6zo7l}Ul1(GH(q8M6;n|}f z_C4Ra?e6l-{z1z*m4*$3gT+#gQLIuzx1y1KD#cr?uBv ztFta3P*rVpHbA090!r}_*CI(=vdY(rtHgrEf@)Ygm@Re}qRpY+L;!n1qDc*((xNGb zv-P+JyHZ0#qat>tCppjQ{%?I<{Y&gh4Yu%IcqEwDeTM-)dAU79W^OSV@r$<|<|YpE zKWodk(_U;w_y$*ew5ajC$NX5YtA6N=M7SH~mA@JHfMd&e=D6g8AO< z&YF`3#Ts^&=j@r>P-$+*M`)J30Mp?#cc(7RmStzp9dkT2V%RiDh(g3JqDj>l4lsoP z4;{fW!Fo*Xn}=BTei-gzFJ8e0yGYF5`gO_0;_@m)#j;J-QU}BZ1;Hr0!MHWzx zz^%+3^K6kS+{uH+E`E>cMobCbS+kUYO3j5yUh?UuD~gRATp;5KCr@Sw=Sd8_k%|(N z>1eM{K5^+Tt;+Z@(DQ{|Xa2oi z1sb-20W8u)sazwngui~R5aG9221eM>fAgX^T+jo^+~6L!wmaL8m$ZFf7$CJh&yi-9 z0bf-jsc$Ibw(*`N8;hClD2v3{Suw0$j%O9l#B?TkOx-TzLHXXM!MfWmtw<1!qd&u z^A$hyEt~?yLF-8WjF9o+JkO|dC#}cJvMtpvLUG_>d`!%%1%ibF2^t7;AzGc^6B@c5 zOpr;6Uze^zzLU|`j#qqJMi@OT$UNG$1l-~D?@*bgyW?Ym(c|XTy3i|WUrU4pbt~`K zUA7xI3@~kyd5N%dd+9l>&bZ&3xhjzS%BqsE6Bzg{TuBc5G%*;DPDl8*o=_=`WAZw2 zb1-(|gc#k|{G~Zf7Ngnwy7YaT*JhTN$Qsp1>(}Uop-=?01 zR@@CoXhWenDqGo$5!`0!A|)o-w^i!qUb7$?XC4RNUAS=B6&5GSV}UR4^5>y{e&I;3K;!vSJ-r+0zLhWij_pG!3;ymOF2%TvQOexKa?*KiX0KI$tcf`;{w6nTDkbNJ= zjf*W%0#_6KD<7x`gTL&|!R`S>blH|+cD~$~_bu_{6NHUi)_W&ns@9VadABZ~eUrm|04+CqZk7Gs zR%#%ULALixd)F45WLPQi40r>`Li#QiI9&nq+%y&~1kUuB!k9Hb@-z`*^4@}p$lUj)l8x0)e zhRVc=!W=FQ6D$L5yVMv%!&7!kp%I!e@=g)}o|eshn;0lnh<8kfpOY>n&GZZ0aNl|n zbSMH>GgS0rHpsqg4s;f%1m(sXC@bB2rKT%g?w9GCH^CH>%<9Pi-So}r4Z95jb}%|c zXiHLu4UoYCWrFR6aG{P*$6`TOik9oCV`r9sYEalb>jk8`l$>r}$p$@1r;~YqKJ9wC z#(gK3hPgk#ULq!Szu?+?GG%M!_jiTSboFhtv8Ujc%DVctnnFU^b0DMOCZZSJ;aQP} zY?lK%ekRs;GdYjlT^u)JJN#U(X?Y|y}XoE?+4H5N&QE7 zUB$mjN*C@Rdl(y|U_B{kNk-??H-br?qr@ELm@rf}|4IR-tXt4i0{PL%`prTo7tau| zNJ>=q^bQ{QF}>|153)1R{jTY zA1RBZ?bWITV-*jLZ;&4%!4Guh6wdkVD`Shl9f__#GYZDqk{J16!|-Z*|oIO3BQ zLZWBvr=LYJJ-3`}DjZhl|! z_;_~iRxFq3Y`?Y3+UA0RqI-#GnxK$L<>HyNbah0Dpk1BQZl<`mvTqig#b(6i&=U0~ z#re#V{F5cwG6WW>;!G7LgOo2|lEO?&&k*j1$b*@=Bw7T`Az%b67dUP?*%!O+g&da`fDK zul7he<7iWFW4PFtOBuLtL+4jK!%)^}A%IK;G)|F}-uSXo?QN;l31yRjrL&Keirv4d z_Bec9E7=`8?)c^Fj{+mDNvTbs8od|q@`ze&sq1mb(6@`q(?IaS!Yk* zJV9U0rZCK#liYoo1J15y0GXa3CG_`itA zobNd$-9nr0A9J0C!~ia4;VD8Vo;7u>h;6jk&SboN;C9=343j3PCdHt8p#mTJNw?& z&@qeAERR#NG8BEZyk3}VVhKE%^nUhky`?J2PKB?@B%@>fS$k=njVQ@6O;39CXudF-#&&FYi8t34CPIj$ucF7YJVefHOfc4 z;k0P^Bn(5>ZTsN zyVOj)CW2@k;S;v;f`j?6>MpHIM9qEW8kA@Y>1hZ!?q~&Ga_^Z5=mByy$Ie>`Vf2DI zTe`0DCJ|z+aw*GDfGgZHwLt7&YMs;@tMjn5oIspOZ>p9i#Oc~CUUaeb$81ZI{LQCX zLmKPLqEUTZLBmTVdQiWMmB83DasEiBMTg3=ykW$rR(W=_Y>h=B6iyMPp%9+rBy0uZ z7=eBV2wj^!G6-Q}$Kb{qM9EI>F}lGbE>X#E!7F^yCUWX>Op)*O(4Zb6I6}5_p*uhq z82tH&=S1ny!kw8m+ab+50~JWcO+vY0;D_{_=q_?KnB}siI_T0rG}_na9&v$>k@>aa z%U-LuF0`@9Iu-4(F#}y??>OyUym4 zto0;#FFGaz_+`>}MQljWrIgxKD*@)$6!SO$p_{9Gk*s3j1+-W^5@xkmGa&!OmK4Q< zCS=jV?!bHx(Jby}&G}*%UKxw*u8KPd`BJZUr7i_SHVA=x^RY7owJxc=UIh*q!pXGfQ%VpSf|`4gomChsv} zlT8!Xz}joWSzuL`LR$()WrfBJB3;YkVBtJC@NodEin75_XB7xf*-V1!|drqDBQNO19E z0LW>h^MMwhEyO&DG%Q*%j{b|A{7=`B!>S?Z^(xMu=)3F?y8Mydi;qBPd@qc4`DokS z4fhNUf4DOT1r9M50>gl49@r!w$HIb!^hZ1qkE)moh}HoJuwkmQR~-T2(Y)v=h`hF7 zRcl_g48JikYVv-#il7;v6yu>n6_oex$>X&|Ep;9f7w3vy<28fd_O|Ym5%`#~v~fR0 zv?%&SXN;IwQrx9RO4JEUqbKDJ_2+o(BQWQ=1ivL%Fr0u8PHIyYHWBwPzyfHP7CjGr zep8b6{)KHXtqPqSd$TIm^nZAQ*BAXN7-{QOp*B0<;sD%w44;Q;VeY^;yvsk=ABK6O z9t!6-hN6Eq_FLIewzRfte<>|W$?E;8{w?+U4ka(Y^pEhL;%y!yc6gh)gP11t(PY90 zAqQlVA2aOG9S8wV=H^B{1%%W3at~QnMvHh0MZ6Yba;U98H6E`_#H-mxso%*jT{Ok5 zhA)L{jM`E1GEeez{M|5jcdbVtx~jvIc@K3P<#>Y`b$YoE`aBY9<`?<=gk%~`B6f5U ziO|lR!*ml$_YWZRocx>x#zy&HeYz7cW_+O4OZ=F&henhU#_8}9E z%1qOk4AYqfuC)05E8e!V)9MH^^~#6}bx&b^q<%C$3$LFfXeOm%9_D(rr8MyZzmgAS zpNU{;P4FOiq&8K)OI)wobUSueBY4p?mH}MPbu?^DvHRpe-xTC%x!9$l;gO$f!q~pE zkbp_g_*3b1P@qK}xDGK$f9&Asu%0lMxH%jz#leS~^sppv$51`#cLNNkS&*`;q|l3l z`kE>qjy>G|h@0rZWUI{l&fqPK>a>YD%rs5heg1GWD*Jf2Yd8)H!` z^so3f`H1dwmw4JbKN*H_&~XYPdg*g99>m@EGM~RT96}Ns=76tv${|rezO!7(50`|! zp>hkSRzrP9Q38v%&sm58l@PNn>Du;k=y#3-!8A)gVB(O;KBV0Q1ml{o%h8a+`-3j( zFgik6fcFD5U|2MTz(?TWC2(u-2=hZJOf*5%R2;4$Bd5X+iz{gO`AEanmftEi ztcaulLbauVKh}WG;G>XkvS7sS z&iHx@A7VuHv_@D&3;KY#LZ%U z-MC|>CR9HL$EBZVhR`33{vFHSndAA?0C{!-EHnIBbF1^P&CZJJsp<|FaO?y%aqKaf z=f`VBadPIpv&r%boK~+K=#h|zs@ZGJqBTC_SMlwV#|Tv=`fj75r}hm z(t4x6l4I?M^=E9A;ZytG^{?oAfCMbZUnwp8LQ|vOjmUJx29Ym6c_gy6xP7M%+8;s`lAnCr^3H^1e%WyLoHn|@3g@GD9853iZnbw>VK$7C;7 zicD)0B7~Yo1~QSL6KDD@HovaFn({YpHo`~1LuPpqk@&)}25tl-l2-u~ADDj#7yV zd>(#N@jJR2!WE3E23`Ep(@T(V!XbxGb$y|C)Sh{2siliAE#^pJg}RIKPO z^n`y+y#cuqlyRN!+VJgDUrUvF-qy?we@UzQks2Q3m-5@?Ec}<{2N`Adr zsN10mJu}B5rpac)KMk#q@9w~X99oH3wdPnv^Vko&H4{&ysZk;s3sK(0FaL9 z431%HFg0Y^_SsOWWWeSl%&dw~^ElB)K-!Q5IGMVR;= z)!)~P6pk%~bd{{mz7+}vd=98qh0ddwwN{UZP<;I!0?*;Y%c)Q=yH8gI7RfkBhIi^J zP#5RL)IW_Z?YK4o?Y8i~aEPhy?1OrORyHqJXTaX4&H|-XnS;tpoGmd4jj?F8YtdLku`U|A!=<#ykdVPXj`71(qhCpOC=SI4SZLVNH-G0<2 zW#~l4?|qB60O$-=wAIWt#Ian|**DdLL*HXukB>vdP|N(j6&Kd6almcO0DAuD=*R&t z_P8KK?*$6JFQ~5-QmX(u9oFnfL_ zN*_V5(RFbcIS*P_U!p-DyGylH8J3kh`}MBR7eHa_N%1TNL^iHld0~wyaSm59Ey9SF zV@OykOj0X<80(RX{1v-<05Z-T-hKKvQvQ7~KNu5!DxuyiKzkuo{fW)DVNLxVOH5qh zn)jUZe67ZO@6K|_`fFMbf&l;#(YQ?7%Qmj8zl3>f@`?H#zfsP1A~E7xaTcR$w-t$G zEST(iJ;pEi2Vbull-eC$Fx{`0&f4Va1F~+|E6*YBY?9M78xKQr#u=dKa#2-4^kP=A z%2?-_PQ*6XicHclGT=>`72HaUxINq7axvV&?l|yO z;=7|XiSpaoS3pak#&Y^?WpF%r8!^0MSZ?2HCtZ0Y1U^Do#31DUDl1fw)Le~VNr1Qg zjp8m-atrJYae;BU#o|AK2ikX0kUN<8$&+!7=#!#72l9W4$>2bvdz2L1Wv>6qGAQSa zLXlq|L{P1dWhdfOxgw;Bsq`vwjM{5$pAn9Z2OCZ^@F(d;8^t?)+2tyTAk!+Yzhj;A zVe|1^)j0V+Nbx84fR~dd)_q_TZ6HA(5tm3Z&M?u-S>Ymo?Ms(_j>ZsL{ zdB)S+{m<_$B?BUy)TatWwzQz2nSr-veR9=`u10uqw z1uPoWR(9=!!0B7wuT*v1HJQSEb4fbKtqiK;Ta?SmDrjIYcaDO+LyedW-=oab0of=q zRc3_EQ@!mb3m52ut!zS@Y_`;K!MY5pKmIo;&FG|Cttr}I|D-9ih0k@Yw($&OXGFUO zBBcPTnxE%AWf|!P`bAWrymPNX&1Y>y+BF)X>RK|^oMIBq>WtmQ%rtbcp{;y8Dm z(D`=m=30ZF9=HGFZ$UT#1uEqUTsxH7J)&5shuOqektM6)j*= zh>`ufIBaa(aT{0b(%(bfO2XP-P)sl2E6E#|A*J3A4P6ik&A`Eaa1qS`7_P&5aAQu> zoGX7GllvsR^Go}5&pg(~!2LgKm}mzGBmcYoFR?@}2Z35=tmM9d=ESva5C3qXiZFZE z4^KV=TsM?DYvS$6%*%D&%4xYM(NfeZ?0e;+L|jWdDZEV9TmIfIzt*Znykef+x3*MH z?F+vrbp8;OeBFiDN=_XH&+T6BxI9@L@U}Oh%_s64l|FM$DDJjo zhxmrJpjF&$tllEi$(pDw-ZyPsaxIi5Okw%DIw&Qh)Z$Y47h~dVkk)AQ>5KA1BZXJ< z8prx$E>>n`@7*Ksuukk1s+dd1&ZZ4)76@T(o;3guf4+2xia49-Y7bm(?WXu zLE8^CRL~eXi7O^cL-kLdbZCzaz2F`VY=uRa7ABhF8sROH(9X<_aHyZ+r%G}=nc629bh zjOXvdy&7Y8LU%`yXTy1qNC^816V8{Y)4< zfw~!$a_0A}qX}?Wf3VXfX7(t>zTAZ4ACVWTi_`k@#X$3)McZy~O~5J(eR6ok3emge zwbRbwb&R@C*`s|edIUKS@E<6eHvuY9YZU78#8J=kDM774^-6nyNgRMr8@+6j7V`3tYH3QKRz#!KC74Es?r8Dw88r3p+3+k$TXfOo-@d2O&DX33z8;aM zZ~Y#K{F5e}?H9@1?!iZ(CZDCqiy_qUknFs>!M)Hj*NKa0*CuP{-g1@LHaxgx{nw|$ zg(N;!tFnl%lhK9jW{Le}@W^Lporwbkr>W%4Kc_}MN;73fc|PtbdfxiC5DhYU*Z3Hj zwzf0BPx`jDGi!G#Uo6$`So7W7#-SuG={Tu+In5#Tc_k9<&R9Y*_($@6qN=~G<$Dz2 zq~mth$*O}PylMWacbUimBPm@CS!IL?Hv1idxpnQMg^VL19}{<8(nyE={SN+|Mmz( zE~tIj!3%F9ZtsOg3z3gLNE6O0Ll%=my*@#ghu6rOQIDBlihis#0dl@1 zZ+vPBNHD12OhsMY4<0eGg**k=9{!oEf(++feO_sKYJi?ns_h|Ei$%o%wJE^pYS^&I z?NVqpEJT!f{XKSGZFND&du-~*YcNzF5JG{|jaR1p)>Q&m6fx7@#?+p>kODE@pyw7M zi;g>=9HUQO30R(LxYi|BC2QF$s~tR9+hU&}+h~uK;bg+|U-NQZNW;24wdk}vJvzS z0yQqHT2FqX(<9NQ+)pTqzHd^wtj^juio9NaQKcmzSMl6aJGVrLH}ss5Q4U#0CQ`}* zGz9KYk&cCKsD+Jc&ox6-8~FmU4av9vK_QU!XuiJ4W~*h-=<9f=%G=IwlRKpmPxY7s zG{`NGu>$-I0~i?zn|Mo&H)Hs10|Q{&9B0J{=1em2CjK}=IsjH7067MN;=N%%GI;VO zir-TiH-bnOnQTkN3XfebgcF1<%{F2UA_BEnRS5FS%AD0&Mau>*Qk(=KJeLyNPzZs9 z00i*o#Q{5i0XK28^RQ7=z-y)jy!W>SxuWDkD>vjh=(xkp+-hJMyijHEQwJwJlaoNl z|IdTyD&jKa(g4lS1?KsCY-3seX4Bvmz!+#KL8JWUsw5>i`l!#h(HRh+%fHAC0xp5zQ0c|DQz#_ z`00<&X=T6xc(hbG=Z#XkFzrJ?*|M}`g)zO?se-R?N2K0ktVZrMt1*7oC{wyzG@hBh*0VS!=o!=2l6z<}4i>9adn3)J zB2VE<4Z$#&V+;4Z_ts)SLYrTtc5`+-1#~*-d0=##|D$+S(hb%nWz}j|J48>KIqEtfGd7Vh07yCXoxeW zsB!`jYW4eF(@$7!v=@I|O&K?{a`&z59}yIS|EBRxG8%%!fb2#~gZF-8f$QNAE=~ER zyh8ZwpJ-f=h%ggOvDnbvP}m4inV|{`VS!j1W3{1S*7C1c3%EAiT0vu7SMsk@jscUk zC?3RBG$agB6`+6t`F4>n$;CDcbn%ZmST0z_k>49|OwC#>{-*LHKG`qDDxe_)7*OfT zL&H~CVC~$UJO1TNT!83S=X0D|&{T{2Rvk!(CUp9ky2UYdW6|PJgJamYYFiIi8L6ML z>jEl&v2jr>j$i+_er(M@%LC7E{WmVted@?|B<;-4n`b=@834HsG#XjAz$B*gsk(ra{2&((YajAkuCtBN(|7ip6EyZLJ9gN z>+5s6JWCb30szF=h&z=D?;aWM2pq>4GWpOnBNRvgVbLE9OEyBeW4C;XxA{MziNom&l_u&DNLbQa9UgZFHAZ(#t!ESk*pyUJ(m8lZzwj{E|y{& z$MXthOR&!-+WGMGbFx_3b`B6WJi^}7!2%XbIrAQ`cIJOkrxyJGqClr}5K1iT{s_l8 zcToz(CFeeeOKPbw%{4kg){-FU>h8l%5;u4MAHc%HhS!6g1M$*XL9$cay)KGb5{gnA zkr#d0IkKoUUxCGbPVz6DtDzo9fP85V$DV<%cqxz#)|Bo0hX`bUshq)eg9!E91CPcpGFuCk&w@UskH`(YKQNoa z9FI`T)mLANjhLe?F+T{PPx|c@dlUtM#zXUk+#BrpUw97GSGtBFM>pk8of>KUO|>J1QK6MVJ!&y&b6 zd4dBQKIV}#Ow$c~Qv9j^s0ch00Z`QvY?QowUe#2Nm%{_18Yd_Fe-xc{T$9fqz}Eo- zMmLP^aEz|Oh|wt`t&)Ot3eq*o(V-wn8K@vqiZn9dODm-k0wN%dgsA-Z{dJ#v?q2uo zkL}rWcb|K|`8T>qP~JQAzjF8@>^H(=lZAy#4WP=xzq}&(-3otoHpj680WNJFkzyg{ zxs24yCa&&iam*eZOHx_m-mZuJ2Zlc+NFA>-`w(`9~Iw?Nqkqw5?{r-n~s&@S;UhL48R@?G2v^}(>)k#5FwxNvmn)R${E!&7|KWPl> ztjp?{j=s|v6OJz19yXf>LjK=oUhxfOX)O+aI$ngbzA&|sPROy5{-xCRu+G8O@fX(a zau~}DNr!j;A5Yk6Gci#4dYcRTar8~%THtHqaky(3mbiuubg%JQy9_)(x!)cfT62Y* zxcJDO3}f<#?R;-q8?IsU{j>9^1I4+%=5{5`qJ#D169uj3`9GMO>fG8u1rJ*U=S=71 zo$>O6)&d9DE&&^mz?(ok+Ii}vCC)f7JmP#^(3paWii$@FBm?WY4Q>&~qGO6Jg`0&g z4oo90d7i}rKCi(~m;61f)QaOaSraA?UiK>S_!nb&6B^>>o=p|}>1ap*0G8{4-774j zU%e8yJxibfIO0(OKl_~5?G2a{6;Ay(Ae{W^zc(k@DrC*UF`C-W_UmLoNBi|`Kqkb8 z?{QUpAl@#Am185p5vWWCC*J!?PT-~_g{q{#ZxH&U#9dL`Bzt(?GbO-uY*B|3Qi(m} z5P$#6FzTp4vN2!Wcm$q1_~v*C!;9&#>vU4u?~ZIsi~LoU|L$*WsJ(m$hfS;W@Q12 zZc?)3rYQ+gI?|m1mK_0B0?t_u*1Ef$*R4CoGPx+%ui7|XZ8S|@&9S*=8z8(&CfHuL zTg@SulI-ls1k+@PQU`*m@T#Lz)BpQ8IlB!0zmLm}oB!{#eDl`-`?&sc#h3}Ny1CQr ziCmt{clYq*Ln^zH_dLDYR>`@ym2Z1r7JNP?KIB}a@*Q8dT)wu|OM;{1KR{*g zfYvK}&m!;^A0rNLVl88=Rc1^vF)iKt{~b-u%p2?jDeI1n<`ySXZLZCc#}$S~WP)(3 zkrY8kS13WPSZ{aZ^3jbN)F*;BFOT4pi3WE!wdHsQg$Q@~t^%n1V>wqv9{F1-rhqde zAqK7BX`12xoI>ND$voWK!l$Pi$Wf9=P*@;e0;O>hLmnhr-$l2F)MbVeaA!66e# z#T84@63-vn)skNQoRXh)KkW{@j@rvnghH+Cir0!*NYxJ<)XK&-X~)?BOWkc3dvvieNv3-{CW9Td=GR_H`Ncj63e9=6}8Sa z3p~TdCC3V^-zzr{v2$~@fu8O!xEjsIdenpW{LXPGwIB5&sMxyLMe#=rpI}CksQdBJ&C6;sLlg>{Uo{Q z0Uo%-4s+H$$lKH?7%Xo~y7ct5!bt6#g2wMnc?vfdH+Ko1q#w_Lloq=C_fkvZu;cJg zH+)X$8t5`~g`15)Sa6fKpP^RgVE&topks^axPO^WU@{LFQ~uq`=JAcd*F`(ArmU}F z^c@M+h(W|46{8nXv8tOQNRR{PVlgO z83>^PQ_?wt?>~-aWR>oB`|BCO1dAis0p;iE)0AJ;iDZ`Nw|DDYI7ey6Z$*P-v?Ned9GHKQ=^ZP`pq2)W^uH>w0>q%JJAT!Qs`c`f zw(-+v?VG%fYaN}h z#*=V!Q$x$J!?~Ra5|KeQwoT_ioZ&@_O|#e^bQh^!3$a_?}X=o(hAc$9HzZj74a ztNqHwZm%C`I&iQd=VH$#&0OWIit0ekUo8fpQ9yn_MBqty(0ncfZ@<^mPv|&oHWe-7c;`lD;)XHe^uRH@lX;6j zAg=ii-gyr85)m=AkF?49fY1SgJFl_%W;d7t2M1I$r0#l2#I@AjxxsXgO{@q+WM-c8 zFZ|>Rpsoz!(h=1X-+36UHRSdOpn#4Le>b{&UsLLQk^xz<zgOiy<K(Q}fcmQb2DOI3q$tqM_q?ofBCedG?}dNY5o!9>!NoQ0zh-av znYFPNHE(xokIzH{UaPA!UM9JuI9z@Q1&N>E(ruu)0 zpoXAedO7PLn*Lp=x;A}>M6QUlWlGO!>#6d=>fp=Ms?(Cw>{Ai5GPRV`d#6FCUYF9& z+*9Y%tEYubWoJyXcf+-RGa%eAcO#V`(x9Hp>9Qb@LoOAD8thK-`h>xBQzr zG})2TW+#93`${gnzVZL5e}1=9_Y+;l4&XCRR?qQlHER(?Uo~^Msci+9v8qzMSgUFl z5?Lg}6))AC9|s4EikJJu{ofom0F!{c3qO#pUN^x##&T{BJT@(+S0?rU5v)+%#cw-s zndx`BHtf<4Ub;zl*gO!l|M)8WOyT_lPx40bqFBP<1K>VmQFE)~YF~Y)$nqT0&ET1O#lD#;4(xOLZ2UY+KqvAR8D*m`#A`)% zSd#&ZoLpQqa5Fl92j#(xj9?_goUA$|aG%1K!WH;)`&Disd*9ID2VtNajk&{R6*-t5~T;sKAzY_+on)7iy;G-%L}VcN7hG%NBE;*eUpa@ zQvKa)tWAUC1*f%ae!Yxl!QheP56ZDx7a6D{$lV1{f6VQo)vZwt$ZJ?>xJy38A(AhM zj|u>^t|p0YLd1H4SXGFX90!|5Whi-jLlC{QXC?Ib*4DR~t@~8+>MzJKDh0c;8;Ksm zI$})Y2@nr`N#)~8XqAdpO$Xihqnr$Ax?y3IH>iS|;FDPo8Uy`aTdNLSU|36(%43i@ zGP9mZ0FK37EZgSn!GlAeiw(gh-*IeX9w~P?l+mmG2RZvh8Xq36PI*!HErVfB4por& z(h>MWeeBMi(xFx0h{BAI3{Lq8@B%e&1*0V!O-(9J=6UJc5a&M6$g&jP{v?YP`&GU} zn)vUtv?90&4^?37%YSTN!HA?|$_!ty0dh%vx;L1*`|h1Oif%Wn`ra;h1-NM|OeX!6 z++_5g8vRi@5pYcQ1*iCm+5r|N4A)Cf=2OKLvJ`&a$$V!rPLi~nC)gQNoxPK%PVn0I z0M6{qH~UyeOGk{#a?f$8u9___Na|sCne!^a;Cp8_rtd%7R^X_Os{T1)|AVlwk_Fvy zg~6mT*6-n+>)v%MZH1M8llOu-JN|CpwR8@&0_t%5ioeS&NZOE^7tyvTCNx@lEwURN z)aW$-ZZklWYvwmsi+2Em{w64*)X+^I)*y7rwLWfdRHmF|>Dbs0Ky^SXN(t@y#P`co3DR>=FUdr!DgT)?Br+-qt|zra%?qCd9`F6d z-_KaP8Z9kTclq6mJ99TMc~bpn;|-I2(gE73>3d7uM_p{-x@M8Cy4n4@bK9>VG!Iqb zt7F~W9?-uBPvmUg3x^enJfZW**49o0@{z(NK1|NewCXffU5PLspr5Gx=+9%3xNRZ2 zEY;DEsTok+(nw4GlrCYv8?3(mmZ0KkCRk*!pvxWn<5TPPD?vW8j651Boiw-DZ(Tt) zCYp#gkTL@kW);BB!bZD_aOL3QAxkLZG5lRkDecdBp4GGJh(=IWG@rws6|GueWa?a> z!*Uo8o;@piW;(JK+y1QmIlrxsnF*M4K_}W+k5ACXPLiOl?WL>U_=U3*x^ni2eNIE? z%A`BVN{>p66{Ffj3~Kdn4~l0O*9=$y7{5n ze{db>39#1q=G9X+vb~_)E`jRCSs9}CY;~O8A&G`}>?D@fwJ7_-?&3kbAzk*TeJ*`r?l&Ob~)SD%q-2<=jDN`k|wQ;$nJJ;Cn%i3wUq_%^L|H{zZC zl9QE>#`7Ulf@S1_Z?vF)m0s2Y*um0j48-iDTE8GxA{>c1+sBW zPzklMM>D$23_TuT^rl4Sw%-(Fn@DsO+9oK z%SY-`%^OLLli#fCsBC1oK-Y{W-g0N`;gnqHevFrKNQ&@o&q5UG!KY*uu08B8S6FP< zdwS<-1j&Y&(mmJ;i-niskHN&J{t9^tO>{VLtYKTSRd&ux(b(W?-2&shgk~59#Hl0; zn2<#u?Yz#!Me&?E!J};Tf|9E0`^A2IeXj9#;xZ-hQ*frvi;u=ht?EE9J4_6bDsmV7 z=Q=dim9i(lwytlA&<#`JpyFe_qzf}6UQl^jCT}NqT>!OeQ2oRow9vaE^}|WtAwzur zw!QQarAGH_y=Q;C{^HYv%&$m8Wf24`TCi%(lnN7QYyhr|h&zScnI-M)=w8hg;n$9G?d0rv=hr;rnM)Q-h5PBMO|_b{S<3}CG8J3i~n{$13h?htIH#oSw7>V z3ixafvkvJvQ~+IXZ5DQR`kkxiE!ZLTQM9XWju^JfN}*OUIDh24;kpopm4e~h<_WbaZ~0}eiLEZ zd3&u0#`)_FLyUgMe(Roe=c^ZCuS7;;Bc6xpT{jzxbtpN1+rvc3*^OfP+R^a9`7=hZ z>Jp$-ei$d?>*zq*jiUiC4NyaW)=E+Hg1a7z8(;FunYPIxG;80jm*=V z;k*mzNCTD=Zk~qON-AO&6<&P8HJ9@P|pZc=z7t`Pu|n7A1Xc(YSy>c<4^Mcbg&(pyc~zqURcK z7NQAPehvOv{-D`J8}3zugh}E5HKyNPmy(7Y~tnpyy~%(XT}NAFa0PL zAuM3(2lAqu7F7dx{@QDz-GJYi}PcLO99a+lU zcmNThZUHmg(nAiTR#Yt&87omFmK}As6$U^p%dP|baNIGw@0Rp6i20nL=}XtMK61h~ zZ?ARcJF92%&C9P;+@lP`7Q(IqMS^OD;Bf7hiNs{@s?G>NrDLzNlIBC0l7W@40VrzS3a<^fOpFLZ+|YlNV*hBAUZkAI1HZ<7EHcbnl7-jO}~7^-)f&; zFwF7J&gLyL_p3m={lS+9{JW3&%a-Mq3Z4(Zk{J<`>&V3`9fBCx4>ysM`Wz86?i0on z3|#rA=e?ohuH!fPT&vB;b;rLq11WqdF(KX=!pmGYviVX__Tl&Z-KA{;DGruns%Ku0 z1)l%9xTr5ABqmKb2s2m4@m#5^>yEacC>#sUPQ`A=ZIuRWYJDtoA$W9%5UF}x~EAgF95kexX z9eW)$olL#1Tfok{SFb3&h~Qu}G-i`ynigig%Q)O)XQZNrM9u5uixWI`pBn5Zw@xIn+4MZ@3d;{NaTTNdihjkt$Y4bwL>)4L^#}9rPB_=~(*%T}Q&jyuyUrZe zk9?X-m90wc^3%hL8d$CpmtZe8Q~UrX{RnnT!CX{t5a=phyOiZM?*75QDbRHM{i7S# z4qXO`RAX%*%33m|&3*)c63<7qOB8%JhE;@sIxUi!2I=gBKvq1@ z|GGeIvgxv59y6XU8JeQ-2N^w5shWl@v}zQt=OdlOx?%1nCVPRT4YtXD^uC3(50*lk zdX-)d_yi78=KyN~OnlWu+G>Dl+@6431gO{xqSIi>Ffn|kK4WN%R32!aPU_qx><59{ z;O!YbA9ycqtsPL_LIYp>3pbbiz6T&@6=+W(l&-(hB{xcfZhasnESZX#p010@<%f)4?_gK#JbL`O5 zU&D=PDu4f#Q|Ffp3~E<<5|M5~S%i(Ns@V!Io?5y+g}c3zX+-`f!m~3hUb{a|F69c% zJ7;G~gFyE7-rjtGZCU6o#+)4=NpHJuWX5`SGF$;@K<_#$_Aue)VdC98iCT7u!@eD2 zI2Dg@xb-K;b3jv&3Cm*pXBrlPo8cN0q(vE3kI&QvOwRiKl?As3n}%UCW^6|3n670% z5Y>-3tRsE>bu76aV*nY}?(rkcQeEzT0jEOznWdIu#^zZ-(JUe1PJt^U+Mq{hOt4QI zsNTqIYGwbx=NTl!+x4s(aM!~->o#y8G;vvhP9Q+MTc3%yuC9}pbYn~5ZLrLhL69g` zuNyX<{IwuH`d9%SVunB&W^*Ir0Z+Er+{YYbr!Yw0SP3En=WRRnhnzl+X6^sz{)wHp z|DDGh#WKE{=dqcZ2JFgOcCHO7hu07KtsEG0O~skq1FvfVrvC;TnoXQJ-wVx|Ht_aK zsaX%}ZjpMR^$`ty#T<{b{W!F-O5Bp0Ifg;p5`*~V>h=oF3kcR8K?`NWjKBQx4C9jU;3k^B@H)R7iz`RCSn+sb$D94f zKd8_XYag1|K9HH6?AOU_tXP@9tm8EGQCC1m*u=OaRlptgMSjj7yMa}YG` z7<;u}$r@8MAPFwMvpcI`^B2SpI&avyCAE!(aN_tKN+UZy2G_=%-q2m}eKDIJF4 zZ!l{Q;ElVDChNc)&_W1iKWGw!N?1XVs*eWWP=ump=a>21d(-CkV$li9xL^z$%P_y? z_nKg3$hSL~CHH@lzmZc5GIzYTM`0YE2o~^b2PNJ|Ph@8k{yC`{GK5lBUgr?&5HV*< zo%Q_MQmow)oXjPhRv?$O0^K?7iVn#171n{Vm1HJgX8`RehHf_>HA?z+ZSgzSM>t3A zaVZC@%F-d{$-qqLlV?}^&RvxAmztxn>30UBL=kCLrY9f%DRT*i^pnm**nWp=|KaH7<>(QX_LLRBoQJR5UQhT)&pRHh_n(sndHa~AmN%EdIl_p2?liGNS7 zumOHw;OF;JLE6fj!5FZbAXg|TY8*Wk@>K^QqICLIyXyx;Gi2#E=4Yr16K_u@E*|&c zA5}M;W&wpv%>z2(kJsY*xVI@i2|-WjzjJEaUatpsnQZn!lCDjTew0C?XF2>O0TOzL zlUSQ)A0h0JY{glCRKmSIIZDrKGPuJD(&7G_T(x+wQVJAML)_4`C zT{4;IZhq4bI0_ry0K`{!1@@sSVkYrH=tlxT#}pN7N9 z)gEM-LBR@4c5PGGz}Ab>^j~;%!bnsPAG?==r0i(jDSb7ka|$y$@JXpV&BOo$Q&Nw( z@ylSUwNc%z6CqXIU?7DaC3v5=l>A7K8_E|xuo&KW?-B$2BSdQV!dfY`DnjV?>576{ z=;}_)b^LkiM65cX&}{z2c(BCpN(fUc>I2kND<~Mp^4mldv9sg^#`AutAjDH)PbSHC zD*1Hh>x>vaas&W#n1)0H%@WibwP_=E8h?bzCMl6CB-q056jJ7FBTe*p*`NN$b9IOn zPzB=tC2INlYR$BqF4x!1yH~%{Nb4tju`z>Quhg^mc!v{YVP^lr`2`*yI)|S)tt|QB z>!n{(d3!KxilqQUoQ@1nY>{A>&}V&wN0H>cefSGl)yi=x!3JMappWN1#H>GX zSJ09j%x2F8aB)x3w=GR9ak78w{>ihDCXlID9`Klr#v^N2fGmUI#Pp7OKCl@P^u|%{ z3}PH!6mLzxjN|`LsIsc`#p+~MlZK>h{bMS&_q>vjI&|Uv8oSs#{>5#68uA+9pX7Ylr0bG>Pw}N0-_%a|g3lB1)mU!Uy zi2l*MXR&$+tBm$HiYh&*S4h)gdQ1um4Y;(wryyn^Xo8+o7E>Sm7W5l}%)~aWm28gS zFQhI1aIHMg+=h!*dq^v}^AVIEhXq7ZcRb`Rj15|?L@U04kRf&;(=qFK zNr9Gu>SQ9--_%?Y0y0@Ajs7NMT9kMT`#ojjICjoeaj>{_dN?dnKU!(gjcd*v@*P}v z+g~J3esOgW!>ex$#khmnT}9b3f~698T4hE-NyqFK>UU+Jk8#a}XPzzpBuR}6j~@s0 zi>w1tlSQCZaOoffYoH`9zR{TCAWj<1s@`{tEyO;HjT=~IP0nh54ou1nY8N9EPGsNjUNa;pn3rEQ+i=KRK9 z7N)dqQPT70y0Z6QOsH(uaXd{!d|Q)LEBh;xt0nzea&Aing5!RYP^PHxAW+-st)qd* zpV{B-T*|8zT(i}A=K!StJFV+Ba_K5aUHN;ljexJt<68c2EN zV1I`o`p(%Jc^pT2#9iB+#d+n5TuiX4?pnPsT_T$s*{5i7R9om3q$rKzHzX>(XrGhE z2EF0b#W|v4%a>3D%G{QqU@R9Oqp-+dqOkH@8D>r)-4-(QvG$8~AOb-R=S;+!S|M1k zE%~f;{})JA0k$s-1xqytQvaRK5U{oM2UDLshUB)EC@;ST0^W@Q-d*~<4E?!2J273V z3xP~51J(k?tft-Qz&I2J&5&R`2w@bxJ8a;#1|j{n0fj@HG%ye7Y`}M6msN;SZy5eA zTng+CT_v-cj@+Jxq5eM#ik9QCSTWrP)$})AO?$iEHa%24lPF7R+!dR2g^Z}l9SdVD zNEqCat_Yq5ymNJ{YbeHNt;)(`jpfM_a@60KW`72Zq)WEwDc-f!|IAFE!}?b$Z(m_q zzr{O+dFjCv59duvH_Hb_*V6k;S?XjPVrXvk{5sk&5kRXG_0N2!cXaN7CIBo&*%tvI zcvUbJ#(OP*(NGz;waJlO~oB^Z$Aj5pPZi)-K#L!wRktHl2M&K>(sF@YoPndi6wLx47Ehr zU8R!I>Ly!tFiX7m9%(Wa4dAD_(F%01jN3rQ?l3+C|99E8n!c z!Xpa`yu(32O(tFkRVKAES#fxnvbPSvU$cdhnw=$1E}u!SDxK!8@@pn0+083gCf+NGp8}I2EA!I|Bx~&7OybzabC5&()nA)>zIYdpLg!LHz15^YB7I6@ zc1lA|8n-J1W60y2mVDS<>$VCQW z%~I0!KIw#oF^%SdjD{X#PZj%y3=Hz{m((_y0*@-ip%FKfxSk}M-q-*5qI&FyAVcbr z_4_4eM4IM2y(8&2p#}f@3;o3RvzeZ&~cYocr#JsCwUdM@B;#Mk9!a?>u)m;ws8e24z%t@beKAd6J3K^v5Hh0fio9 z-#Hiq14K>DSXBn%7Py@fcQC%?nlmn$t~SeBe92+bps#|J(D+RJt3)3O_xEw5oNHSHUCQ^lym0S1agGMxfcE(mBW%ZD>Q=YTU!z`e0W!2F^rm>~>hfc~vF z5N4bSf%`{%mgkQl<@urhdABCsbE>eu)P2r?c$8H{{~ju)5_4%IdJ>FeX*nZ6Cc)mJ3++m-_|HW7J1wfBlu!-(()H6C17{v0S`@Wk$YSvw&{R z2$w@`A65N_M>bZpDttrlp1BZG>Z@3uN%#DSy%}MBQ`p#{YyF+xUv7RBjq4W5yzYDv zwruE53}(@p#FBP>DLfhxTLg)a6QAPZJZBP$ROgPTDs-vm)J;$=t><*$VW+-Cf4}d7 zT)Y2yzBkeK6`$TC|BuuuLM>KB(<$I9^bh_U6r4wv7Yqbg77$G?A}GiAy){)49Lgm$UT zp5qa>%}9B*R<^g8J1~3}Eiz~iPrNUaXA?_&db=v*$`koI&F*02yG+la4mZrH`Vfn{ zvqrwNt$Vus-aSeAEWocdjQK$nRr@t1k%em{a3l#hJVvVQ6We1QX7XylVqC8~l>)Aa zvW965UZ0i~J7t!t(~w^8aJdD@^9Ai8@C-Bax_UfyiTj$Z#~@(v)uL$~v>fDfWZ>>i z9Za`MH^G!hZA=5)cb~*jh%_K@sBBh!v&<8mPHk3zAg8KF34%SIQ_SibAn`%Dl!#h& zyjxBCi8-sQ;=jbd8k%7+DveN(@HW%UpK1qa)`32jlxnXlxAY4EN4}Ax3R_F84-Y;L zu+DkpG*V954@I}?S@p{V#{TSAE&VOZ`|V_1te)ZQA3MTt5%+bwc4?=oQ0D_L+K`V@ zcV1()SIZBZ=ep<&lbJNEHIF-e0tu1SMzQyyPL9yQ1j|`%I@$D|e!BwQ)#O>}81;%z zLele}4F)kfL%gJ)j%HO~Dv{LP;@-t5-%b#^s8I|H1AJ zc0p23ju0#X|4Pvz^3UiCl8R+mwOq2UE1;fYL3w)bS|J?sFdvQK4cyV=e^tOaVE;}3 z%zEZe34=bqy;12 zASib{Q4(&by~9L-lp25LHv{Fc4i17qmr$^!?ses8m?K$h6*Jhafmg#=wze^2t}ti5E`SFqo2sd^%BKS^aN|GbnlA_ znGAgc(|Ahe{Ugnd?O8r!nnmtou{#H9RUmzf)PQRNWo6|!afin85T+C=mL32Tgmm?Z z4q4io*Yg(orb@pNa^DNVAIznTnrH`Teb4(Sg6kq!{Jup@38W{!qS3+W9`NBWSo(g& zZs}3eAl>OwE9KZsK4Z-~Z20aAQ_-=PFL7kbs%s*jT1(4Giz(M%x_OX@=2$P=5V|;N zU=E@0eknpg8yH^F5FHXlvLOBa&}-`>jVsHQ#+A+%QM#2Y+1z=T{3c ze@Xvz?6bnxzHiF16SrjPkH}Rh6wjxty@+m6#7a?0YnO2?SwG?27Ro3`u8`HS=4X$! z6Twr9?rUTMtqwm3L`g&uK_E>mU;dDioHTRBk`kOA4rX%Dh{R*$)e^161>|KVOl)DR zm&pPtIoa$sUK%_(1ZLo?DJUH^8(PL=5Xe`k#sy{m?-{TRtW#VJSk0}uc#6Yg;0Qy= zC2f_0rGpFtwPCUz;LFNY>6%NvVVk%lI|#_gB6Q`E09<+h==;&vqaHecJl$&6$DvyxVE;Gxio{Hl}d!2 zDNm3$FWNGbfXZYE<{8hG-b5JwQ^P`yIKMK$422Te3Ly3Di#FKIM>rOx3Z~|GdT^iV zLs*9^BFg&4w_HoEuBhNH^Bn02u3`UMR4u^Yz}8^WXI<0O-g;k#l1}|=I;~94Uy0}l z@>FrN;jrpIEEi^`xTmh&Lv9!a>zKXOx3wEVYSXE!Cokg=NY>kT)9S64r0GsUB^5!M zhflu{XdYS*uvHobYuW(ov=aNxix9885HU~U5Qd^wf*sAmnNcC#d%fG+ zlISa+-7iqs5V#i%Q1StaHiCWJdhv5c>G+v>i^{aFN20a}snQ3Y?v0*C_123@^#~NQ zID2z{{9H*Q`DGeQ#){offmOfK0Y?}Iq(F?9)l;q$bA_H-n=Jbl>KKC!1dJ0m$l0L1 zvW@L;kg6j)%(e+|W+hWbGhW|NhsH*bh`(a{(XgwVM%*}wA=+~LaVx9hFP>;UGin7Z z_(5v^A|7m7N-V|wGo@lYN)YSJ>n?dw8Bv}33KWt&dKAn|DO9u4C+$0`)UPgq%1lk; z6GGUc(U_Kak;<>5%|?4C&2W&Z*UODBj`(>(@5DNfnPSIS0$glWpepI2Mte3^+uBTS ze{XLTxcHRVTaNgu#c1eb9R10kroiEv2(@+&Ay8MsG7QMl;KB|Sv2#U)+F!Yl;3-lz z7ApMj{JIGy0HKT%Q3b~JKfm4c-=NPw>J_=kt9Bc&TAG@7kLdxanUa2Q{!Z);c0F-{ zh-k<=m$Wz5szr(86^=E<1Qy+1aGgX&(U+E(<(M*WnTCwpOj7ie>dai~fjz2ixK1O6 z5a3tUq-P{YD($-r*M#&TU!4;=r3Gn5ZeJ7{W1#J%9hs$7)mrdc)<1EWpjt#(`|4k4 zmZsAMSK#2uMLeA6zsGCc28AD@nM?sNjn(;qHW6v}3P+>iUymQL)SPcm0>&nRG<`oD zfOvz8y4JNSE(-wEdR~duWnLXrh?@K%rKkr=7G?;Wl#RIgbE-q!@ECB7_2MF_T;8^R zrbos=S!fo($NDRELI6)2L4GbHA#w%u2~Rt84tjoP7&s#baeu|U+o~yK4T8Pl?xyfc z?t%C_6V%<~s|!$Lx4GQoU+0=ALwn9QBx>j~qPdx;mC-rUZYTu?kdX+P9YGN>pAUBZ zfb3yEa419Ers*N_0@rS)b`{3DK>7sA1uTYPVWE38mqafX*Grtheb!0LsKA{&w0anH zmL5xwzUKuqM`j*F4Zk!7CK!S)YyDlJLHomhnW4U#W)z!+P&esBqTFs84-C-C&?6W5 zcSSb^89k9-<+603(hNviIfKW{OJE();!DJx4+l|T_bKwuD^~r7m#iN+5dmx|`boKX zO>gpq=se$0VA-ohrO^NU(RmzwEjTkiG5l#+vPbMZo%S>_jCItSfmLmYhQvIHkmBIK zk#T2JLr=c`CncK>G-|c{2FV5^4+kSB%~qF*!f1#wnkqt2Bo~kgaMqVteNE>c&Q-{v zWPomX%1=uaWUXV&XaR1JT;D0pKU{6-{7AJ;mI=B*F}=#2)uqcEQla%v&VKq|ZU{C1 z5Ozn}={3-DTuON&Y8JqyKyg;4+^ihekJ5Rjn+vN-w+9yVY!!L9++vD9_Da7gxg)~e zXDhx()mO5**8jnlU@C8px?-0jkUQ@V8nIOmdo%GQQv^ZfF03xmVGIhlQW%IiwL7$C zqH;BnKA5PA_!oG|zi|*mDLcHC>WxouV|KP!^#8thBOV#PUL4U#0M1Om#HMYvo+uqa z=qZ29F>DqukgFDSUT90;K1=g+3c4o6-2XyW5lsd2{+;S~Gk~JuSp@APpH; zcyND@cvT?LV!%S>qawS7_84P;Amgj1LIx&LIH}7)2>3fgIPEDudzPpMgr`w6)}EsQ zQ)OY9Qmx5ij7`=I5p!8}pmUs*&)ydUw_%vFt}LL7`Ud6+LC*_p}U}7 zJKC3qVxTj1815+%(UK3nqqtT>{!IPp3+?T1mbvsZ%<*fMZMYS}7+%hmCeNwtB&eqd zgLsXoN(MEoE~(k8Yz~oXV|BNVSs$=I@btep6e8`NXv%Gq_m&qH-l1Njw#vIJ5X`*6 za1%B4=D)+XL}o9~BgJ7V`Uy!5Ny&oAD=QA5z%G+r=7KECqiatKyC~)O4rZ-B(>?C_ zb0W)mbEhA3l}qg_OZ8cuTS6lEkQ+YzY8-c*dQ<~D)NfbzB{h)ikVi3VyY{qFE+-Yg z(z%`X%0^@BS?-0xJ}K0*;7#8ziU}5h3s8ryZlRIzv6ypG6Uo2$MfD%)iUMf-cd!8L z_ABOFDqa^_1vVv~PXMErh=kZ5pI1rKAA%pTg|5n<5Nvrv*0g>?QnP$lq?Uju4;4b` zgoB*{elMoJ0}p?Kg1Hw#s&!qXKMKO~*=C`tFi1dQ$SnsWd+hDfN2bOZU!MzF>?gAS zVtFW8JIeH=hA<_!-W41UB@Kg&Q6(9sH#x_-b{1N+) zXX@^&Z1JqOmgVzh#s=+uSS}OJaDq+v4X%+7?pDugBWf}kfIUGu_gvHg5DA|}(n#4K zfv`X_>3qZ8Aef&t!UNiJvX59_)F*^2v_OcGjtYKT9B)BbAb5plw!z~&+kX}xD0 z7BSraeL&w>xKHC3{PjFDN(mBxHKQdja@N#-2Ji-BS6A`AkG?H~I5d`E1nNh;-|a%) zj8F#!v$a{;$h#|n(qDY~wHJ_F8mHi|{{B63E@>q#fjEepQLnGx5DL|41FjX{r`MFm0}vaeMRYl4}ia56_X$D~Tdbzi5B%OLK?B zEUTH#%tkzKO@_BQWvS@E=EE9N+RODxwL>}xJ_1FHxrl1%vaCbh*1Rv}wVIP1=x1lF z(^?G*Uvj2K=*tf0GJfY{^zpijK4a*;?D&K!l@SA#VSOqoj%Nuej9Zn`X*k4Ib<-+< z&_f(-uKdT`N`5>zpD;4!j-6noEx!)R++F58y(!=i&e`pd;d1@|!Mgx(Qw4l?1hQN7}a3*lw&{NP$2GxJ4)8nqB2DYFcjt^~`**k>gYG9Nc`H!O;Pf8_UVWT0qk-au%e~@KQ>|@g;T>u|GWL1V0SVH zNY}O%fZV@O4-mm-|J|C^h)7k`eF0j8qm^~2fLqU7_s}d?peNmAKn~KBz%kY-Pqt>FDP1z@(7N>|q+OUuTukb|CO1er zt=E#_N)k{ffYrU=UpnA4%zwpBbC#*sAv#wbm|?6eOLW_~8%eCEkGI-;s1}nSuRel6 zySc@AN#oDNzi2x|RH$d7LEa)yd#ac-1>&kKmka3Qlc+k!OLl3h9XC%9m+lpM&h&e9Om zK)w?n4oWtcMSmjC3EY+cnk?^jKT&q?Hg_?z1M_BjPoNgV_VS@;xf)KkvoJ zg>wf1Tj`cX3-jW_2G`@|Eq(S?)XB_W|A?$bbooiDKQ>Nd({hy{&O_ ztr8Rtw&@OJfB(UK5EZ=niEPW3p_{l`a)_>{J-|#tLNmQ9RG1({{ znSi1SZ(!<1>)CHi@)3Tj-(94Q$}M5%helgsF3R%h^^0GHqEm0MO=$DCJ-J?5@@7Wd zTu_BJhgux>?3FNzZy-xl_oa9WiZ3NqGVL;ZkWV_}GDgW}VgG-v9QXMD*A%Ma=~GZj zH4V4Om8&XhB?x8L)HscY1mnTAGGfCayLMR=4x33dlovD}@MO|f27MpanjFp1Gku+X zjW)-;z}%9=EH@>xSi*EQCo|l-L@s4XO;D7MB`Q@$t$3k6HeEf3;1X7YMnWcJyIm%uUtAS7hSdu` z5$+QXTBHZ{8H9Yr;)Ru6w5UY?8VW0;#1pz`EB9=Yk~Jk)q@`asBo8+ME)1FJPsd5e zN$WShi3;|txzW~nip$gHD6Cc+D=$O~g=^XFGhQ9C69=0asbj68KTl+_A#R8l)WhRW zG7Spo(~bUI_m6jjTkGID1jUSU_a6fKkK zoHE|26RmU~;%}doDC+qS*mZ64ODG$EqBzgr3(YvNY41{$716$yB%pySAfn%Jp6GNL zlG@cbjM!(|HE2)xKV$bG02kITb4sJ6%O3~kBsHUEBV&g1Y_7xJ{UmCoT_HWxx67l$ zdAe9`PM8TC{7dY4UWMw7iFNbD#`CX{CU>`qOTW5z7KkIZ!pf^Yp>Jd)ZRa9>>}H-u zkAIO=jjRE;U5NcnoA?f|9A^(R$T-Syx$C2p^n^8iwPZhp|L+?B_wXL1Nl7+B?1%|p zi(xVx0)LXNq-+lNkjYYXy?$RCc6PUA>jMy^lnr>*0z+6+|crlwuIYm);STYQLDR@GMYZbhr6mdL$OlOB9f zyus`)n8wWGU_<|mUz1;*=9|Q_yce|rt_2+VP3U_HF2QRH&Q-RhnYl#Jyzy$4nOts; z{U1m79Z&W5#{v8<*Tv;tTwL6^_Q<$o&ud?s%&aIGWuJP2oyb`&y55EKFVz1(o9faKYHaW{cbN}4wrlNJ+qo>cWVoy0-doTV(L1Uc%pttzT zXH&t}ou5#C<<1QMZpQGPedzj4H;y4>CnB8G?!kWRUkbDMlee5dNE7Ge>b1I%00a!K zXghmb3~;Fy)w6ov`kT zvs5F*tIx8lZ9U`Dx9u?Du`#ZAXMl`Sov9SxE~+cTUW-IZ7>>9vTz*9JN$=~8$+8-W zez?;Xds|qjvql6*ta|aVjN#vZEGLQn{nx?Lw zsvRGS{mBrHe`v#cz>l3=DZcwhr87co8G|$bks+dsbYNx^I(`>joZis!N z0!1VCr>TP*afzFqP5vgrPzieP+u9{CtquyyE8@VZN&rBzceA4qsplad7 zVj&vl2dm%JVxA*!_6k)>C@DHoDGFlvNNl4xO)%?nk+JNp!K((qm*VF_04jPec?$!Q z1Rs##^K(0};ih|DTq|rNXJ!Kibd-bG-^N)JKp)vELX)460zF^KYwZX&>(3u0963}H z?=^o-N*ZQ-3vZNqme#wrpvi&AGJr%$-W9K~kuzxVmI~A5Lw04n(@>ArO&z?GJ?y{` zSo>`6y&FzgFEv{nPA)i;q|Oy1F#RGeMexO0)($w46<&+&*AWVn_b;1#>g@kmoKh@^ z6P{r@*1{16QqdhO?~Bo&uK4p}yZI849eE+p7gLh}^_RSwNG5278tjIKw_~efrr#x5 z_0oqrkX$W?9oLS}xGG!b>l$7h4-GmOs}CoD?jhwie|CD`IiJMe0W)|O1aO0VhuwcE zS7difHN_7~9lcUg1$+w;7ztwws#Wkx;+J*Gdn5%OG`#VE_mOkd6EN=iSj~M%uyUM< zwts|Q(y$@qelFpjO}fE*HAAS**IRA36MCS`NDNzfP*Fda^8tkDU%$pCTc!zufyrd>ykg`9N~_aE%# z@x29%Lf-!O6}4#Je}pAUGRqN#Eq|Q+!xJ%{L~bAL3IGvFWqy!ip}daMvb25B?gZ8A zVP@mz0gG{_D@9v?&jNsc7m)ZVJk)${Bw}P1hP7{>)x<1 z5shZmVYr(08^Fi`#qkIP*Zc=y$41+zDaV>1i87@X(mHCW2kQZDj5uj&#gGU+cW*X+ zo3oe>!vxiz!3FGU|I|DtP+1{SA4#G_hdjU&zQ_cG} zmC)v5DAP*seF_vMS_5F`*1OkI3Vtn$k*mP#wzLA2zgUT15$egp>lg*C3#a}`(&DX1 zsE7m0BVk0AVDZ~6=AHavQ{Y*oPsO!KO)XGY9Ma^vtKYQj&A0kLVp!NPj(fv5xq2_N zm^ZUZa_+C){O&_$Wp&ddNEtv_EkkA{xZVsXa(jK|77wh_*M|mO>*7D^hkhY1F3D29`nvfY{C zzLm8aNq;F@!VRa7FlOCRqe@?4-4cRqLv@&q%6ztbgzz#?$9wt2vyBH4x4F-cMf8~< zNiH@3K_g4J>K6Qt__LaKx8H_9*i|fE=7AZWUK1|~LBcvu%uY}2gE3doB-P<; zm0ZFFPVNkn3w1BzuZ8>n2pDzH+wi|fUL~u+T7njySCV(wq`5Q9TF|$oY0C>j*B%OQy_u2b9(?!CrWdHy^lIeerl_Y7yOa9x z)L${dB-ya)_sAX|2s0vABfeSViw1}ah&Ekxfmgv2|FHE+!>S4@P|6PC!uS&5(ztT$ zan`+*Gnq&G?79roQWFW&S_`RWNU$8Zmq}gyH@tUpgJB}ERN(u>XPK!z`P?-AZ;tn~yHw$(N}lvghjy0Z>6bbR1O@88;}x-OtW}H)Utxg})d`R>&2s zl1uv1pPCes%7gPJ|M^}Ueg|5Ewp>fIM(T%J4^WUr8jX5yqAavHKTjd*2hgV;?R$0>n_op zhSder0g!}#xFt5kTiHsd_8?70ipU;?_?XokVjWE_O!7c;+68-^Qn60Pn&bti-RsVvyOfMECzO*XIP(Npn<`1 zYi|Ae_a+^eu1q+jW-V5s`FXEh-!L3@@}Za*XNEvz18O2Sd)0leyp|e%KpXM6@E-IS zl>cn6SH=~u7^VliFp;(%e~Hb>0qrz+Qp_%M#t!t&zJ~gMtmXvaS;2$+H7vYvNnVLV zd6PRtR%XGEP_&s@$eore7vyuij|(`XZO!(z9GWlzgs?oQ7x)l_R9cA+4DTlVu?d4q>P)$Bd4MgpE0cHtd9Pe~c6&NHc2PrY3vbYgMf zVQOLc9U;eSAz@6-ogFXhu*#zAusA0$)ME?FDEaVTS#srlM10w;)9uS)3fnIP1g@lg z6X(X;`hNIpR|)ZgC042hZ@?M2LxD%d&OxERi1t|)29Rp%TRm0gJ45M?Jx}3YHOs|^14Zu7XO_}uPdCq*8Te%xe7q^M{lfz5%g zxDvTKMbA3gWr)Ra)6$jt@H0@CM2q1>VY%VcK@=u)I=6tZNn3)?#ACuV7->)IZ zW8`{f{vYg5yO2dc5N6qXHSQ+Czt{8=(>W4%#-AjKz!pPCcw(eu|%J}&pg#9GwT};G1>`JB30qK_-Fiff) z(<%28asi4v5apDAbM0j;AB40H7B7qwNj6Bc;Rvw+dAwWmN_Z3eXh7@;roC5!+tVS! zyMkwCNk22=5f`B0&PHKo;_6h;&$A5-u1yZ}S_~~fdiEK68 zmi}K51*}O?1!ZAHoq)a+T&rEXNrv}lqrBANI@H@o>uys=EPwWTLqFtlYr!g&_EuOX zw!Ug695Y7jA?Ic7Sfl> zci??_7M{TAA-Kp(B+{k9_|ggM`3}z#(9s#T4AmJP&na z%(E@+O8Et!to9$tVQGhTd1pbq+dLP1K{v5rYfce?3H;jpffxwd!t>T2Y#zMGQ+sA> zjlcwz3AY7oHy3{;2%#MA*zpxJ>#pPol#pTuASs~`R`U{49QMr3)tVpn&=8DjVv?%o zBN=KCSDQVyJ5aSv*JYaMG#X|Y06UH@WDpGD_F-a)6D!bTV`=*PNLU+$B>GX}EI#q+ zcT26CI7gflzNVOpQQ+tfFs!`GWcEXE0tiI`zJ*E^&N1no+$^|#mvtem`(E~#YE%Eq!yoWMIW?wiV zz^w-Nnq~=3%0IzaVGdx-EBD~#e=+h{U@SW~Fy?*YQW-$hZD-E$Yad`NAAJCJY&F?A z)*RSbms2?>%4m6`I?zaiQGtO=me_RefXEP#Z}LRoyP~JR`>MiA8Q-<5uvqV%h1f{Z z>P#b-K15^>ll3^GOJU3WVtgWH)!B@wE>D1+qNMlW^ujAh~^!Cq^>_o>Uw3_i^mquLNEM zl8KKnE&P)dIsr^>Q4k*W`88|HVeI(9r$TyM>8d}s0GZ=yCM0T5XT3}brkm(e*9hA zgP(C4-Bwff^bzq!Kv@gGkFshOKb3)7FZ0+iSj?fu{5cW*e$%{th5Y8B^y$B`83fp= z&LIf$UlD2Sa6F^gCj`U>B%@UGSFF=kaI5>5sdb~El?KE|ME&rEDJ*Eaj4zN$bNp%x zBX5_p$=U=X)X&}+$^`rK_#fm~$($R&M~8wl5D5{@EC;!?^ERFsck3!YV0mEil{7ua z!z8vx{v2}yFwZ2t)cf5qWlRR;Ll{ZO9=Ij0L@#BQ4}+;<7Ij6S%^W?=Q(XnJZ)mx!Jtog4w#5&Vy49pXNL@a*U}bm* z&8a8gQA@k0Ck?&dX&MVa;wV?|3O;8RW}q@I7vanqKw%>Cc93Ss$;r2pX4DdNfx7T# zWqoa(L^C3t{!z!eg2N>=&3}(}JlDjqy0Tz$|M{C1etEuB0|Bh6Woth(*K=+XHC4Bq z(8IRqqQ`J}9b#`gj9q_oVcLgjF#91;i`Sxdf!vdYtJzBoXIN+FlXWqSwcwk$zj<`< zbc7taUGW55KHIZ3)2+QV?KsaPaGa_K863vgLg9=szgdq9y=%-&cX6lyt=oa_dR+dM zoQ~@1%XTyr7U+t#rv0jBX5>f1b9Y6bb#n9t@E?fcSbXB{TU(Xp zry`WDQr_px`i=EBlL};tLEMp*;D_OswNVbS?;6ee-igAxGdmUzsQ6p$hl{n_o9ysv zhWcfxA9YKKAh*AH>Ld5) z-Vpo;DDmbT^V50Pgb!R6IQdc)ycbz!KH9(AfaR`V)O!U7x(}TkU(XW5cB>JSFZ}BD zTi?WU8_NB>Ao=JP0qYrM5qfJ?d%+??P!X6F!jWAVo<*`QPNW?2Qt%-3Cv$Y7Lb(%5Ve0*4p)Y>Ec|A>Z?uxP+I4(m8li-3{Uo^B@lzr0lp5F|1%qB=?K~&rbFJLdSS^@Yv4X4apq}r~iYUWK#dN%U{El z4A@9yRf-419RZ#JsPRBpSv~h4ApY>+rDp-+eNg7UVBdp$a^;fuUTfyt-#i;ZJgI60jzu$#-5UssRY<1e{}wW z@PdnvXZWGDm}phVrM2Y3$clzYkLtwiblr;A_0c`Om=t(82Ohf{R}|^G z!q4+EhkzG80vy#|#FTp8R-R-ve|Eo6a818#UCA8cc*2e4rp(84MjK+Mw7$3-vz@?A zfCNyjlB%gX+$?+hJBRWrTggYqr;o6=I+-q| z;Jk((=bd3!@>1i}id%^a#XmDd`Xnc)s|>#;T(`Jx!)bp3V8Q0R-<=U+y42a0`1P=c z3`E_*?PHP@V_`7PUKJXI&kjaz3G&es#o^oQAO6yjF=*MxRm(1sQ=vVe}y6@O`w-8pPg*!kgX!ChQtf;B;rUs>E321e$j z1SFec&9G5}r444acATW=(eJ$?f!!!{O0U=zF}9-i$-7?@o?|&)vWcRzL#B}zFrny{ zk5PZ|TA|lsED;W;+lBf1h4MS{&_hwLKKkM#xtTyC`|W~5sDb%+r4`O!x3%vV1>hLaWFgPKZ6S!+ zuin%+wSaTj{01*dmx!=gvU?mT+XuNY0lg9nQ+WLjO!Ecq3>BKDj}tfql7=LDLl!=3 z03OOAKn?SKc$cO9nFdWPAx7RF!+8 z``cRxOT77>y5!$mf13e3Jj%gRqhN(+W#WgnA=V##F2rG8OmRdv%B$vs$9~{{2}Z)W zHOteVQ#iyUU&oFsh#0=-B(wKaKEgLdbPzsV$C$Cv8UrRlMYk>D8oya@Up99UznKU! zmUFr2uVbEt>dbr}Gp2?iByU0zTe0#_&tF+`&UV|^LNppu56Zs z5OC;7`mkOd61~nBP;mKES}Uufde(Cg@QJ`D$<`lQ5_Z6VFGR`H6FZRuSjk-QZ(HM! zJ5PzScGwgg@Tn%Ub7{tD@5_oY*@$u^d$ybDcv_h2taMVuYm-M3tc=f?v>73dOeuB| zV&flrjGvh&7}|$h2-n=9-Lz>a2^~s!LzSe!O5cT(F7h_jd|O%BdHOXeM5peDN@7nO zcf4*stTp7}op(2P9R0QbsE!_UTJB|eNemXhHCZhv+`|3KTb+0@0Ipx|xSyUd!DAes zuXXwx&=*BDn?IZ6(@n_Qj1yjpPQE~3&HnPY);vD(v;|paTb^61(ZGxI7MCxhv77@G zS;%DEq*M10fS=ECzXwo!xoyP$2D9%As!SSC@!8xXC)9=M&16HlFq395XyFAHwjU1J~g<4G^SERxuhCD=Eh)HLGdamTae63rgxt}{-5Lnp! z#n@k`Pr2^OJ3jby4OA`4QljT zw7$gF@{f|+t$`UQSPsqrr7flY{o)2|(dVGt9OJvsl(34sE#69oZ$ypRry!!%m0*8y zB}r_sqI>%t>>p7D=1?)wLN>sQNdC?rCns_Cd6W=r&f%2Yg~2!S5#%H&5;p`)qE=r;fL9E)_&^=@k*I#QzXtE{R~X;*5_e~asu>kjPs-t>2Z;N*1@k ziYr$zfsxT)^&}#Mx{_a2LQB!QgYhO_v3(VMr7;f0TT8^L`=ET(a4I={;?kU6yhmd+ zW$5hhhB^-l?0Ti%kEzs1{oPD~?8UZv>-ax}{3n_+>34DE4oR>%i43v9CCcV#$t z-afOpxZW&j3=11nHDmr^X-|s-O??Xqav$vDmO0!$vbzhK`j3mid*1NJX;H@+3EBph73if8UU= zey3=pHHyYVV^+__VkA$ zI^iS=FwMMyLD^6hsCLzj;c`LLLqMo70)=F_iAt4Zfg^otez<=-&NSQFu*wS?;GERS zINDcy8hnlt`fw+1V+JW5oDyMWB9K9fMP(N*?^HSDS3mNq%YZ1fKm$ywOPMs z*Jqf?5-pU(I?TA-zE!2?ta4t&LC4v{uicE6B`iOF}iegHv!7h z3Z1*y`fr)TT1NWEv<<9QsX}OYwS9bz(->x#@5kl#TSWHYP~bMbh-e(_)n*n{z9@l_X2S*nA9D63g50*!8t! z=R+wk>|s1)`w`(vYaUtNLGn>hiMePg?z(cH(i_|zi{W>cSV_t4&P6fYAS5*t39W+@M%ho&Q~%Jc4DQw&cc&X+rGw9?Ij)f`jI&gp>ZPG#^=*Ac=2r0y zCUzM>@jLc%d3pqRAa=eo_JxzFsDWbAmRW6TxYwin0c~uJpJqz%oqiJ|Q@Oxa6ANRU zHL9DIi$)h@<3K#WE2b!kj3;<`IlRb-3Xc?4jq8u570ny;TbFtyY?iY%!jv~Qb6663 z-oMPwZZ$jvoFxnWW1!fNk&U-IZ};@Ozl*0vp{V}*50C~5gOy{&w zK*UW9FjF6FY!x!9^MIr(_&sHtL?u*Jve=??Q7}L&aL0+Rpy+io@a|AgFn>M_UoMg&<+CcV=AR)4p!O37U z?o3f8Wkhst@A+W{C{&%~mVrWMc!biBDCCtxIFfR@jTwD9ciJamP)H0CfHIX+ry$X4 z;gQctTK`=<+&rku-!grSMIQ8VD~cIJx70Uk@gFPz4aYeQ1arrxTmBRVg1p|vA4&-l z9VozYI0UhE+%Hjf+nKq>cJ%3e(k4c|f}U4=vfk-sTY>(`uB z-xz*gjq8)*yS-kcSV&q~L|md`mfYtm3%k_4V@NK;egCiGr6ArWEze0YFZHF!TCPXs zdtxKHs>;L#Fqs}t%*8h(4r^bl9fbd6HZ-JqRu`Xui}S96p0NHfI?UaR0Rq32v{E3JN6o!~E9C zqokZ%e^EP0`#6r5-+uQHrqI{wn=FRWqTc|bO1VKv7qkdWv=v@~^7PbU(?V-NaE zmxClKUR95mkp90wBr!UpG($^0e|sV^wPXz1C1^8+zRLfWmjgN{;VV|gWw2@QBV7{jP0cPr z+5=s~al(GMrh4lzqD9_$+zlD7xV-)?cDZ|{H~yP^bg<2M`18_??GnLAgj*~vTyZ~S z8kQmWbLk6>27pYI&m+24f;y_Ry9LJ(|Ezaurz;T?5kKWqM@B}zWFRQ{T4{NCOKP#6 zEs55$yEJY@};1EiN*F!bcoh5^JAy&q# z`oV~aiO81aUMs7jrZt3H==0JN&(ou{V~|HAlqSstG$WJax(&?ta@jHnDm-qqfdt z$~Q0OimQ~AIFoyomjmS?&umIjr3zGTutqY#=wYEOS5?)>HNA47`&GMB`uD#xCa@;@ z0O5Y?OZz;a!`^ARgr?;)5C9McilS`kA>%e7*n=LsHpZWVc+v-Km|Q1*R{PbZk|oyg zGe7u3g|6d?z3pDFnd7R0qmssKc{_Io6)x>W=VT;Izal1$kllZZU*+_^8g}${*1mo3 z_CNBtBsLu*7nk@V9laZK-Ts5P7v?vn#4Pe0yfZO8-Do*dF~s756kgQ*uO#I^xcJOv zydy07z6v)t*oe`IjQXT$GC>s{bg74KwHP@Imeb~xT`WNJm33e~Cuo0wm zOT^lF;vyw0Q)vTL_*U1LChkC<+*H(Wct+a70pz# zK)N!7^B@eB&ILeeFV>g@gNHlk zbe{m+_yVG)Ui7>~IZKVhpWLPqbtQu0H0PS^%5E@qt9T}>2tCv5KbgUKq^pO$Ky)CJ zu2p~EHNc|ig0<%>T!@w?y*3O?IQoIE+*Ya3vOgo|_W_!&(yVn2;Q+sh604@BOdsgPET zi&8E7D%-@FsZ=Vs`X?pO>xBE?4lwKhcIzpVJ+e2=^6LrZ4}|*kaOpZ5)4fro0nqT0 z3|mjl_MY&?lozD%&#XmtpsxaPfqSM!BRg>ym)_?_y~xzMWEGi+@EbdQ^Jo`DczZ^l zPH1W&BzyW8X^3~Gj~S`?7WC1^O)EgcZj(kNyrPV5{*BiL03{Pn?`v-5&Q+F?q|gEo zSa&rq1(pOqN_5+In?i1*ips9>?4vQ|YFIh%>U{wuKvdGBgQhP7#0kJzx+vxfij>pg zoRV_a>8PTrcKQK}x&}Sd)Y7JBI=Xmzrl(KOPTLjHGh-71Ju@|;e%fs<$>ZYi>2TV8>2&UP+4~AT^YNu;e*RbK*|mT` zdKMH+&qA(;(z6@HFnSgqLC+#@-lAtw(YNVYOe{Sk#l_RJgv2CzmYhP*QpsubEIlKW zo@HgzGfGY_JL#$ zY1zE&+RDkg;5N-zj^=p+2Jelx<>*0v3c`+pPS;5KcPuvkI3-+g8xlPd*m5F>=}dOF ze>n_hg$n|K1*Xz@AP^XQC3jR50%0hwRy1XRLK(&DEzUE-U`(e~dnRV)(_#S&3mkr0 zy~4`M#&%jQMj(*L)2cW-I|s*U*_4wLbz03uadB~VmiHl3n4HA-)Qgnlqv^3pt_Njwps?iN<>gpPFgNCLi-JqqVtxY%R z=;-Rw4S2ks9^Ig?Z(u+-7#bSU4MxVsCUk=dfnZ8En3|c<4d&(+7IcH9rIi)kU~O$< zLpRvkoPk1bxw*U34IUmBFVYR3o?c#b z!=+1?FVhX)-dE^`D?UEHbc3&-pFiE;fAuQeaP3+^0KFO*7!*V|1P6zN&<)qGhlbJ( zH*OH=24YxPINcB)5fMo@MBco4i*C3T6-75hN8i3pH^jum#?lQWQd}I}5FekAKsO{N zCeaN^$;l~nLrQ8YnQkDbrO^%P=@}VxLuO`H7Tu7YO`*^YIXSuXvT0skKHZRCP*6xW z6yCX0L^l)_7vH5D?%umcHH# zDBbY-_1GBQ@aE0iw{*k!_{0R=FgZCjMK?@O&(IAs@7~SQ4YPA|^K`@f!onimu(-5D zH!Lr&tk4ast7~g?!}|Kh2Hmi^`TjlKK%;%28$NvexFuR+aeiz2z}{yKmL~Y=)7^`v zs-{;yf8G(?p)atz`{fILfv;b`eWNe1xA*-!eS!V`AM^!&{QUWgei6S84i4!Tad>n@ zzlh(zPkWf3GXMVl_m6%N|NVD-EYV^wesU74RZ4Y5!~y_-dwK1Nz%|{fnIxhhE8E)X zdsYBK5XtT}lH|t0`9Zk_P?K^qf&h2D_XMvobEU5p4eV1;O`V%q+HHh)~NDvlE~onDd`$i2cFA3H^6_#9K6 zP(eZgo_oi=M31-#G~QF12Xi-klWBJ8lEH)4k7U0gKt`TG^U2fh#ZZDN=O5z-J;yynhvj2Y&&MSOG3;NR=$NmQc(fw{}i{3t$m(&FCy{){}4976+_s{%43EMJUuu2@nvFL8? ze~F0>72NO=QF9Xmzil&)i>VAl9tZ!BK`&${n_BV{ZhM-n0v443Kao6kEku{ze20kU zhk;k~|N96drW*lNnT>G=_^rSv*|X{ACuW~muEzsWtrC8bMYDv7V@tekJ=sT528yiU zi+?%Zhf$j%vLZi1rh@p>(jM*f>u*+gHSn({1U;+1Z+Jn#LO4#^Vf%Zl(AkR*7|vJU zpD6KkO8AobK3}ypp&tL&s1@}wRm7{F%QJ(})NCJw{@p^=YhCU{e(J9BhfU?%!-S$_ z7zi8kpq8R;6%s!6wl0^M9bXI$zK%nG7s^%hIynJ}wlM(S&2-02bvHcj985CBed&yl zf&KxwJO~UAATkO8rr^*`BTcBUH|9oBtHzhlKo44eWO{mvqjtcrVQtKtg@vm4vP7KG z^5B_qqTFN82NSP^uURUAqgpFE*EiP&-OO!N#}FBW1S@7<(Z{H(iKITxEFq&CaR8q; zqPOBL#yMf5ke^Gh2|bM9FSf0T0?puePr}Yqi$4uxzc7bU@QFYidw0OOFAftNdv)KZ z7!fl%b)%t?2QO6rY{Qc$Qwl22PqB49^4r}wJ!(pMub_T7MS_=>lZVz!fna{85KSIm zIXgxSi@pQ9crDw_fy9g{j68!?kL+1nZzk(YUeFiCL$!wO7-INf z+u*G}p)zV+BF7>QY9NOPT!`zFT@#+7nzo}cT;`mjgv^DhoBrijMt9;r`X<&xgd*Q5 zbb?-F&umyIAf{&mH|C2`*Qp1*_z6(&>K#RV3t3l{T3Y2`zq9q<0gaoL$$aa~A>6ct zxbBipjw(tNP-0+QrEH~=i$oZDlU&PefMw&T$G6#K(%s?dVZ__0Bv#a8_|1D>XE%#f zmnL2kXhav;K{Bhdul_71T%y4{M8MC8&xeU=Ngu7f^{Ob>Pe3dEwxyzl>BBaJ8)^c* zjPYL9ZB`Bee0LwUpVyipTlU@ye>uOpcZ5zSN|Vn)JXI=`p6K2F9vtKde5*iJ52LMS z8j=!F&OA+UjDVIV;0^YQVz|llT~^Wk>IND!Rw{BXXqh~-PTtLMIB)4V?HOf)xqe>q zn80`gTvCLCYym`WeO?yo*<&;)I|JKEq{Jo*O1`ml?^ItUVpXSFwB5dHxqs5a5|%>H zV=t)dIF7Izv~^$>DB2kuz zQcCAj*=YDInB986e}K8CnBt5Xl3q=sIFl=yD^ZSV!?UI5_u&I;@MVBC02skea5-) z4@{*kC;r&njejOwAYijBtpawLfPJ3F^`{w4cW?!A8I2FFBHnucdr~iXyDy0UpS5r& zSUp`Ad>gsz5;i}$&8j;4HhN_iHGE==PVWaTcMKEF9ci%1ATO=C8{imt)*O1CF+YF( zKQi_bZkcfm@+aM(U1;0S3hFa2PG($K>8c6#WYYVElC8Jz_yw*PhkKZ1+zG#`Gf|DE zML{}9KT)H%Q!VENob5bkLdJW=@ApyKEa4;x7`hvFX64@F2$mj&w(3oZ*W@8i69Wcx zKjxXYa>$cwoWo9**B5o=4vb;I$j}Z8IiUsS?4TyW&n@*`k6?UuY}AD5tk=VSO1(qI z4GM1vbvG&Pt}($!@1uVL%03CBrrGH&P0`8^B1eswGc=lINAqW_$v9o!i?!M|T~dLu7VKzFPy<17XA1o*XhK==44 zvaPN=hWLJi{i7OO zWG%mNVi|J-*Zusl2K>$6^GNCytx-qcZm>Zf_k)(I$OOx_Nrx85F8Y~vk+lKa$NshQ zWR|%Kxw~@JH~iqlCjxu5d_U8N)s3(VNNEdB>*(M~)Ad#Nq{%jPm&xVfXB#uR*$7`4 zl`$~usUWk}(^kpvPx?`(H|n-F_#66Ttlj@Ig)?S9rHI&+tAX^vQN2Cjk&%f`Rg>)O z>NMcZI{%xB>jAP1melTy#OHcJ*!j7K6qv9pOvl*xWirIT9IYnzJ=?|QJ5H@+VTxDW zGT$ zj%&HwAXu`!tHTc^I0oLmgC{&ZOy#r8lmTVD-N`cLnB6CobBEdX&}Kt2WTs+;F??74JwOD{G?F45A zFf5>L^^jsPVfOK&JFmWXx}=Ki9lao9?*d#Bk*5babGGgE)HTiDRD}I$l0SQMn7!_F z!gOyi)H+uMeNr1Tkq?pagcNJY{xVmJ>$RSg4zY508@>+g#t~=#f-gcy2)2_tG5DXC zz=)+oSJcjMuTIPINv1)?5Trb}Gi*`S25D`B<5KD_QThTAMWJz+H604J}tT!ai$GbC1TDQ@KZMZJSxKZN+qGm^JHqt3M35GlooQN z`rKw!m4lrb6@AIB&H5f38HNa*WyWDxj@T~Tq!299vinOpaS#+b7{K^rpK zHagnPG#E{SeU1ytJ)TW+Dte4j?Fh6EZ-I?*4a%W5#Bgm-df6FE2LVjPTWXo>m&@6=TGlRbmMp+j;GG5rHD~m{!c711ic-DE?Vvyn zs)yLOxOEA0p5tg|z#_*|IK`yd*l2&GpPjSfCz~^9sfC8r_5+%xf;&C|DV;`ow;G2T ze=?dtz8{QMen0AkGR%YJlFqaA?|TcYE$*^*Dk0Duu8NrE(%}jC^)QBr=Fd?}@wVpW z(bOu%Fz0N&c`VOvBVR7~-ru;i^=KK^vag{`>{0vy|Mg;B9tugwW(qu+aqW7u@q&7K zYwE!nVko9019OQAQZtJ@?4ZE1(=4geR3V1GEDKGxPnBLkIJU9|FMv&J%SpS;a51p3 z$YN1vLVv#F3?{I+&9N{Stf=(r-{HSx>#}Y@G@v_S+jAhYgB=_m_sPBI6BC&eK7?(u zyU}(t#9j{Db)(+3N1>-Jcdp~~Tq^wzr;oIp+D`AW3Ap}D3fA(&eAe-DYXMr1DUj-^ z(PX}!$l_@fuGHnf#(A%`%Tp}1A5=7XM#PXOK6!A=%)^>PC$}*WTMO#Xp5CfUhfEXqZF@G3dK#U$UzS&I z{!G^%IC;1%Toy91~}htlZy%EdLw1?Ob#mf zoYL{b<27=iD=jajFrTLmUZCQZ%hSAP5<2 zn8y&xBOxhTgnpIT=KWeLB+4^{P|2f|XPI6eQ4)EyqEw^@J@;S#eLg$id+yym_uSp@ z_uTWn_uO+@8m)swHR0&q7riqD9Aut0Vcih7c1^cfyt~}_H{;$0bnC9)DQb z16m8%k`CKOP5;<9aj6+)K9De1zw#j9YM=TV{%`zbGlHzx7UDI9hPSWD#Zl~uX$e;< zSq7)Jteo-tunFr;>o0z@1bbRp{alXnRdLgo_|(N9C%`1gZ2dt7?&&Ykak+kj1@ZHN zU+`HDv;c?2M1gyS{HHfbcxRBJa5^mYPrHlxU7Li@7bt|4?q`?6WnFLpd!^gWGsn}9 zPWvxNiX&j|hUbC%)T9^}-Wvo*|6ZFl~eXz;G~ifb=E+M@~qY>&271pSSY8T z-VJeUS$@WoX&{}4Lr!Z@o<$s#kC zzRqe`Ruj!B)xqJ2S10kyNUs9mlW;_-HBxuCT3*nLh(lfvFs+%4o(UT%!(9^R6VO8m zYNspUbXkPnCx0Bf*H_Hx0t1#c)&3!C4TOB)*k!dQ-sg5nexT(l&HgS?7WUm8=8|QC zMLiO2<;WbM4y*RwIFD3vgf(Mz2*eSpU+oda9l2*#7B@~Va1}NDngOPjmTlj!j0D9j zmdTr*gpfFHC6T@ywDK&Sg?H;y&)ouMQ1Zpx!__hi5ECg=pz~_RG8Oyh6}B{~Ge=RSNoWE$s?nP%M5CYQ- zPN#Pw z4v$WVpU+neaSjHpo1u9KSI5>2AENZkh0)bf32j!OL56!cA8w=y0v(f3wF3cnC=#`x z0;-!ZWW(7c&6`S;@D&Z4F8jA3muhe-N!9&0vqFoRDZ1Ok|=VnrE{Gqj1?d5&8 z8#TjvWg=^8WpsZr4F#yPHl7nd{P~+>&jnJ{75djp45u{IyM40U3^<$T)hRGaIXd;g zz_wGVF!PaV_a@*jtd63nWLssYq;Bfm5AiLJb9%vMe37s&k9yaKqV;rZH>vy`RL!ze zS-E=K$4nrpsIjqkVnrCId2J~}T$253A|B07YOJ{YpQRK^1C)$hAe>y2eZ2Q~Jh43< zxi4+dYYn@y<%L{L02OrjWA%~9U;Qh6dL}1y!0J~CVkof<*OSQCr=RDDyiBUm1%|^G zc6rP--O?Gz+1*vB&G3{Eb>e%Yj{V@nNqvQ_hBK&7dqXBky>?p`eB1+wo z^}+=4huU%sif%d@3V(?2luU1{j6cNHFwT_zeHFvHHyJvwT~pF129B&iHA^DA42y>zt%DBeboJ~>^#+PQIZDg z;EMMQ1bjGd)2g68&OMb>y>k9)^ArZnD=2_HhwcmtsQjE$?7m&VO703N#zaN^4C0dQ+melnS-Rj^K`o! zOU;eD!$s?x?8A}`B#KX?=oTdiMVw>LV60aCvP*&m?W5x5!RX?h&m$PJ+c*|IaIeNJ9l z)Si_Tt|heWmtfs&xC`xjH|RJ&Rb0dEu$R1w%V$ZBJx3>ZW~)kD~WP)1vl@ZRzekw`No@pEbSleR0x#(c-lE97G7$4aMGh&R$mv z{oTi^y_&sSfKWPWnty7(r%3yhiRrao*+=iiZyjfSoSip)o0Twg;pw71-iWQeBX60o za;x5B^pEiA#(^30r;Nz>!$Mx>JVeNo| z4}1h35qkGmFfJl&k2?cqem@bW`C0y#eY@_aiN3bO%odfEvpX1HwcwKbM^|zR1kdK@ zdpj?_&xye?FT6CN9l)NrZ?y=uZ%Rff%is3JDwkjdpib&+9pfN9@Pkc-EqM z|FIX4#l+6`(?Y`xWD}R`nV0`3@epp9A(N^`2xE(ZbTisFMwB!YS2X*GSx+A2koP;F zLSQsbj|{RGwRCQ;LV}c$_uz!c+1!o`2M(E#Up~RXGP@-O*bb5*)d2P=>&c?l!_JV; z<|D_CXLC2QqzUscj%;NZ5&h@kw++I_YR&n_RjwBN_HA{AS;x` zKe~>NCWMAF;1;JIC88W3Y5pMZI*>1a8a)clsV02%S?T&Pve7hUJ=L{_wFCfQio3Bl z|NC)>H=&)W)WzkGDO6RvUVd>^z4>py$J)U&3eAh1NQd0cP0KFRX!4_J*=|$&-kes5 zH(l@KU|qI5>sfD|?5xoI8ACZy(jh8S|oncdwruH;n&TTC@dQ zEB{vfJ_Wt^#Lnf@Kl72d^J6FpI!dUD?@Xh@(0zHj*nvGqgfj_Nh%dJ79}=5tN9qdJ zF7__m&aui0#cw=C?ecg!KQ9oV%zn=(HA`GHoa#Csw|=oJ$ms_^2)4@u`{~s6pUQH# z<~=N}cQ=aJ>xq{B-zwy~({vC&AXma)T3=i^4hfQ6&hro&W=;h-)y2*_e3Uq$Iw8?3 z_2Sg`h=%Wngs~`=!`Bywzs{XnMtx@sxF1kx+=RHECku}+$&F3fyNn{&Dtc1djKjOT~G!g1*vpA?1liWaUwJSi|RR#+d8 zY29fi^W*~3Og8xuC+{Q$BC-ckmJVJP{8i``;`7D5%fr1*~4+B?4imzk$vM4C5=bu-WgE}Kk*gYEwXMbbK88dHdQ9F88j=v z+QZ63ABv6!$U{_u6Fiu)eAsHQs~o=ewqSJx1nE}6NmKeheH2dOm>IvZ>pI?MQ$2V* z-OxUCVSE~znULY?;!1dFcc%}dc`;4dkWYFHmKvRaJ{`A}^0vEcC&vTqLZDk>Cck+N z?n)Wyp$@`fJrJPbAat^iGYAL&th8 zWls4`g`av`?8CM{#4c)UJ%c)`;Kol?9Ctx0;xT-iZ4(dyWCj8?52uthiDE!m&3IV|Q}6Evwd=D(`PjOLsFeQq)nY)v$+F zcPuONCHFQR>TbgbrczJ3=5nCs0*YJEKYn}^bp%ALK;;AEH$>5hi2Mraev~0-p377Yli^{P+Ukq z%0)jDNMx5GoOt92nuT}=?(PK$>P!c>!P#n z$7>6HRLpF)NW86w9iH@oIOuSFP&n^s^AS@QJ&F7W59tp1olhvMj~b($2hpFe-cprJ z6_0|Fw=U&@4P&0Ko-fM39&C3nYl$Fiig(shWK4nyY z40G+vWl`Vi$DS0s?BkBpDR8*PV6lf0NvY26nVVZ1-N=aMQyw1hz^i~)Ui?-TM2JUR zAztM|i%cPdUi_@Oj&4)hpq%JjN~W!pP8)Q1!PF$$hJRG3(!!QHpkp#Pd&ZJ&>LqcN z-6zJ%Ot49-h}5llp>EBUzE>q<38@RZUjjO)cv4<~;>Bu+BEzW3 z>*xe&ymJn6CVaUG-Y?0HT{?K-ZBS7BoR8EDB6sK51S0oKkYcQx!m3nyz6o2kuxEcM zSOF`LW_JY^VZs#;S8YhN?Kp?a6~7ehSnv>3 zmjgAhZ9c*+)@cd{g`4T@ASIsoX8qzOPtNTP1s%F^k;>rMgxM3TfT4-nyD}uysugkVevmd)^qg|Hs&X`zLh|!Vhe8+Paf&FL>qQO3qyQ0UNQ3vALBm1_dpT-gn;L zJ+cEQ3l&biN%e-wbm4*Bt{0g>eV$g_L%;aL!^Sk44AL=gP9(?U0 zrH?c6hsK0)45h%9P$kdW!XS&|p(~(YK?5KGZ52dT?IE1b~I0DP{ zMs^tNQU-?=?uo8I?r45#{`<+50KOyd&L>)!odXLCj*J7e<)_s33xCH?F0D<{+uJS_ zQI^2b4R$Sx{Gxp~XXEkVEQzCsxPAZ}EI=Xr+T&gJ74`P`gu6C6r-+1E z6fSN%?)6djmK$x>57Gtc0zb+P&24Q7(z+#H{%yX)}LBJCr`TF3Qtaf}2NoCcJZ zn#YR(T#PNexr-XZ2a)I)+tbnhdR*6I+HvHec72-7bJrS8=r}d}LH{wEX}ATqlyA3W zf*Uo>_83@IVxIJoGW{xL8jc*cBT1QNOBH2G@=de58i3paQog*vTt1(uD7{-pMswR; zfkrJ+rbxXgyk>D1He=01FF*YdSRJ_!qS^C7scV#indy|B!|C6Nv;S%dXXWXL^`!@_Pb@IZvSw$d-_hr^kJg1dU|@g zjU6J{?00&2zP^Oy{dB*(tpsUkb`Q^7WLJi#u<#lcb1+ z4{c__{cDrECwD9>7d%s5C)fji9)y=!gqjmnsLB3C!uSlRmXc$q)BW29ANx*M%5u=E z=|0t+X*sBevIK?`WsEgLD3+pySdDD!hw`<`;R3CVZ#<_ydCu+Fzwz_TvcSJqQmbq0 zS|{yVx5dC?S$~fH=_7it@2d8F z)gg}8J1R9|MIJUxS&%~_(8+d7RCaf!nexL&!Y>#cIok&%ndzfKQhWR7w%Cu}Ur6?Am|I0RQL| zkCKi0uK*jQjB(a%r=;fm+aG_x-+m_eO>OfB=X4Ogv%aYW$M!GfTp|8)EA1u{O5xzy zJf3D38iK=_bH(IwFlgL|9mj#lEFcnwjvV1Vg8D?K9+8;B8U#`>_K~RaxHnH3(sd9G(|JqK>hGoQg^1G#4ZZsS_~om!NjijS7o>$5 zXtVWmTH~LVblIaClT+vuwn7>!g!$&se zWuEHp$BU}PbW?x|j|?nW!2rfH((`)#)0E|)ux^Upd;%p%hc`Qt>} zdyn-nu8wL4A-GzoJWuQp4wr0&X*VGm&zfndJW;jl+lvdjoQ%2a=|=Rxam6uDh!!4Z z;UM`EC;;SIPGKxb9ymaBW|9d2pm8@NC=G~lGBSd}D4O9^$88<}3`UaYfnhK(H-ZQ8 zggNJjq{9;+9x$#b8v=o-hf@&{$k+;qY7F1)1+mzwvVzFg1~$|=wSCCSd(-!oWkqWH9a)2&^WZeOc1B%GU4P(#0c8FP;Xnz1d+ z(^>AVs?vV^YZ-~Q0$tB110z=5$4gwmN{X1NxRCPzWYqex@sE=*u&q(f$@1Uit6R1@ z9-np0ESHDi$kipgFDz{fJ=v4<=X{VOhWzITl} zAG*@#KAN@ppUO=Gkd9{`)6UjR<&3Hv9^q+^AUGc~ZobMrYt!5e3sH?pbPC#6xw3KZ z1#hKIWvffL+H~yE+d6C=mk@L2x6j8VIXxy6HYn;9e9r@esB0~5b5RRY&fUv4_Mjjs zgD5{3IH@%@JaEqQInkf`%>dMuIHzckxE=yZ8oKL~KmYy+&Uh}kb6BO=wdPI;gHN&} zC@4ZkSwY)m@805O$xFCV1dy)z17NWEt!)h+CfrC>^AZ;|o5OTHO1jo|FqLdh-C@sw zF~BN(rXoX;u#Lf|FciqdVYu`2aG&WQqqLxxy*)L_eCGAtYj>WPO0_iwlduBOQD=7* zWg9z;yL3%{DH!5sVHEVK;%dk1?uXNz8F1YkjPs4I>wCNKH*oPP#M@HrbB8OU@s0RF z!U@}F9e*{i+3YpG#4{zMx8K(zJd=jBo;xTbKZaFtt_i^xHoCC?R`^8fUix|#sh{M?2Aa)PMO;Uj2FPHVs9fep!9OAqIYA{4|@*z5nTC|KN%uEZ_1&u(sDh zi>OuqbN4H*(v=MQ7TnLFybp*8=X|_T{zOXHWz=GCnAjymcWUFzr^g4%H4LEJ)D8Gj zYFCKF%O7`rJoZ32_3==e@GW4l|2(5FbvVqSTaOZEqGb}+GVsl=74@dq4T{LsIf@^9 z)-}qW??o$K|MrAU$dTad*asr~T9S{|@V(LabN&`$(bYC4Nl5Q6*5v7*r&HgO7La*p zZ|ooLyH8M&fR(WHZml^5nnp67M`3p2)ywGyy*E$ZFu#kLh!HJK=(P!+cx&{SZ{HVV!}TmcVoK5&Uk`)C74OPF zEuU?Xxx3=sL+H)xmXuRiCK7h%es|qL`#@6*^q1qlmrQiK|I3V(FpoKW`NulGB(Av0TqPP(9-3v@4sFq8v_4WN$L#^^s%g_wvp=TvJqRumA72Mc6rT zBDW<(fNW+1f)$vzA}c?LiSPQ^8`wJjp_!*EJ~uDM1?Iq9rXbJOWL|TrQKC!YJY+tFz@~0T z+cnS3etc#1UUj?Gio`anPo`F@7iL#e5~i3|Sysi{Mk4fTe+lvRSv_Se)&g zb^Xz7Z_36v$)&%SX6h1tth0B=|@#TExZP`FtwiC%u8<93LY?{AvTVu-c9@dx3O$5w`#X-};7n19|EN;QHJzrIN ze$JlnywB{V=}Spcu^YEQlBAxjGNU}>zzH&{sg{#u3@B25 zbQ^-}JuBk$Rv8*0Nm7vtO2p~ZGOD}MbQD4!#kGsl>P+pX>UA8HRTN{|&k#~(hy&k^tj z2aZJi!;WM#$mYYqEOIvXxCTX}++SEHcZIvT#-;zm;u`*86Hkv8{lkvl5;4T6m@A)S z)Z)|ZQW+2MY4*E2>Or=6KV+^bF3*uwP}?LZLwiaO0rQZbl$01#&Nv}j765^0{ z<@sSrkskmLe{arKVj9YuW9z6uaB@H?{$RvKvt-dKqZ&ffxlJyTlA8}8pkO8un98E99mi1 zVsbv;gT)r-I#f6e`f0lJ*(5*qUydrCl;OfLO->6k>g-6<63~Xl(md1lYhu#KR~WY2 z(0=J6pGuX0xleil(k`1i=9hsfEaS2=u4LTq3fHh59NqcWQ_Cyl`T%FHEnD&5Ej zogAmL^H)pO^gn9Z;B09JG>2^b{}yu)IlBK^%(4F0;>6Qjqkq52-IIGrGc1k#&nV3j zl+0uDeC-*-;F@aCI#F;o`fO(a6qS57pKE8AZ5(^9N&x1w&#j%iuX#j5V++<6BN1CT zU8JFwh9nmLMGu2z=K8-Hq@vR8A*3H#}`XU`wod}Hx@?~@34vD zWGC8fP=n4FOWzt=2MOrsADzF@cF!l$8CDTKoJ(#~LAFaz3URYPTOsoR*{Wt(O9Wnhl=iThMD7A_%%F1cI^@=?K3 zE{THBB9xWNqlqoNfuZu|^4?r>8nQg!gBAz#7DXiwK%uVK)s)J25*&h`!5EXi`sL^# zm`G~}(5WWSR5XC{R+U4EmL|kx#m$N;DtG_NKT{Qvc9~nJ#ZBdoqdhDDfmB323NZ5A zLaajz4g#=CXrQs86F{$qYjAULj(`+w_c&@O-T4H6rzv@@dD;p;`}IIwG9V9P1*5t$xjAUeJ_;KW8xivbmleATkB?dKoA=FJecU3ZqK?(F zy=EHhVB-z6yi!Ykzmiklb4;NfE;pCHl7RcT7oQKQ8AommH>Ntg)OLP z$;5TqODsR~A=zn=c-0{DR>&r&3?iu%HhU7}*njj%dMmmnyxZC@%EN@`=J+wNle8Q|XW!3?T4 znBqn}i=G^_F(?r@9*&Ak-&?p=X0vhn;u9vhFRoEKP15MYuGs!||3WoWrJL-#re_?7 z)w8h1L$tE@pz@n93XT+)B5&3ijFP%}fEjlgR0Ya9!D5ht{>sIbAJXechmBP*=e%e3>W)6?e8^ zR#b_3J6Kd@_;&T}o1!ZG+w5-9euqkn$}8npE*MhX9q&8E6w&>7=gOT#23$q|&To!D zh8e`}aYFKjNj*6|7sa_qV$UJxec<7qHOhjmJD6|1&}SjKu@(DL`p!`IF)jOo`vjma z0V@6&&yKG>pg(X-RHbDgZNR(UfiFHVg`m3Fq_|!a=jOcV_H-BorH$=#9W;ta_{!^d zllZ&;MqCrvb?feL^l7}B^1JB_oznh$mU0Iw(#43vk^&bsir!zLvO@En`y9Ee1?j_@ z=?E(@c!5XaR<7NqAi_aM1w#h5YHx;y&Ymqxf3(5hHuQ~>4&mm%eR|17ZE2XwmAl~W zKcISaBVB6n@RE&;O2ddgz4bz+$UjZkb>IDbRU}=1e(tEWKUjjt)}rcjx3@J23d+sv zSw8wT0u)8BdU=mFsB1}7<|(d!RWxt3-5|3j(boF@6!o^%LRvjBu{<0g`zLnV<^WwC zq|6ZdP@*1Lzzg5s-ImF9z0CrA4I>lDwtDh|17v@hXY53vD;#ih75yY<;%VqTG2KW8 z1s<;7eud7LxvFgIcqskHFqW}9bE(KSLSi6Su{TKlT#RS$Ka zs{S10)flQvz^tj(=Sef>-p@RSpTG9ydQB{}Q z+Z(woTwX>q(W=YKmzkc0*s`E_@H^rA_eLP2bMN2#Kpg2v24f7qB3xM+2JvYtD>N|z zy=8dVLGFX_OpZo0iIXfA&uJyU>b96&s4%tsp03MiIZ(slUPxya9uTL|E9nREL zt0K*DU)8=wi)8%1uP48{_$M6l9OAJpL*~8}eVgWq9++<*zTIlNbyGt)Y_rW^ZUeF5 zGAw#>H-a{_DCq(i0&JGgb;T^cvIA?pIp{4#XJM2?>fY3|wN@!MJ?SNOl~|3>{*M2RHJulE|h5)^e0MZvkUKTCg( z{JH+Gi+=|H@Pdrs%)tSM%QmL4Kq$-XcEJb(gfUzQWt(s(Hk%F|@{k7eJa+L+r9pS4 z!4LtMTPF?1OM@2aAWe{*H>1km12P9OFYFGCm4--*LO8$Da?5r?R3w2XKpwT+G_LEX z{fu=&+w-{d^^ktLWw7(*kZsa1%(h}J_w8+DuMC)x(qnIxW>~C8y;*=?_-S<()V5x3 zn6yVy4PFI*E1FEb;r9`0I3`aTzUj-EXO5^LcL;nKROAU{fB?pCMBWmqmqb`ah9Xaf zqy{_?!-)D`2z(NA2Wyyv#fZaM0-i|6+*erUf$*4tC8%&x5+Tht;C##9C zqjymDsn(%o(Z_i-6?HU~g|cO~{GSJjbcqN5c#vqopm@qWX^ELv`sN_>wSE-%sI#kT zSZ1qe)!$xUX)i(&jHKwco|Q|L!zy60jRK#0`+~8%8bmB&tFUoo2N=ids0F_=823Pu zg8j!MJs9^%(yLO-94tw!r%Hl%2h(RT45<@RYuLNNqLFZyx+D#u)Qhg8idtUwdxP;> z-ZC9*dT9$_0CgN2lSmv*6SOZU*C~J_7m&Bb4 zW(bHGa@hos+!N8AN8Y(fx?i$fB$?W+YmQg(4obyJwG7TZ(HUb6TLEK7@8oYB~j ziNBVnU;|&3_5x}7uR>^j4b`hmlZ8p+> zmLR$3y<naGQ#VXeLMn3PC;L>Pxw1z8qWD{DIXdG-@g5Snm zxq46nu_JK^$iI3dC7GL8!CeAS==tohPV_1U&D3Ef`t44)RY=C2l9*x0j2*}9R!LV- z7i(0I$VD54>6;`SY^pN3F*k-+Fq2!&OT20eWq&$q&>poJ{UL&D{hYF{IBAVSn>#QV z;^^&9{kV$69cZ-Au;kVh39e$r!j3Q2raG!lDt;I&AI$SMlQwgM-nxP>zarJly;QiD z+}g@JbCECJ1nct?w zCtSE|moJWSZkK|sgzRj}umiK&T4=rENpiB51t;@0qszB~t-$zZ0f;gajx%~(Ij^CT z+kV4uw@jm8{J;)F+R^lsTZbb&gBS{ll=iF?_K}qjHUunXPF*XcS|DwsmF?#s3MO96 zQTKn0w_Sj(6X(@!9|#;60y`bsFLt~BDe87`yYr%ftKhILQWc5zuN){?t>m$zNOcD< z4Uv3ykauWmibjD$A<4Knb;tGfs@KPze6U;8og)4TJ||tL-bp-!Rw*LwukMU+82Siv zj>77zizo}6PdM9%C<~k?I3m(qwyT1Ou!ZYMtev`R{y*98N_SmeRJ#UIdZdCWQK8sW z!?yWm6V)MSsg&cMlF>W1lnG%!DgvS zc%p0)Z+6gBE0m&`~V~_;nsww1VKt zSM{Es3)znHFBkB@*#$M#3ZSbR8^a)n)`B*OFP?EjS;K#?bpeX24*o=l;sOGK84f^g zi}sHc%GD61%7uG^7&w*A4jZl_Gk^#`%k%)ViCw}#h7AfM6b6aZBb&hi0&Ez(m+Zg& zWrold!wTOw0q~&Qr@=K9I0I}doFpd5`=6ND;-%E;ziUDrr=cMYJ^eZUU5+=LNgP9cQ6Gu z4A&G!BSqde2L}%6MB!9;St0abVGKpj9GH08OQd2BQ!KMD6db8Lth?N%f7LT)Qm)Ro zzP&-cj_eco*tE{>=A@2s9rqBJuUa7Aa5bX1u7)T6Po3M|3KA@p7o#VA>)LJ*+kI8# zsvy_cl+T3~SGACR_gfO7Q@6)Hh>j z)r|okR&|}I@Z|pzCqhpngk?OtHu$qk`&i zW$;kaaVXb&ILSAuor&}LoRrDsnhHFdZdscqKXGwa%{U>Iyzb)8>^|XjLd7?Yf?DF) zt!tS67yV!Jzv<<_{Qo`?;i~HY^(NyRT?$N3A$Z=yq*E@Zv_z7+s73em*E>}Q?}n?%=as2Qjf zn5Is%*0o9+o~N?*#o04JtWPkt<=VM%k?tjmpnJZ8%yH}02Uz|N$u{tG-(yNzM=q{Q zvyV%Ez}`yv#~%9v`)?FoRh|6^??DTBE}ga`#auKqq%~nrq@)Fyi)MzjyJ@*Nt_~;+ z#;z<7%~jV=r+?3g!R}1?H(6yUWSkyt5MAB&7*_NxUFh4sxyU>jpl$}kO-oDmX>CueQLy2w zR*U9_dp8s5xCWnZi7;w4M06g71s*1Jz#8?g9_aAhjq<#>6JG^%OfqozrjEf1d|J=| zyCQ_{&|#Pz4b?ZuJZSJ4&|<;t3$R>qkUR?Aq|pV!m;s15{Xj7m9nLlkHV4czQQ{ce zd@ZyDn_KOIc{*6n!&YVX)_+0HLj-hj3U?=fZYYpT{r5~)0KRbbg1%@X&a{B93dL`3 zB#8Ta`q_cALkcL8O^#^=AkorHP++PjDR=)n4h0(MU_+t|Xs8|m=dhH#jcD7OlsHU! zVGz4s!MDy%X&@SpvrrmVh0;0f?a~4C-Ar^cer%F%Bw75(b{ZY>b2pKq%rB@f^gN z=@taIFSDe_rmq3?qNndY2qM-a0ygHVP@BG3!tLR+m(P} z)0z6@<0&Ey!+uLK+TI|A!v_015FwK2#+NCufd-I8iYH^3!eJ8(6{vG$rt*W1!}DOG zHhcc>OBN^~5CXDixx5INnt&+#MDC|&W^{x3l)irb@(TX7!=!ldWK4+UNO;8}t4`=7 z+9EE2{4yjib7DXjyO;tW>w%{*xJn&yu)GWwJ>0Wiw$oAO;`4jQAS5~N#uWg;Q!C~M zQHJhYKL;ep9Pc6Vk+2uhX3Qj=gj}8O2mSbL-_P0plpakp7H;E2)T-g%)VE9v9v(6H zbYpDnu)&qI2Nd1VcvfwR8m{~_dd>Uk=uk*jImI~ZhejE2-I=Nb3?@o-oTC_JqFO(G zl(inr-pO9VqNo(=orCVfGkpI!wovS`UevQZ1jeYGh|G3FFpXi07O<^%);dYEmGCb8 z+;6FGX7D+=ezAWI zI7_nYe)aX9tB~(*y6B_TvgLzM#lFy>t(W)*k0d!Q`JydSe?08_VOsxt+d!s&0tjn# z8YjqifX*yfpHH_3Ej}DhT_|j3iXr|(ABqd@Q_7nCDuZe5Y6v( zuJ3={2|IkAh?%Q5M`0xYqvffPk?R1-(&NDEy6^aC#jBTd6E*G7ZP9TujK|}(9ky_@asHm7q z=7_cfM>uv*(>yUh$tT74vCy;D9gU$km_;^9lD6qQK&3x6_pMq?rQ8lpu-z zU#z9?e%t;H^%MNZ@Dq3%+djULG*>1VPkZ)9m;z;g43P;bZdNjXe-5dGzxAMF-Dvd>48)qaNQ9J(KRO-Jir7=Q8>PKnmB26=e7g(tq9KP#EP4*}3G@t#vBX0D| zWFA^pvCTwu0O;>4IGf2qrphdy1eUT^ImayK3=LD?hMVRDRetndyRdCM{w=Iv#{5@% zX<Om#p#|f#+yWtc3jyzI}r(Jr9|f9-~0IN$Z&BcOf-g&aA%p@t5q}oZ8{F8 zxp{t6REI%r)>d9}hYsuwshIak*t(l**4}!tIR2&i>O8(C?99-2JFa+UMNNW7yvIbF zmVP}X{$7ZW%KZYminjx$27+Tygbfn&tIqb>-VT}G%U`OROu&vgu7)PkrXgZtd}yy5 z-_(%g;H(Igeb><0{}%)(N|VA2w~*v<6PmmY#boa zLWxI!SYR(43VSd;#0;$@fwsY2Bin32m9zldaymnjk3#cFq_9pgX#*WQb4`32CsVZ! zVVgcZxFL>8QnDm>VdKGaGFb*aUC|&EI zXij=-hr+hPFonj4@O5t1;mr2x2Z)YDq)z=}Hqeo+U87S+tkdyrt-=;5nxx@ndPLCs z7{VHD!p7GXBV|c`Ou&imDZ4uA$y~$s8{Ar6bopd_pwF4dTOw$P$9_DL;fzzfddpNs zpNZznjE;yPzO#ebEx@B+i@z3Ok{arTqC6k(qu#Q<)(a&NNOKa4VthbSNgGs>ot&Nn z`tjXP50?;cO{S7ryvAaH&Xi@kA zXh_x+ANOu4TcE`QL#WNAg>jwRf+|5|a{|xA@n5P>Znefyg7m18O zAN3z7;0J;h`y2^l$WQ^juvA68eC3eBGYA!}mL>snbDfKu7)+{;r#z<^GEnVu)nd8E-dq$~NlmLIw z{&-FETbg88TY%Kqloa@}C=zt3rqHIZKH#yCo5LNUnk~5T!15YprQJYsI;~8?hbs=b zGTh0H)R6D+@5E_vD}#>FWE9ebA$UHGPYACEWPrm$hUB_=6m2mk{CujzRs41+Qv2nP zVH)25N}Mr7m#_1XR1s1`ZjOJ@@`i}2*^`tl&|tNkWV1C4#F)*z2Uoad5}aX77=_rC zNo}^pGxfjqKpEYupA;VPD7&-JqO6y455QSp?M1&UQDQu&?v?!=Z?i{J-o#8uhR6YWU&df-8hv=+Vz19`ed+4QwP~K8 z`96|CT&Gb!EEPS{nJR)EG?}KIc21%cUqc<5hUWWt>zP3mRrRdUmtz(ndzRrd*VAl( zE;0)X7>EL1>*!Q?wa?3ZQ$gAr$N@xfnv{OS0rAFnOEGx4R<@9jRPn0!q~p`oRph@{LnLs^HZ=%PZ;%}|7)i+t$l0-KI&lM(`%MttSXH-* zl+o&{030ft8(O4Ow8KhsmzJtTPb3m}mN);*`YRFk*zBlv4x}sF8h|%Omm$@y<={s4 z021XpYlgMfLxpDoNSUTvM>qb**0%>T^~V42>~4l(Y&3PoF!#|aW3tU;nB0a{t5hpQ zC6`L6hPmHn2&F>EJr$u;ZZTaxM5TMGb^EAKrJJt4r{C|-@Ar@AefGTH@8>+vIeX6Y zyw7u9&-?8zA*u*ijJ4R{^%M6Vx`K+uY!xSN4LcYOeeial?oZkHG1nKVYn6U#LV6cN zGiHs0aI?4gh9`lF<=En!@)|w}_S`|O+RPDbQ(lM$)GIePjI821>vM$VMh&jQk)+Nd#knZq7durPXf(sJPB z7T&}_{Yd@e`N1UZ$JMJ;OOmcir3>YThao@)X=n%nV#Ey$^u}E2^dgJPYFh zDhcdDdJV3g@Z1x0Mvp?qv|BIJae`T{YCOb8KZt&-=T=p4~FX* zs$eDt;JwmiTNt2GU-|G%n`*#j#fYdlXESU!PI>cXv;onUwf|sjsgn#-K&S5!?ZzvU zeUc2=qr(?c^t)5ITQYM+szv6^p#GO|NuX*yJ}94=BNX}x?%sUbY$e@V14JE!1>Nje z;AIdz@jg$7C^JZBD~(oPVealP^)orx_xhj=0VkB-O!4$wwBUMd!0186w>(p4utc%j zw0l6NvzxQ?X--gP93O}m&g`!-B#T}Zyy~e@G$JNsP-O2CDQ%;1HgO1L9u!8?eV8gO z9^aVWG>rw;=PNK~_;rQ3YPdjKJqOUBX&<1GI8Goloe9DoQyLVhoC6F+F0O|!7D>7r zVxIIP6+&4_;a{x=(@@>G-kezO$RcIw4&7i4tO^gj2#rDXg2;_kC2J}I@l1|3bqr)U!DXn?>^s1a*X^3<`TPz&fOw=WRs%0FpT@`%|2K|KLx#taKesj^4*1?fo!f#3{hiXli*a{6KBx^I6;hC-BsPG44ob z@%~zpXauTL*V!4WGav;kI}~M^G;%6su?$O+)-+5U>|O>k9emJWDut3;^E4$jCX-^# zQZSim;UO~pSw>p>l#d0RbqwIqgGL*3VIDG%V}M}0p!cw!*${4=R`_um56|EOik6Z*S_1+lnbN$epxz}vfC5*5bZBiu0)8gWDN?Kv$o=#<#t z&86|3?-Gt@43QGLK(9D{Y^*w-h0Jb?$?>EWigmI<_pEVMDPC7DuY@NB<|XIhqIHrQ zWqBualSvA*)B@X)a#b}CU0yJ)R=qGN*j@rb`CQtL=1-FpBRJ^Bw6v6kftf{o#YDr( zIHg(94h6sPGmK>_=u4qkOZ$o&ybC1jikJ2lPgc{W?<*Tf;vK$vANXlkaYIdt6VwQn zwKHC)R^g>-t!Rehq12Ri zD`M(pS>bSAiGPnwq1n*O(9DK^RO+WlmfVAlhp*9uf0;gnN3`lN&tpiO&I_{R|3(}H zkUd*+_iSXIUaHVrdpHWX!u*hz=0PqJ%{D!~pK_{U8_8tMvK1RPo)?5wBv&#WbBjnO&v z3I@Y;WB0DJS4fFtIE9sz&(Bg6Q#$QB9>Jx!3bEKUBRcb3DamsTR>8A~iP3$1zq;!; zsdWd0t0u@~_$h5v&t{UrRGdP{FDV)9^a9S`B&GbnW;ZnaZFaBQg?E3O-P>!XBv8M> zXYOBYB&`aQT{wZFZT~k>=Sjf$bMS~L4aWy|s9$n|bjAL`_>}gPPR#CLtn8bwb&3S{ zeKf6;{fjHV<%-J$43EyJ?4jPKz-Ydf=12vv)dG7667(w<%KVy3qB;#km^OlfD~RR|WCbNfEAYdS%ff_Clz@ZEu zSpa1u5;24~=b>UDsdj7(wP*S~`kUNdX{D6QFj9d%7E@lXfGRG*LJhHRX@MFdZP4iZ z+pNCcR_)-v!k;o!<)w$GDO_XyA^0WM7McG3;(!MW0{eC&-?)ve+Cn5)1b0FnEd~Y) z7lC4f2tFK_hUcp0XTMMfWG`hprD>W>58a8+_)pXu0dlQbPr+iM_D6_=*ZBZI9OfVR zZ-_-j{DN4X$e$3$1N8%9+(3WFKpepw_8Y{~)%ZHce2(h!%>-1AS4*T(6?Q-~?$7rA z^sD>1*7OZw2Z{Zw^mDhQufUa1-0RcvG@Z<0^|}yM2=sVZOPhEgr-9eTM5W-O+9(GY z262H@erzC>B#yyVG<2VmwOOY&mSi^2)lIzK{(khSTz$M7=Na&=Ta z^W|cfY@vaZXUf5ad7#B~J}=WOOM?))8@myX42?)FhxLbsffgJ_s29Y}3ta}W%|b09 zHa3(5u~S1aVib}ZhBV8$o1>VUP_4tW+S*&soD^*zPSI`Wl_g?J z@B}u_NMNyJRNY&p(1b2OK!r>PWza%569*?wXj8T`=;~A+|7j}EUK~So;s%b%BmqeM zk{u016)L?~BGSNyJu^^=Ql3Fc`0N=(44y$r_3Rl$M9==0O#c6692JBl=BSWzDwKp& zYDkKZL;YKhO8r~zuS}FeO-0HnRDZbyscOJ+RH$B2isbs`FMriOfBLI7`|)qpAO2ST z=l9>LfBlvH{P9=zeeSR9+cmCC__a7CTmN|4xA9yRb0H!KzuuK|)Q0YMl+ybkZhHX*!*YWH@HG>ZkxHzVf1LOTC15>TAC1GkDudSh1 z546R4Qa09}bdV^%oEC5-YOAY`T?!)8`0Ql*y$TO#ZKPBd@JeTgKpqsh8@X6Clocjx z=ZN5Ip~a(3!YnwdNu3&wQJbH#?tyN8T*#;hWLh^GhuBkk8fvxvF!;GX_SRx^{g9Lx z2bmlPq z04EwW#Ev3^OQ3NE$`qHB(9D9JN*Y>56+)+$hK4-hD5s&f&%is-n@PH4078`H&_j!zx{)YO`;ti z!UH^RGB;-i#RU zT|EjQizJu7B&e`HZ0K}KnNO-^n7Kz>dATnk&gWP_iSMBhCmN=ug?dt*B$PG?n;;>q zuP@Q(SeW0QUcXD4>qF_5KL={ma7WIXlqx^csbPb-5P^D5)yRW-d;^TE>lD)KxyfLZ z#FH6|GM_fpY#6Z4FOirZ|C-;yAh5$gOai``iKdm|X_mpF%$RhoyP1@6gTTa4flk}| z1H>TwsQ@*LueXKT`gXg!rk_s~5 zuV3q`J~ahTsiB)q`Spu!s(2;32KC3-CUrUi2)Rr`_Gd17MN5LNj23!UP=Hl~&gq|; zq|RX{rKZ@Z9p}L~7$*Z2e1mz384JskEMUPDlJ#N(Bvn6sejQ9zM2U^f9>}L`h%uOlaoUvO_c6DSuPL6% zFffc-DXr2N-{K|JsSqqQ*mQizV3X8kwbIV`DrqBHkp`BR8>mk3HaAy7X7OfL$>uxK z2AC5;-U3e&sahhsiYYz45uObZ&$g%nIISJ8`l42rO8O!%_Zm&h7#LyArNCjV%IZT- z@!SA$2@Z{=33rEVfUMaOAn95vA!g!O%JHB(qB zPw-am=BneYIoj$yA#n=J;CbMRs z5tdws;S?yUDHN|OvEcq55(Ao}s0V2Ra8WOZ`Hjd0nwADX~_IWO5C-8e}yzK=qwWH|ceT>g&yLH1~2;>Hrw; zVgA?6+>~Fo%LmeLO8Ba@^#S#=U>?mHD50I&YQ#W z>`lL!9$UwMA3VN6O@B64JWj-$YOmj}4uEF=Ice(OhXs`J?CDGZ7sb)0#Br&p?F>Gb zN+OCYsv2Tr#$@zV4P40%$cV6S=r#66;xeHj%zk#ZiBpf8g_3l379vfvvk>9TLaF}$ zkbzPx&ru5JLD8Nd0y=UmK&hi+Jx<-^==issE9rJ^K^;jT!|94pu0w)Q2?H70MJWey$RtT6jP>Y9QAJp=n93!;I{M0$c3H zL6!mqz8j^2RWP`!5($D2Q9M%om!e{L1*Uj(m`u?)jl&!)QCJyeVH^HZjouG1g6Y8^ zBJ5Vd3$-!iDB=rRi>7SczjlCXYLGqb)oiFmiP7)I`UH_SeR}E$`*zWZ#AhUL8m)9N zfu?cpfo-GQLC-CVHvaW)bBTLI9c6CWa5+#J95@k1>08%Gf74E47??x;Ot|$S7Gf+^R;FN*YEkR#quBdsR2Ukd#CI#5k_) z5-Z>p!S{VS48H=m(e22zvW^m0SPdF}$Y%PF@@Exmu7-BKi!*W=oetZXqcFJAgzid* z#c@ZiGaAGt!ZHWaXk+*4%Kl*H>`|K=5&!5a3fWaWG-d2q zob}3-G5W!=iIo9Vea6jo5Uidv{`#h}vWjQHpIiuS>Pnetm<(cZ(}m(&j}^te@LQ3$ z^){s9Dq`+<<%JRE@6a)K%&;uQgbbdfOS`+lLM^JCn#sIdT9Q&(!@F-Tgidkq)csWr zaF%EKgEtk>DIUZ>H0edf<=`H*YHM01JzDnYX@t?9cE@};W6o+`q!)1jWT_fpNg*^!LeJw0^H$R-Q#o}hMvgGuK>3+ZXv1N- zb5+>^_(@dZ3c5}vN|^>4VAKKLR~ob_K0)T&{xFoeKH7Y-ebc`+F*>rQ)MlR&Ok4=6 zyJ1l;;gBQ*3sNbH3vz~%dP=WbktRENHbMU3ka=>|nmakNQ|(QN=ISo(RFr3ap<3ky z6Vy|1ENn;yqo+yH%oksgHREDNgs@SOtb`K>5DOc8^vp@UukrA-m89z1p451bcEwj< z&>e?kK|Nhl6R!;&@mg9?hNJyAv2=8RzemUw%U>P>@s|fhxBJV3qTBuDVK9GrP=q@T zj<2=`SFg+{699t1&pH16MnA;J&zXREB9XJfM;S(j5BQtyQ&e@kmDSD8VtQS5&VBfB z`Nut~&5irEKDr;7*l&*EZU$Y+e`pJ^ZA5?wz+l-Q3ICPF5CLhfUGM6}qYT2)Ic5bj zLrMhL%^O~e^0z479R$IL^W>J}A{x2>{=_fGdB=B-eeCNAbT$eBR5N!Eg1u+7P#-9= zWUy|mTlE6l)7#!5=h`FshN|)unXpB*?o}Y@iSGXw4aj266CQI!Hi+De)Wigb5F&2dl!L?oDc6WZtDPodx_ie07bgg3 z(CD4oNV)z>_|4uE=8*sEolDCv)}-WB?mVQsXSLI;pQ2>F6V${JH!Su9G1mTmh4pFo z*YrVwjNtMdiLh_KLM3Uz<(=LJ@aWu4_k2TjVg3FuU<0T-zF|Uvrs+L?%mUT!2OXPE z7yfkDJlxb7g7CFTuJMev(s0Yyy&2Nl>g$kfWMH@2*ZnB<26P!gbX#0IoobaiohoY& z7V@_f_uG-a0^0$|udGY)$DDJpWu#p=Is3-#lPDCsm~GZ&<0cR;cU7{@IQ;qV-7bf~ zDDV$$#MeY4!h3$i(8Vyqcitp=MjYY(=@4^lgug{J&0n&CG~w%d9Q#ey>AsWF&&vVk zXsylR`k|+*wF_QEWsxZnpj<>CRMHGZF!_%2p#(x>d{K&wk@65OnTjaS_H*eiunT-@ zvqq+&cV%0^H!k~|a5nFujhk4qycYTuAaXyZ(>$}6T1}?>O9O?DS90)c+^WAdcfmHS zajz!2-U)SI)*SNoZjkS?R;!#sJNIR+7SNwVeoQeks<3XB$q_-01}72bh+~MIz9M~R z6Ryh#>vMc_o8n8*%4Djnm|c-7W6Zd{j1$DiCM=IU45$?`?gF7kZ3TzJ|ES;`7E8X! zZ?`v8znh>KAm2sC$QXjf0^!F!T8*iVQ=gDcSPI*>@ z7kUE}8AZhm=5pG-UOK;L!M~gIxs|DUwHDPe>acAHpk#U8)_V4hH+F{FPm}dm@3y^! zK-KZOU@2~~oHaNH@|*Viy&C=T?KsUd%?nDlpVnk#*$ixTnd?|6t0$9mLxbt=CcYTn zB&yfkJ=B&Xh$!3JUcQ7v8QcQq*|z`}zF4%_q5Iu}cXr@T$CvRab=buC#K=B!^}{zV zJ70OOTwwXjCXRKuqckgTI7bjXmS~(SXx_a0yd!-=visY_>+!*~pWZTs>F!Yn)7Ht| z*!~&p5)A_ScD^$vV+ZN!O1kcZ1o9lY$7)5do4>p9$DllacYj~UXX(EF?hA-phx|AH z(>&-?m}%6N2KCVGeciM&5BuJ}p$(lO)~+G7=MHypqr=LYL2yTTfPOO zbOctnJg;;(mnv%P3_GdxMFGmbp@Pr{j)A@#z?O4|SA}(&G=s&7zOD6$@S`c#SZs4x zF-*K|B;OY)zuhs~=;v!sD(M@#xKo#~QlG4Y#V+8Dg|q_QI{p6gC8nKWGi`=zkrBFf zH}dIIp_PHntt%>gKj_}Csten9_w;>ZPv3p2)AyUE!uGutZ`0c5yAK2@fm02Y)`xmK zTc!+J=eE{s9Wm6}G&>&7nTJO2z|~8G?CLL>NA&<3LOi@Rr0UE}kS2pR2?&IEY~#R_ zewf1gEER_s=)Dj}M9USgNS)1mJ#|C#tYo6*>_l{W-lIa4m=Vcp|#^8yC zzAX%*LhE4afOAOZ%s=M@;>4S7+d2ZQw7%_|Y{kOu3UdcOp#-#Hw=#<#QPG^dL(V?b z@#otVZjwa)S=O}9>Gq{v*hO%^-(Qs=CBYtxNLb~YuzbF9ggy>iw;E#v+|8)pd=# zGtGWWBO{KENrMgzJKQ&mulNLCXg!#z_$=J)j<(ar;)oc3{i`yI$L3@xcTkz{pJR$18jY*eWO_=oS8mEw00Nm!63EvP!_PUM#Lm@2A zA_ADOiSuKZ76~Q3cncl%E%R3~w)&iHzL|$LsaYHNG7(_Q<`_I+Ia7|Z3$$Ib7r_7BMtEr@lxob<4^s(HdDism1rsN;ub z8SBR^Bgn2qRg~3NsFu79%F5G=S_=l`P=nvBzejJkLp}PJ^LQ(?jVy1Yy?xOg&Otst zwm7vkOeN2wILqY7V2mn0RCWzHZATqs;&RX*Vv}Hce-^ec-kwHvdpWRT-tn_=@YwT} zPRo$DCoHPf04z86h;S`fwk>d0(WL0nmhA-1F#dAmh2_QdCKqPn)BIlFnbB&)%{$0Q z>Z`bW+21}Vho(*v+~8gIsq1~BU&S41-Q$W7TJ3t2(Ez6|SkrA6SQv1_WRO#FKB!pE z(4EK|8;qBfii+T=;uC`eStGoU6V>gUYPv)uC=SPaSEu=Dl^bJA!vWe>k*noG7$6SA z{Zlyf#?Z0vXfYLb!h7(L3vYP)f09c})~`p~zL%x3Dz!!`I)DcoR+60VtaU?6mG`W> zWJss*#r=v?c?X{vhF^3veRcd$^{aK;54jT3X4!^`H{tyHD);hpOR$^cRS7M*{jhvB zYF9@{<;bzTf>2rOmxb~KeP&A^xJd8D)ojrD@58?ex(X4TQoDI=wbr%XXO$ z6(609o)^3TR4uR$7^7ENA4jJ%T3~=8A-%)nFrvr5Mcm>#e4(QKJL~zCrNtTRG7cW* zwDI<=U+@P}aP3!ipTHQY^OCN>d}4-C57KSvQT8{yJ2)M}7KK?9n$D+pd=_cb`RiAD ze+q1M<~t8Jf&36>eGi_?$i>2HZyfK|b0*M+d%M%Rtb-OGVaqbIqZ~^O#;ici9Z22a zor)Y`Xzh6TK^)I$S_YEj4QP5L(?6a@r*$v{X_SeM7MftSjrnD7E2V}X{V>NGd4Qhh zMHQSMOGk#()`98{-)x0hBp_us2yQcGlxWA3^#?ud!>}eTG#9d3(Tk|eHu}!Vx7)Fe>v84IEm=Mxwwzl*XXic0(QxQ=QX~aSklX2}@L<<`**Cs}*FP zb6(3GGponGr=^-QqmVQ=uOHpj2r0a`oTe{aN@}5c!m8;s>M8H7^DXB7K`gaR)rcrm z$$u32MYdN`b4=P$){@H;CQ|i%j4CC{bB65m`^##on2XZ!-iuWnY+yMPhYaw@I^m}1 zGX6bFuT&xV^m7`^QUd%Jq}(z;uEWDD2p4nEnG$H7y+lz$s8E9(WJ!hg9>+8<=Q6rY z6mc6^LaC&*mo%b8XB6v-Zl&1lbZ+}`P=m3lb}PSBdddQcWA8}to283}f7 zsmDJJCKh^*AvZA%ER)$Rw1{3-t>u`^=gg2IJJtc+3%Y5mujp2>S~C#D_uDM+-HY zb#E8xbZRf%TH&C5{8nJ|T6)AYpKNOn&D$?^ z6)2|pxaKDHZHA)Bj^n2nv`?(;{VCURPB_cZOrleh+M90nkJFaXl;a=eY2lk#$jPMm zo(*d@J-|iM&9Uj*IAlBB`JEO=pUmppF5%LT72WHD%jEi=)cL2{vx3BgL z;sRk7-#=?*QH8*DjjbhCex#IGbUYjJni($t$232bsb<12Gq9CiymPznzFr#3;DmuU zOPh`zvKOSB*%NPQUwU}&zHNHN85wq@T;U*>w^w5b78nLWJMckZ*(slqQM3m=KM#L; z)fRwUzfO(R35AIe+k9y#LBgt9TK)9m)o@2c!-#crEM*~`$IAsop z;u1tgM{hf(J$OfR**fu{aERxhXT`JkU+gW!Ba5TNtLHkb!hK6?YWtpV#Fb{8s2hn8 zM%@>e-sttI39U8(c=sn@JM;|9Vc&3$JbCAOr@9Re{L4KJp`#2$zPtfL+q!U+>WSQC zt0v#jtG?`qMi7mV!QbpJ-OXm5S@ECZzrJLO!W-q=Nmrdq0E@9n3)<@kjziItsIFr! zz;2e&J-rR-FeeUnZ2?;dbi#JN=bZyDPQc;=i6?tHvj=eK7b{q^b{A8S}f5TRQAP21XEmubJpB zkFBGsOlKw;eqcryh$UrqYh-A<%A_||Q&iMU;(@B#Dw@f&R0A?%{9S`-o#?WmV(FGu ztNKK~rBP2o-9odCN5&m3T2RH{1E&sigRkJH02iq`&OjPK`w$wX8*!m2(mM>GU+d{5 z+Ex)OZwl24Xm$s(ZuYE2O9}LnCGhpjoPsJn6g?g%!YbM`;G^RUl^!m>csxCu-?WYA zyP0aXC_~>PS#V}FZ^%}QVDK4?Z8jMhfsLtB?pu^q==R6x`$S8{%QC%w>zNE1(z88% zZA9DwpsH6LI_A~qxkGhyo$hQw18PV`>(XEK!w6~$o&hUHU zRaL15)BW#1j~|qJUh~tOy9?i&b}~0LG}xj-{d`=}(9-a1cFOO7IcMzc!nK0>^>2eN zFeOMb&-MaNscqC#Wy?YNF60El)~e*Yzyl#;iVDHH+pn3*=B+skVu^P93AzD%M+!4C zY2~#z1M`AbU*;XC-B_kTa(KlcZW=z#RistkjU_+zu{ZM)fb=|xy_A8eWE^fsWD55` zwO~#6B?*OF`p11v9yU#|*(+6s%_=1zEc1a(Hi)uyygOV^@57S z+eCu>kiRhfSE0HV3Pgnpu3|@8@qbfNfS647VL&WiL!FE@NE!MxwAW`LUYz1F>>*M2 zZ5W8Ys%!c=%k`n5QCW23VQ5p4tyHiO)X-xHz9|u8w%&1em@SBr_-mlTpiP^n+y@cr zxOo0$JqNYxRuAEExxc7mi4rlnWIUDkyZxQ=A#JPWsy8*Lw#Wfk0h?vCO0oz-sX&1i zR{VOVYV(JFl<)um%ZR40*W+YW^i9 zm_6XToYxAB^+!tHM!vVhl9WfU5XeVhW1vCqfVjQ)T1clA8QyK#B zyW~*7GgMM4pL#8>0~rU)<_OKzYp86!xNWE+58Iw|+4YkiOp6i)m7%=X6tu%%V(v>- zkHci>evHXoW~ffF&)iuJP9i4z?y(2wZr-@m^D+C$JZZp_=*_y3eBbIuK5|c_@edo; zX!h{k#A`6l7$2sE+Y_)x`2MUIqspzpE?Wlw(=+yqO1~s-m8yNj zu>i~~d()?N{UIb*2-%`vT>Q#MBnnV!Lb%cZKp|%>y9LUt30unH&! zkm6CQ)?KB2S-;O@ZX8!+ue~s$k>( z)7Pr)TDQ1bt^vIk3SQZcdvd1`ZPn~IPfqdHsiY~1X8M6n9MEkfm9tLztkBS@RG5|Z zLf&CI_RHFBBaG$!VLX-aUe4d&KCq7nD8sic)5wN)8qGWXIq^$F3dnx26aVwLLt6K- zPv-1k6vRf40Jj>r&a17e4}rV55np3Y;+Elxszls}?(`3_VMR6T!u!Ebi-XANy%{60 z*N~fj?C5L zn*Fh#x73gnSAECzZc(~WmOXG1eV4n=92?`bK!X)$^7*U+#H@_is2EKn(OzXS%PMO>KdZ08nczLy7<1pHDP5IV_i8J4XD#`Yx>m$+nSp@ln zhqr`A{`Xm~gdAMLy_2UO{^J<#xEzg`n5=yp@7YVffOzn(`*lYiZn?Xv3SqsTt`ATf zA;YI`K12K3(_ub#9b{@W=ktHWFkm8xMzTX$6^6DmjiqYVGr%fTH@2Y=85%$i%$#*VC}Z zz!X-;bzc6X3YBwR$VtsYKXzzK{A}*Y6a4Rw9zr4SI?j0jcimP_cO-81OJTb}d%O=3 zd?sP@_g4DQc8ha^7BL1(H}|lrkSeXt09O#7$_g~+?CoeG^8g@fVE(`c!MpmgZt_;R zBHf?^*3x@?^qgVm=7ta~{-XMOg~j%cT~))?Ev?6QvmXX0GE z{+qjZaHr#E?)IpXvy`n*?!z6d=fOlnj`bJqR&v*^^mhlJZ`g#Kv_t+j4Mm*1$S356 z`!&y;w2Nxn%u4{+o3io{zv_J29VLsLovr$x=bcH&5?7FUWzssbF<6A8=PP~Jf8U9x22I4y*oD32ox$)|;Pe53b0n~9 zaW-M;8mEM)yC%8*1NMl}-5E!;jlK7_tubpz`$!gO;9_#jtOj~8K8-Kg#-BpxPVs@{P z7Tqynn(;b+ubt25RlkKeQrij5OLse_z=3{2{r1cMl|V1OjGbfmqyJm5IxBc4``w|u z>rv+9*GJdADM8f%hC51DhhWmTrO*@CK1(gL`@JwhCn>S<3c&Gj{kQ$6(rebta9xS} zuG|4#La>f%kr*XSeRR*`Zl1GlOQ81-mnoE2FTwXt^j5&|`HscuA4TG`;ix2m-E^w( zuH)z$qXl`VmdbuCPXs7?NHE#hyt~2I`FTz@TFtfjkM<7(bFTwX898tBcp}lT?bZ%7 za_PF0czls27k|aaf>kqqEBLx$LmjYfO*@1Zd1`mGu0PZ8 zMMRy(flus(!8Rt!G$aWtKpxemFwkd1+EyA_Qs0ZZP5sG1VVmtPaZ zZ)*Fl5#}|x=@k`SecQ`6YFO@cBJ0Vk!1l_&IgxmA zCVC)2@Skhw?r3|>9CTKzpXNN1ZJKyBpL;lslm16|$ri!-E!AvYa`)CQJl3{92(i|^ z^xaGwhq)d8bj6H1-k?Q^eg8#WJ6PxxKkN7LlR{aX{qgJRg!CtNxumpfAMiCR$Y>R~ zIiEKG06Ay(R=iTs=Mpv)mvIteOmlqNZzqQ5GI1KAw zt*AY{_Q<$vtE~nXNgQM@-=?u@+AsUW>nYx7+Q@lv?ac`03&k~$hEYGI^V)7{YDpoH zvLXv5t+n?{S`&5p<}sH?+dTwk^r(6wd2bi_1<|!Ps>E6_u`J8R>%_nrPo3c&ib{&L z{aWLYf9J7*oy^ru@0k0IQAb3A>;BN(1PQZPbYL(?|DPK_OjBkv=MpH{cQ27HqNLJO z-GbHFLZhNkS4w3}lSL9|^Y^0uhjgzILJf!NmO z#Opx*&W%_9v$eWk#ier6=;;6?vlbwl{zhqTz<4;ZeJ6Pq?*A zU-JuOGWVXeU1#D`nAi>!jTENW&Wh9O>BIAr#C}a#QS&9xBj5rsVfCav2=@6g?aS*d zm{#_D5h%NL5_a7@+@u7@HDqpI%*7;Q(mLe$iBZEJX5@i`TKF8JEV9^Y)a)~dJM$5z z^3JX5UEcj;e%>PSsJ1rRg*(06=cCQwa1jylGat-j_6lEV|nH?~G zW_Y!u<(<}kr_VuCuVa8VXLxj5T#3S!Mv5SDW1}*hKro`L&rPlPwa3zRi{Hny7vY{O z{&>ou9o?n{1L838GD^fX4)!CadaHnS3n#8ZZs5ZNe^XBxx zi6$E@%Kic$GOQ6X@Xn}?S(TB~H&A<85;l;m&OG=;k zU@zG!zs+{vPg)*C@32r;5a{@)s|jfzm#~as;*>q>7vhj_gt?Uu5?GoweIWK#hm5~R z=G^=o`*PuiRL*z12L;p9p+hh9+{E!!_V&dV*e_*p_u zFnj~vdCo(ZkqI^^psQ%Z)`nPyIb{loNek??Vw~ozHN8`G&Ufh+i|<<~l(YmDNF&iq zclzM|OC%eI0H|gs`2V zMZ5}VuGstmflfYr?G?6Jv&wM%<<+x3@PA`|BxB|+IVnX~*Z#oFccHb8wq=?6-X7b> zh32R^#rsh)W^2RhlY27nkMA{)M13YD#EyB!nyX|+`0lzLOJv%e0q8FoJZd8Bkkcfd}b@Mc69PE2-3E$$($;#AN@g3Sy(IhN(xm6U2YQ5%$zzjG*q<}==B$zSP z!}ecHhH~@^(#^FrvJ6IicOWVrp*TC13lB!-`CYz4wy{@xLvVaXCUe(9V2Uapp)+F+dZd&OpBAJ+F zWfEoP`#CR(u8w-RXHN^fQAAs~6Zs)MV>?1iu=mwe)ni;hGyxWX}ffxoq~)A1Ux=oRl+3T`Ab6{TeuR5NbjR$i|>m; z@I+m>(``S`qqf=yZZ2py27xZxpyyRcA)Imdki7lm`Td$>mX6HMW z0BLO$Sz9c-r4cN!RVO9zQ*}h}u5JUX7e9_Q|9)2Byl&kui99m?->3!#XreGc*QON` z7nMq=7$UJ8cZfpYuUIp9!dUaf^-~d@8=8vjE@Y-Gy5psjLHS6jb{Q)FeljWNyoMlp zpy@hi(@8mf$=v&=jLv9c4EI&i(x;uD9h=e~y*GGXT~lP(I$o;uJ^8_~8)-r0RE?iG zy;IK>NjsR*O9sa9Y^!eMtli9FmkeBq5OoW$7M_2a`DnNjPrKO zjA(UFfGv0X{68-4mxR#~#BI4g#a6?X4ePpS{;swQ@?XFVgZ&3}JA!04bVif3hn*vA z?P+who>NdSQL-&$u<=4{0GxK{s97Hc`z>|`wx}?(RcCcP7}^O-Qz{I_-!xYxE!muB zI}Y0z+oE=F3~i2{7}wN{Ss!R6eeoIAu4OK^p-rhe?8(spUKJn9WpfzkQ~V#Z=M(mF zb|DiucumXXa_#%XTCZtCpQ~W$%S9^^XzZ074y>CEnM?V24(x+*Wai-R3g6X=Z+}*5 z%_^e27;H|z-1#S=$I1{He&h->zlibcO{Sx)&MEBWlBmcE_DKtkJ1g-c$uwAnRL#z2 zv)M1~OQgU#y!#QMsAQ6nxf~WAOEVZBMruBVU58=K+jpXT(cX(U*C~&23N`O$o85K^ zH|~hBja({5weDi0g&0-~ViZ9Tqgd&U`?k4_*iL$D#T?yid|5WLFh3aubF=vR&D)y6 zK0skL9DJA~S+S~qXXLVj>V@n3Yn>UVvsHo)4`7u}TLNwndBZ6mvw>}CZW8MQ*P{}) zI*nNN<*@YwKC;P16>i}Y;(}{YmCZ0UgC+4hEqwm$p^#x({L3aZS`2e0B~FRW7>%@! z`FH-G`h2`ZVl%BrM6pM*ZRVkAy)Fj)u6R+EOx#P2FpXO1&k=_sW~i9d!NTSK(G}A& z>m|MIwH5FXAse>6b|$9xdj6b4iA7MzsB~egmxe>X7_$WTpxS9U3IMcMJPkCkh#r{4 zKfGvW_>Zw^w^4V%z#C`nlWamPo_y5w#^MjwZH)V?zHL1!E5F2syJ`=^?PIRRwk>Wa z@Gip(Wyt>i#O~V;;#Oz-04R&C1UsxpL*_jLWWUW`KD26m`sb1rP#te5r zi$d3Wj=bx5hrU=R4X)W>pN@vVf6b2 zi-ZRjWF@~R1gx+xREd^Pf1k%_^w9~?>?auq^56c}w}%@au3deJ-91Y6;ymeNK#`u# zaQBbJEk^#!`1o)_6s_AWy{<|T{gLXV=KdaHL|Levh8T?NCW+>(q4eQaxHKtbgmYNB z-6WYoXz1JQ9=s~-QLz*?G#ML+nCD#avmWacU*A5@$hHdbzMNCbf;RRsJj|qTI9BX7 z-~oSk!2Bv<&YTk>Dl+@(Ov0`3jzhBVeT#V)%_IQ{H-{4*_Ggd(zHNMFoys|&un8bYbP__J)@qG#S{$KnnQ-xuZo+^M zBW#C$TF5}UFeP`*puO%q&<8wW_NT~sVbOLgBgId%=|6c_Tz&95 zX=D7Od;Mzixy{|^r5c%^H0cQ2p5$)LdX0{qF0hI!q~RboWxa0FrRbCFo5}2srE~c^ z_7IUena&0r;_LnfpJQS$P~(qA@IdRRvXd=ywSz}~o+R(eelpyj!$%*ub>p4VD_<~9 zok8iY)_LLF`{2~gtVT`~e+D1mG0wK16wCfZkSDYM0VZ|-F;)K13o|%Z@{?ft8Mv?e z5tlfTz5PaH-&pp&{~qz4h#zYFwnA?qd`*Dgn_f7L*qQansmSuUgGbi5=6Wa|rFPP& zFZ&>{6VL?RjpZ#rWj}ZIb$EEf?zv0zzA^C!UL-^i+0F@A9j3~gZ{SuhwMjS zpt1bir|NN>X3(>5cS(p$+1W%5;`rcQ=I6&VgyPBZWkCE1ME%AiPxbW-VHCv5|9((C z>c+9$WsIH5jPs3>^~W<3cf8m-6!TbilNPf`!*)6GATR$|*4@o)`-FWPvL_gGwufrK zL>=D;OFicE<<9V}-}>_YDc4szOa*>_EF$d6x#_k4$cdScK%*(W3tyJUKRStNSaA_! zvU~}Xa`n~_Ja*!P*8GRPFS^KHKk7;)^qW_2+2+|@(>!_k??L-qZCd^R(K zVFtqt1~UvscE&Q+F}AVqONb#vA)gk^?|aU9zg|yvZDa=16i~R#rAzJ;_+t9XY}pn1nb(thF!qBzn+26G zx<7Cma*EII!&sEfxY~Z&(*xpj#9%<|Qixmx>dPieZ`BV~@{&XVXv*>aoaqeR=(SIL zMuPWm^2@FjcPxjWtkL61I=)cKO6#5s{ri?~x|$ovHYnH?X=_Xhd;FuYaN4iw&qK-k zKSeo@pOG%)f?JYgFB;vI=1VfoWDtEVe;JIYJ~_UpCr0z$TVihq997GDVr*^Y0*gTu z0Qo&bgWTGhbC1=dWJod?o2svdN(h%i9=uebw3oH%7U-#@cwC;;x*H@WM_p5iN zk(u|U0W=w5BUhpoTsLQZfIf>_?f5tEm1^#0AehKS>v`7a3``f~=`tR&sW@v2l_-ot zQ1O#nNaPWSBsBmy?=1Jya2^)WV9u4AGRUhR<|xil@sEBPfEQv1U3O$eZ&L<16Mwk# zp0r#i#z=-KV|ZLG)fRHGD)|%^UTB8TQFKkV8^82_u)Xw2%9n$UtU!mZ=}WyzP@}YMVnhx zGS#^v6cw&8%}P%aRJPmv2#9nPH~|NFI+>`uRHnRp2QxL>Sc$rox8e$QfyAAc40prb z*`Mg!5R}t}4Zce+=s&J>Ei*Gk81$7%avC+PYy6u1c0SDo{!Z2*BOoNzOg!lAkvq@N zzjN;Yyxo~;HUSngw|Fr-CXawa;#1~~?@`kR&@=O!H|ca2piPZQRBFgAiFlF@fy5f) z!c9N@MQ`U_O}dP5(4A#Q<2dVKwX!$QE>h&;`6jqp&+60T#n|Qp&PRqb+2)t2L9<<8 zA!B>fnMm9ps`=hnQCSI**bb-#40yDK=GXz=5aV6S=g5ktJoTms#PHQCqA0V~({6w$ za9a5XJGXfCBIhvv7qI_oB{`RIo433eDWaEyBvoUic|}_XtPf;F@%Z!b6bot>w^%|%b^$p?lh~sI zNrf@vvz0j#Eh*@}DTOs54K1+8KS!>+oZ@?*tOpcS)R>;as$j)XIVLmHIZ~(&Zmhtx z&X|@l2J%e2<3y=87R^09uCSX02GL%EkGxAU7}8V@MF|(CW=_{Lp&c>2A9qZrh<&jp zicpzf-^CmaA}NlaE`w^gXbTdgcVtvBGS+d$qJ#OEMakA+o4b;r&;y#hO1~7ykY<$4 zWyQSjKA1+=bqMsLGCp}#EhpsyUCP>is=geJWQkiF&RsQ-jRI?r(0&<8PB1!U@S^ys zN6$d5cnlt2zhjHHHNodQDd(9KItjE+63a28JONg?hZx)q5qCFCu7C6x9_xl(xfl6V zrx)ng<)fUg6KPuyRooQ;#?qTbptW;Y;>T?}hB=t6j4O|1j^l?IHfIrQ;B{=II?o4W z8om>4!q0Q-6UP$A7h_V4^JjO|V>+I|dHBZMaOu}UBufb%!kK14PHr--GUG!a5kkch zWTytdkS=oN+T5i4k}a2^V2M~%G!cU(j`(|y6LT9FjPIbC*LbR3Gsy095XEUCL00KW zNtXXIdSq6g zAZez9+TdK26!+#roIP5sYY(hQGC%&b$hE-S++4doe05SazV}IoOHsqmo|q>$5xxIe z8jFU0+UIK_agJcguA`<0TfMjPTjWT>c*lgrq88L>Wy zdXo#-0J~XXW7$Elij`X(@d?q?#?(!yk*;CWyKQt&&MSYY1YKsPZFi`;Lq``eU%k;4 z*H)1PZYob)e*l+Hg5JbIBhHD)+;u-$`(Bi4=A_hn3-Ef~LDQ4ARtYJw_R;)E21h=K z!01Q2H{zx>#S;SFy;o0h#nLgi1L~sD)Y};RI+iz~mt=eh^24!sUCV#sDo?rP zm}@w_aWgKSqz8@Qu-GFK11JY@WA<0)3OttmBoJoQB+@_3Ryr0L7J1;9p82AN@&%a| z&TnXYeMYVF~o#4&P z`N#gUznVP-Jh-cqZ|}1Bf`?D`TW2(6@Wm0iAoXV3@eYl=C67b+);K%fw6nf#_| zHcM>S$m?2)wI?3r6IoMxOhk=7ApqQQKU~H;lU->lv5L7var@mD%~FB$;Ox3ml6V`i zGmkEY=r!R+mY$6`oT&?~JwH#lXbw1=Vm^N-ZSZ@l|Ld$va#nxVsfYigufgYWyp`eN zH!=ozb1lwCM0dU?1f}Yp-OPSE__h-A1H(OoeeNt+O|4O>C(sLh{A?gW*fW_gnfB7% z0Ap}0;I{jpI(DGDx#*D9*$t4UFfr}pN3)1eXSA6vTmrz2eLvjt3+Z2n#a+a)uQT7x z^CpMbg7=+3bGb(yu;NxX@I5u`&BuQ$UjscOb%ydy4+x9z5}r-h#w^ve%Mk{_KQf=x zx?XdA^&+7To%q~$3vARN`5NS6>bw)*74AYX>$194hnFuE4X%Z~W`Z!k#}|*}+8+`3 z1U@^o;)I4X@-m9DFc#-#cbRS3wfGT#5 z0up%&t-)V$wt8`Bk&zJ@(|+R^Y>Ywf7>IS(0xJammR*iz|0{Adu5eIV%QyJpw~PiD zxM$scEntZ?s@`l(63)Jo)PK9<635Ft1@nGRx+B2BgVq>4(FsxBAy(TStvz_`p22Ez zJ6vZV!O}yoG_?v)u|Us_rst>#gg|A0o3`rNqEebNgZoY3rrXhc0UyO~T*{BCs&P>( z8hCa3x&Je-U)XTRmrBMIw>utKB8minIg?th1+iT%8+l7K6%c>atvUyIzwfIGmFMtJ0RH{eTia zAbt)#<8qIbw+fJ>M&-$rD_#SV?}Az6dwMGbC2wO2YXbUCX#?FBM>>D6ACj$?9y>mc z#-*pI0aHI?RY*V7yi+ncR<~FzHEz^Bnlgo$m2pfUMgTVUzUf%H634U>K~VD_$n3SH| z(NQ}h59mKE2$olz)jm}NaalTe2o0Mc$Q&FzSUFI>Q=R)7UGwSye=pc0qk4HMot0y| z!pJ+{TlAd#`l`{)`R%3^{wDGSHBBt^^akEl={!Hfk5yIyAC#a37c`px-Ebkk?27BVt}e;% z&gq-_QVUUvMfG(9s%*s0_H)aL5c!Mk%%zh1xnAKAL>`@4GYftB^Z(9>=$d&7`1qiH z1I#y}EnJo#f6qWOLLp1@acNl8yg&}V;fISd4S87vpp{bZdskhfy^O8>#iN=%)I~ee zQI#P;QAh){WoLv3j*vZ+9@g8-5hs-t{AF+Q+~*pAFdLI?WN!0r*<6mY0!u#|DI#3v z@|>eSkL5G=(t#-3 zm$>@XinXmV{X)h7Z~9YVYiFv+%R0is*r&QrtYT({s=91%+EtR}eXex1g!h_XBRDbi zscUGNt@;XK#?q-?^b^gUJOlJ6^y5QD=0h|G17H&PKuochOridlOb;3p@$R4qYM-X? zCkv+rpI`-ZSZBy(H}AaU!lD9Tx<^%>G;~)v+FJUgtusQ6&U$vm`c!@y_!DW27GP+f zBA0)dQGP=FBo1XXc0U&Usn!X)A%exZ3c+78`E^3Jm2J8AZ@F<S`~3URQ7r|s}dt1(8LGLCG0efV;v%6iEQP*$!~ znOr1Gwx~e0KGsD~Wx-i%b3)+u5A*Hx0}PQZ8mV&CALQ! z8x!PKd?yDxT;dn%5V`djYzD<#A}IAtjLMCyT#g*`QGb<04O zuICpNsK`SV6rIPdL8f}u6c4Be)VA&mX$Mrn+C2S7;Ltbr&(md*>3Z7zRN=?zKO%1K zTYqU@O%FP_#r(wQ(;fZ3h55dfxzfyBxWb&ia>V0S?crRCIVg>}a&7VZ?|U^;Cg}@^ zsurF%6P-W3y!o$ug}1c)sa%IQGv8|J=;&NwF4r;_@yyxGia^c;XKXj{NSXEf-T$?5 zmuA<)?$hJ>>`Z>Hg3tKxI~FQWX0g!Vb=Zpa1p7B)Rnznuxm)dgfQ?aB=O=uHbwA|}4qp?V(=_0~xU}G81M<8|94FHFyc84n zUut&KbLES`GXasYiY19j0S4#YvdM{y=?G%eqxTn_+c}qJeF$Yhr)j!?&scix>e4xa z&^ff0l%^I#U84HbsJyY_s-PNB zvk=nrWyh6lq9qR&(w=(XoVX0qQPl0;={6_>QY`hJ1MG?V5A*>{2J8X-6}<-?O@mk& zD`jtv(2nLdy+r*WIPeu@qi%4jtJX4nhz5c5@f-KpdB!SFU$3oQS;S2}ha3Xoa?Lcu zGzUs}EBGhPEt6F`I6_-y#(6ZkwP7h+bB{QJ&`!JMt9HLCTZk&a(qTYhj60#|pBd5o zfBrdv=6U8$czK~tzNqCh(KlzzHc5}hiXK+ibDQ$2+<6B4fXcnfvFtOefZIdH%seWX zGSUXBX7R=bW&^Mb&s1#>DRqHnd1mcGpT{!_Crqd2G>WP?rvFTd);MQO#!7}{sjgb( z`QRF+`ulhlg9(w~^6@Imsqg&J9J`+*-^cSN3>MGZyz*#1BlA=nWBu)X@IdVCss5&8 z8C`-;?nu?Ey&cSzTGTk0$XsfkXvXcfJkL-(^>7U3>Z$+nqLrMSt>?YGkfqbBDd$l0 z@qDNqXpevnGV@Q>$$rxJ0L*tOjt&eRIqnY=RHvjr!l@h09UPMgfACUij~@4Rg% z=<-L9z*!lrr6d^N4{MFEts~!BC}A`^L}w!rU(@WmHPBd%`mL+U6m$Ct@G?>~Ur#{) z)BK_|&b>B}E?=(|-=+oAARrgDwDl{FsH#o>QZqojrc9X3lLsdo%+-MB z-|ffq*j!VWmRYtcK|ZZJhoZuXs* zGG%s!waY*D*7P|>O4o&2KgRV zBY-(~tyQ?BmSp1-2tvcPy5(ymib`QIFQsam)d}yWIu#m4PAdL;8Sr2iS4$Vrap*Vy zWImR@fwGXot$noBg)X;Ic)J(VC!`j~GHl0CdUO_-^aY=emNN~vsI(8RX&?~6()l4r zrTXalBe=fx|56<_UH>7$_Y?ks-Y1LAFALu z8+5;vDvy4It?M~>&wDPQV(}GSyWK%Io9rg7OFo0hQGa24nnYfHk>Mj1gU~DcA#%xE ze@g2T_2dPmw=1Xqg{A%$o5bnGD*ky;##R1fmn{8N9Vk(iCKQy7g+_)-OPDU8Kdg-X z@|95UXYF3Gd)+Ph2Us7uGguwVEkR8?@pq2%4?=|G-OzMAW;1e zaShTT7qjEtT$U#m-2Ln@5LD+FB$P*IQ%)P?CZ;+pAw?CA+OVo{D@JoZswV)GtC{iCshnY&AD_ zJT?hRC?t7X{fCvN@Rb);a}NO2K2w5D-Az}}J`GSLNXdiFt^lO=`1#p$q+f8lH?KRh z`aA-bp)nXCK>%A6_0^Vl{lzieR8ofVRipTSd>aeP zn<>GgD?mQ}kDI-m&iQ-6r32z`Kbr!jc;TB)9A<6(*Wv@j=Z|Ks!1E1*v#ZYsNng<~ z+aBV%cO-}AQ#`rbM$O+yPOv_YMeae&3oNf)vu0KpJJ)LnRK(<3-E8tv+y;awXn2n> zHpM2)D{y#(toeT`d_S&8YvpX}NQ^ZUFPhOH;yv~B@gZ0={6W9Se`e~Arj@0q|18|N zCH$*zv!?t99^mP%#qk)v{NIWJoUV1ytL{>I2Vld_X3Md{_{JxJ6$wuPi+~XPlQA2j z9h(!VdJ>W%&@s&p_6z%j75%wlqF}`Rpkq&|nnh-Jlrv`7auPeiCiB;0O+-B4%8n?J zVSr^hU>!x~kq#N|RwNGlPRIU2)_jWKJj5pn3w`1B9P)VhiQVbGR4eI*d&hUV05vu6 z$5^Q!4%yYMQEE=Gxv_A=zG96LW$`Ozq(XUnz(OB&ujx3IN{NsE*rb;&p_&Ywo* zY(&06Q+~LkO;x9FU4?@4Hj+hp*MQhMJjP{tGR5-)?*AA0~}^Rs|i$fVpr6 zuCCY~<-ysTkWtJiQWIHM(6cVmz4z!M=i*bL1@q0a5K%Zn6fU781edqGdx-n!c+0pB zCZEwzC~L6|lu8W5-3BLa0;$=(8EL|eN7^8w5pN?v)A4Uq(YSfo=Q_`A)iy9}@O-^l8MRXf@SClgrw5Abvky2YUNq_$j(A3vfXRA=2+Ess)T5|R; zG~O(N`dDnuBngtwfnd0JdBJuQ?Vaca*I>HDvGWRYu>lpQHy>2Yu{}>PFIUWg1N=6# zqzI}39w!e(wqLw}Q;5n3zE6Ttll30_|Ml_M_yK(y*(;Wek?Q4|% zbX;+v$aclQJ!8kC;l6o&N$`9>;Pp#!^i0aEb;e)jt;BD+P0Jm_`Q`_NjrWxQmCWD>7;WO}x+SN5GwZ~u zH1q@ZsS(pt|IUeE>dWHxW3Zn0z+g*`(16YL&el*T#0@XJ-)R)xm&%$sgs3nq7TV)& z_{Ug!djNpED5pP->HJcyEpv+CRvxqp|NTX~hTQxX@d^laKOqo&|B%yidaBc29v(T~ zR&!!A6INUY@C*IXy~C40R66+4Lgy!1ZvD878&6rg^P@O+Ooqbwk)gXkvvJkq$C+|u z=S8aq>(sAjIj671%tk8@#S@NeKSB7qJ8;?c`(7d7<6f5n2`Utt2#-{Jw%zT`wk^L!K;3PoC@aRB3h2gLJ?ek_^mjaGwek? zwr~hll8ESnb?Lo_2@kD^Kvg-7DZ?f&2*M-T(eS2KuqjXcXNP=FMZ#rt9+TCVowX^pmK0sYLR zD6its+bCUs>9B7Dlc<5WsVSx*=WW|Y@^fyC4^u0RGvWtxsDe09(lY?C| zv+2>#wFi;kAYmUSucWgw1wVAh%u&A>1u~i<*6!do0y{CwWZcPv14CFu0D0(s&jJd0NnehR*0WKb&`Y zC0A-8Y!xd&Qp8rDM)t(sc zJW%9(>G);*|K1rUjrs45ItNmyx(5YPtaAkugYYvXBuoU!*RPhB+@_qtX+j>_yZ|D zHXVE!1K;H!ImQ^c_~G$)#V!sBKP$=p8fA3bNN6|laz8wlE>l>$`-oaM{4(i})P zD|1EH%01&Fmb5rbqE4n5Lpy&^8y-Pew#K2EJkWN!A$NWl>bf6aVyzSEZ~poHcCJKt zW}-4#XHO;Vy>%E7C3$In?5^48!PAHd!8iv86Uvjb=I=CVOgk}!j~3Mg=H9?C`l8`W z&^7|gLwf0zk;;h6_cqbjeu@^`j{XX^P}JiSH2-Br34vNxzq+PhxEsL+SI6|baVx=R zXEs^JgYy{9Sh5u}@Bo;*=#>pgN5vWB>R*!wY~&zBYAgb;F17O@F2_^4(@zVk7K3R? zlg1)k55gF5gsM5Pxfs(BT#{4`5Ja^i7eK~Dm9Ml72WKuDk6|qK{mg=`r>0TR~ zUbMI0FJ9eH#spSv|MuAlv`%Dr{|1V#r6hX)#-|x@w$FHP2XZVmHkjTUE;0#57=+cT zYiA|owVwek^T?|jx8TS+dB9OHy7c6lKoBI`Kbgy zH~>7a@N2rZd4?!XWP`JChcu+)E3KWR%~8?-!|hR;_VT1?W-pTjg+T}D21n*lyUhm z4Zcs;OZzQ3wQS{F$7gacJbKlb{&1oZz0}B_Np0K2pDty>YG~sYddtZAz1tE?bC7|x zF%{r0g!4NBZa{^iSj9rZe_RUTY_(2Lj6aKi-UfX1VzLj&}4RyX>nr$=O31wLy07vq8`QDOzpSK4{|bS}6z{bgnIT;(S9M{mM=!Li z7;V~7q%NRk*T2$G5(&07oZFKVi&YUdrRhICFV2HsPIG&e^eC%3RXE!`sWKM{x00BFPAKTG<~3-?|x>d<}3C+hXV%4E@^2j5Rd3mDq6qObD%7u z?*@rir5%vE)u1)=7S)h)F}EFR!=J%i08Ye>I|I z{XcVJ#Op@83Q`nDj+dTtH)#`~x$;_Sm-MY{4I5;~D$@odQMUsyKK`QXjA{yJP6T)(2=( z*OgyiE%aSf7QJXuM2Go1MJEfsQdY(@ZqV%c|C0K=)Yzxa!Qzl995 ze-DnmB_)f@S{EfuQ|HwOuDp>sFWU$b6&Ikrk&}+{uvz(-*yKR8XtK#k2NRRk8qV?f z&~`JUoUqk~8Q=^LvSV`o(fg4uge%wrtQ=)|x$m>vgQNM=ro>1PWuGa;p(>cgHk{L! zjs0Syxu`G(QY6oRmnqE9mrjbKQ8t1U%6hgk__szF=pCbk(AaVxN5^~b6lRZ=%K?L? z*)CGRT=My3`Q59U%!NI4xYmDs53uq?sGlbn6lf6oUg)x%;BXolsa`jnf5v!Z{k`#S z+TH>4%x}s9GRp~=2@Ktzyacre<-3hCZ52k@iGSd-|L!k5TNfcQW%3O)A_Zn_YqG)u z9JwK1MP*Wtmz|3o;KW_EY*WF`jGEGqjexIo`@K2bl)p4Agr0*QKZ6%2CWnPz<;mPV z`sO*^P+H|%ys}UV12rTgAY|m!h{5!6oEUSEPn(@ReYS|6Z*#7zrx;4)(2=Kf;TIlr z=u;MN@yRLe3JQWj9KRH&Q!HWBaj;0KC&vFkhsE@lcYsSDoy&?6zmKvxVe!g9I#VLw z0+`XXJVF3&BY|+P+Fv70Bn1l9|8`y3(64c`f&Oybp*J1gEE=osnVy-d$dS_4Lcvzz zq{w0fYP|P!3UPnwPMH01Bz;J4;G>#vjhrXcL^&Xf3-R%t3_Sh5BFXeOg1l)?q*`w> zR+-TZ!xRz0IHlo~4(=V_aBVL5%x3gf!Kl-P0R(z$@m`K zB=V{(&-NePYleHeTI@74=-pZxQsy@KESNf7gR}G9Vit7W2|TYuKfYNbn3VpUJ$R6V zP#c)C1aZlmD+W?FHKQH2K=7~V^=?1;uU6aj8l3ug1)DXq+O}C1cTYjVR1dD^qUP9T4*a$KJomR)Uyk->(a$C=@6^W(^|J+6 z>M4R-qc2I;T1kgo(dcofFeL_5QN*@fH{*@-*YjC-Y6@y$y0B_NJ=~>^;pb2t-Sudp zefF`19tjze@$!xh9)@+@LKP3^DN@KvSh&3BLCU zd>6};+RnzkJ8>Kjr1x+=TW$}P}1CP6n;CWSi-AD8ybHeXKFy1ig z%1>21TKvOzxse4k{k$08CU9wdj-ZN&i&o9!GWCJunRnkVA7VEDOq`^nzm{&!s{B7EIK zLu)4kn(Ho-D|CZb=!7R4Rcld5-hNk0^L3OWsaiAuPk4kL4DsI-d*V(Rmh7XuhA0fL zz#{&OXJ+0v!3k0N&GE0A{UmWsr`cmiJF>zhxZ1@nP7gz+BGuk~4;i$yFc`J8 zgN?j-)`>rQEI$?q{;ePH;gT9>kqavqimaBAF$J5RePvX32?k{cqz!{GJ#-(@cZI)K z5ss6CcQXti^U;ok6eP!Q4(1Nen9=uTmoRu zvx;R^D~@OaibUk%^HQLZ@dkH^fcxDE(N@A1Z6pIw7xLAzO{&-8HMwmog>Rhiz&Xek zE`A|92=M!k^dC!G_=XBrpUBMr?$tTA%4e=SJ-j9U;45Dx!1chMLMA2#=O!}tIwa;P zh$N;naCwuEgL46G--E++m-e9dq!!212Jk$8Ck&|h2sO%YT4@_A$<8%Dq3>jFcf;!2 zNWQ0d+CD4&w5%xiNA?TKWWux!p%-u+zyh3#sO$>?<=Y>yZD(iMK1)7EV-x+8F)W

mvL5D#B+p;GEGVxTQZ9 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 z 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 z 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%0D@Jp5ctL-`t`KIIbu$!85t8dvqq`!J(4!zdjH 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 z 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

A?L~`lScY?+;kTPU_8Jps067(@SIhr<}$c_~3D! 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

    Sy{_M@OIcQu&fnLFVDyMt(v(dSl+OYA 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 diff --git a/snesfilter.dll b/snesfilter.dll deleted file mode 100644 index 1978abe29382ef4c57f574ebf3e75e82debbeea7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 96568 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 z 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 diff --git a/snesfilter/2xsai/2xsai.cpp b/snesfilter/2xsai/2xsai.cpp new file mode 100644 index 00000000..c58a27dc --- /dev/null +++ b/snesfilter/2xsai/2xsai.cpp @@ -0,0 +1,132 @@ +//2xSaI / Super 2xSaI / Super Eagle filter +//authors: kode54 and Kreed +//license: GPL + +#include "2xsai.hpp" +#include "implementation.cpp" + +//===== +//2xSaI +//===== + +void _2xSaIFilter::size(unsigned &outwidth, unsigned &outheight, unsigned width, unsigned height) { + outwidth = width; + outheight = height; + + if(width <= 256 && height <= 240) { + outwidth *= 2; + outheight *= 2; + } +} + +void _2xSaIFilter::render( + uint32_t *output, unsigned outpitch, + const uint16_t *input, unsigned pitch, unsigned width, unsigned height +) { + if(width > 256 || height > 240) { + filter_direct.render(output, outpitch, input, pitch, width, height); + return; + } + + for(unsigned y = 0; y < height; y++) { + const uint16_t *line_in = (const uint16_t *) (((const uint8_t*)input) + pitch * y); + uint32_t *line_out = temp + y * 256; + for(unsigned x = 0; x < width; x++) { + line_out[x] = colortable[line_in[x]]; + } + } + + _2xSaI32( (unsigned char *) temp, 1024, 0, (unsigned char *) output, outpitch, width, height ); +} + +_2xSaIFilter::_2xSaIFilter() { + temp = new uint32_t[256*240]; +} + +_2xSaIFilter::~_2xSaIFilter() { + delete[] temp; +} + +//=========== +//Super 2xSaI +//=========== + +void Super2xSaIFilter::size(unsigned &outwidth, unsigned &outheight, unsigned width, unsigned height) { + outwidth = width; + outheight = height; + + if(width <= 256 && height <= 240) { + outwidth *= 2; + outheight *= 2; + } +} + +void Super2xSaIFilter::render( + uint32_t *output, unsigned outpitch, + const uint16_t *input, unsigned pitch, unsigned width, unsigned height +) { + if(width > 256 || height > 240) { + filter_direct.render(output, outpitch, input, pitch, width, height); + return; + } + + for(unsigned y = 0; y < height; y++) { + const uint16_t *line_in = (const uint16_t *) (((const uint8_t*)input) + pitch * y); + uint32_t *line_out = temp + y * 256; + for(unsigned x = 0; x < width; x++) { + line_out[x] = colortable[line_in[x]]; + } + } + + Super2xSaI32( (unsigned char *) temp, 1024, 0, (unsigned char *) output, outpitch, width, height ); +} + +Super2xSaIFilter::Super2xSaIFilter() { + temp = new uint32_t[256*240]; +} + +Super2xSaIFilter::~Super2xSaIFilter() { + delete[] temp; +} + +//=========== +//Super Eagle +//=========== + +void SuperEagleFilter::size(unsigned &outwidth, unsigned &outheight, unsigned width, unsigned height) { + outwidth = width; + outheight = height; + + if(width <= 256 && height <= 240) { + outwidth *= 2; + outheight *= 2; + } +} + +void SuperEagleFilter::render( + uint32_t *output, unsigned outpitch, + const uint16_t *input, unsigned pitch, unsigned width, unsigned height +) { + if(width > 256 || height > 240) { + filter_direct.render(output, outpitch, input, pitch, width, height); + return; + } + + for(unsigned y = 0; y < height; y++) { + const uint16_t *line_in = (const uint16_t *) (((const uint8_t*)input) + pitch * y); + uint32_t *line_out = temp + y * 256; + for(unsigned x = 0; x < width; x++) { + line_out[x] = colortable[line_in[x]]; + } + } + + SuperEagle32( (unsigned char *) temp, 1024, 0, (unsigned char *) output, outpitch, width, height ); +} + +SuperEagleFilter::SuperEagleFilter() { + temp = new uint32_t[256*240]; +} + +SuperEagleFilter::~SuperEagleFilter() { + delete[] temp; +} diff --git a/snesfilter/2xsai/2xsai.hpp b/snesfilter/2xsai/2xsai.hpp new file mode 100644 index 00000000..8f3031fb --- /dev/null +++ b/snesfilter/2xsai/2xsai.hpp @@ -0,0 +1,35 @@ +class _2xSaIFilter { +public: + void size(unsigned&, unsigned&, unsigned, unsigned); + void render(uint32_t*, unsigned, const uint16_t*, unsigned, unsigned, unsigned); + + _2xSaIFilter(); + ~_2xSaIFilter(); + +private: + uint32_t *temp; +} filter_2xsai; + +class Super2xSaIFilter { +public: + void size(unsigned&, unsigned&, unsigned, unsigned); + void render(uint32_t*, unsigned, const uint16_t*, unsigned, unsigned, unsigned); + + Super2xSaIFilter(); + ~Super2xSaIFilter(); + +private: + uint32_t *temp; +} filter_super2xsai; + +class SuperEagleFilter { +public: + void size(unsigned&, unsigned&, unsigned, unsigned); + void render(uint32_t*, unsigned, const uint16_t*, unsigned, unsigned, unsigned); + + SuperEagleFilter(); + ~SuperEagleFilter(); + +private: + uint32_t *temp; +} filter_supereagle; diff --git a/snesfilter/2xsai/implementation.cpp b/snesfilter/2xsai/implementation.cpp new file mode 100644 index 00000000..cebca7ef --- /dev/null +++ b/snesfilter/2xsai/implementation.cpp @@ -0,0 +1,1171 @@ +static uint32_t colorMask = 0xFEFEFE; +static uint32_t lowPixelMask = 0x010101; +static uint32_t qcolorMask = 0xFCFCFC; +static uint32_t qlowpixelMask = 0x030303; +static uint32_t redblueMask = 0xFF00FF; +static uint32_t greenMask = 0xFF00; + +uint32_t qRGB_COLOR_MASK[2] = { 0xFEFEFE, 0xFEFEFE }; + +static inline int GetResult1 (uint32_t A, uint32_t B, uint32_t C, uint32_t D, + uint32_t /* E */) +{ + int x = 0; + int y = 0; + int r = 0; + + if (A == C) + x += 1; + else if (B == C) + y += 1; + if (A == D) + x += 1; + else if (B == D) + y += 1; + if (x <= 1) + r += 1; + if (y <= 1) + r -= 1; + return r; +} + +static inline int GetResult2 (uint32_t A, uint32_t B, uint32_t C, uint32_t D, + uint32_t /* E */) +{ + int x = 0; + int y = 0; + int r = 0; + + if (A == C) + x += 1; + else if (B == C) + y += 1; + if (A == D) + x += 1; + else if (B == D) + y += 1; + if (x <= 1) + r -= 1; + if (y <= 1) + r += 1; + return r; +} + +static inline int GetResult (uint32_t A, uint32_t B, uint32_t C, uint32_t D) +{ + int x = 0; + int y = 0; + int r = 0; + + if (A == C) + x += 1; + else if (B == C) + y += 1; + if (A == D) + x += 1; + else if (B == D) + y += 1; + if (x <= 1) + r += 1; + if (y <= 1) + r -= 1; + return r; +} + +static inline uint32_t INTERPOLATE (uint32_t A, uint32_t B) +{ + if (A != B) { + return (((A & colorMask) >> 1) + ((B & colorMask) >> 1) + + (A & B & lowPixelMask)); + } else + return A; +} + +static inline uint32_t Q_INTERPOLATE (uint32_t A, uint32_t B, uint32_t C, uint32_t D) +{ + register uint32_t x = ((A & qcolorMask) >> 2) + + ((B & qcolorMask) >> 2) + + ((C & qcolorMask) >> 2) + ((D & qcolorMask) >> 2); + register uint32_t y = (A & qlowpixelMask) + + (B & qlowpixelMask) + (C & qlowpixelMask) + (D & qlowpixelMask); + + y = (y >> 2) & qlowpixelMask; + return x + y; +} + +static inline int GetResult1_32 (uint32_t A, uint32_t B, uint32_t C, uint32_t D, + uint32_t /* E */) +{ + int x = 0; + int y = 0; + int r = 0; + + if (A == C) + x += 1; + else if (B == C) + y += 1; + if (A == D) + x += 1; + else if (B == D) + y += 1; + if (x <= 1) + r += 1; + if (y <= 1) + r -= 1; + return r; +} + +static inline int GetResult2_32 (uint32_t A, uint32_t B, uint32_t C, uint32_t D, + uint32_t /* E */) +{ + int x = 0; + int y = 0; + int r = 0; + + if (A == C) + x += 1; + else if (B == C) + y += 1; + if (A == D) + x += 1; + else if (B == D) + y += 1; + if (x <= 1) + r -= 1; + if (y <= 1) + r += 1; + return r; +} + +#define BLUE_MASK565 0x001F001F +#define RED_MASK565 0xF800F800 +#define GREEN_MASK565 0x07E007E0 + +#define BLUE_MASK555 0x001F001F +#define RED_MASK555 0x7C007C00 +#define GREEN_MASK555 0x03E003E0 + +void Super2xSaI (uint8_t *srcPtr, uint32_t srcPitch, + uint8_t *deltaPtr, uint8_t *dstPtr, uint32_t dstPitch, + int width, int height) +{ + uint16_t *bP; + uint8_t *dP; + uint32_t inc_bP; + uint32_t Nextline = srcPitch >> 1; + { + inc_bP = 1; + + for (; height; height--) { + bP = (uint16_t *) srcPtr; + dP = (uint8_t *) dstPtr; + + for (uint32_t finish = width; finish; finish -= inc_bP) { + uint32_t color4, color5, color6; + uint32_t color1, color2, color3; + uint32_t colorA0, colorA1, colorA2, colorA3, + colorB0, colorB1, colorB2, colorB3, colorS1, colorS2; + uint32_t product1a, product1b, product2a, product2b; + + //--------------------------------------- B1 B2 + // 4 5 6 S2 + // 1 2 3 S1 + // A1 A2 + + colorB0 = *(bP - Nextline - 1); + colorB1 = *(bP - Nextline); + colorB2 = *(bP - Nextline + 1); + colorB3 = *(bP - Nextline + 2); + + color4 = *(bP - 1); + color5 = *(bP); + color6 = *(bP + 1); + colorS2 = *(bP + 2); + + color1 = *(bP + Nextline - 1); + color2 = *(bP + Nextline); + color3 = *(bP + Nextline + 1); + colorS1 = *(bP + Nextline + 2); + + colorA0 = *(bP + Nextline + Nextline - 1); + colorA1 = *(bP + Nextline + Nextline); + colorA2 = *(bP + Nextline + Nextline + 1); + colorA3 = *(bP + Nextline + Nextline + 2); + + //-------------------------------------- + if (color2 == color6 && color5 != color3) { + product2b = product1b = color2; + } else if (color5 == color3 && color2 != color6) { + product2b = product1b = color5; + } else if (color5 == color3 && color2 == color6) { + register int r = 0; + + r += GetResult (color6, color5, color1, colorA1); + r += GetResult (color6, color5, color4, colorB1); + r += GetResult (color6, color5, colorA2, colorS1); + r += GetResult (color6, color5, colorB2, colorS2); + + if (r > 0) + product2b = product1b = color6; + else if (r < 0) + product2b = product1b = color5; + else { + product2b = product1b = INTERPOLATE (color5, color6); + } + } else { + if (color6 == color3 && color3 == colorA1 + && color2 != colorA2 && color3 != colorA0) + product2b = + Q_INTERPOLATE (color3, color3, color3, color2); + else if (color5 == color2 && color2 == colorA2 + && colorA1 != color3 && color2 != colorA3) + product2b = + Q_INTERPOLATE (color2, color2, color2, color3); + else + product2b = INTERPOLATE (color2, color3); + + if (color6 == color3 && color6 == colorB1 + && color5 != colorB2 && color6 != colorB0) + product1b = + Q_INTERPOLATE (color6, color6, color6, color5); + else if (color5 == color2 && color5 == colorB2 + && colorB1 != color6 && color5 != colorB3) + product1b = + Q_INTERPOLATE (color6, color5, color5, color5); + else + product1b = INTERPOLATE (color5, color6); + } + + if (color5 == color3 && color2 != color6 && color4 == color5 + && color5 != colorA2) + product2a = INTERPOLATE (color2, color5); + else + if (color5 == color1 && color6 == color5 + && color4 != color2 && color5 != colorA0) + product2a = INTERPOLATE (color2, color5); + else + product2a = color2; + + if (color2 == color6 && color5 != color3 && color1 == color2 + && color2 != colorB2) + product1a = INTERPOLATE (color2, color5); + else + if (color4 == color2 && color3 == color2 + && color1 != color5 && color2 != colorB0) + product1a = INTERPOLATE (color2, color5); + else + product1a = color5; + +#ifdef WORDS_BIGENDIAN + product1a = (product1a << 16) | product1b; + product2a = (product2a << 16) | product2b; +#else + product1a = product1a | (product1b << 16); + product2a = product2a | (product2b << 16); +#endif + + *((uint32_t *) dP) = product1a; + *((uint32_t *) (dP + dstPitch)) = product2a; + + bP += inc_bP; + dP += sizeof (uint32_t); + } // end of for ( finish= width etc..) + + srcPtr += srcPitch; + dstPtr += dstPitch << 1; + deltaPtr += srcPitch; + } // endof: for (; height; height--) + } +} + +void Super2xSaI32 (uint8_t *srcPtr, uint32_t srcPitch, + uint8_t * /* deltaPtr */, uint8_t *dstPtr, uint32_t dstPitch, + int width, int height) +{ + uint32_t *bP; + uint32_t *dP; + uint32_t inc_bP; + uint32_t Nextline = srcPitch >> 2; + inc_bP = 1; + + for (; height; height--) { + bP = (uint32_t *) srcPtr; + dP = (uint32_t *) dstPtr; + + for (uint32_t finish = width; finish; finish -= inc_bP) { + uint32_t color4, color5, color6; + uint32_t color1, color2, color3; + uint32_t colorA0, colorA1, colorA2, colorA3, + colorB0, colorB1, colorB2, colorB3, colorS1, colorS2; + uint32_t product1a, product1b, product2a, product2b; + + //--------------------------------------- B1 B2 + // 4 5 6 S2 + // 1 2 3 S1 + // A1 A2 + + colorB0 = *(bP - Nextline - 1); + colorB1 = *(bP - Nextline); + colorB2 = *(bP - Nextline + 1); + colorB3 = *(bP - Nextline + 2); + + color4 = *(bP - 1); + color5 = *(bP); + color6 = *(bP + 1); + colorS2 = *(bP + 2); + + color1 = *(bP + Nextline - 1); + color2 = *(bP + Nextline); + color3 = *(bP + Nextline + 1); + colorS1 = *(bP + Nextline + 2); + + colorA0 = *(bP + Nextline + Nextline - 1); + colorA1 = *(bP + Nextline + Nextline); + colorA2 = *(bP + Nextline + Nextline + 1); + colorA3 = *(bP + Nextline + Nextline + 2); + + //-------------------------------------- + if (color2 == color6 && color5 != color3) { + product2b = product1b = color2; + } else if (color5 == color3 && color2 != color6) { + product2b = product1b = color5; + } else if (color5 == color3 && color2 == color6) { + register int r = 0; + + r += GetResult (color6, color5, color1, colorA1); + r += GetResult (color6, color5, color4, colorB1); + r += GetResult (color6, color5, colorA2, colorS1); + r += GetResult (color6, color5, colorB2, colorS2); + + if (r > 0) + product2b = product1b = color6; + else if (r < 0) + product2b = product1b = color5; + else { + product2b = product1b = INTERPOLATE (color5, color6); + } + } else { + if (color6 == color3 && color3 == colorA1 + && color2 != colorA2 && color3 != colorA0) + product2b = + Q_INTERPOLATE (color3, color3, color3, color2); + else if (color5 == color2 && color2 == colorA2 + && colorA1 != color3 && color2 != colorA3) + product2b = + Q_INTERPOLATE (color2, color2, color2, color3); + else + product2b = INTERPOLATE (color2, color3); + + if (color6 == color3 && color6 == colorB1 + && color5 != colorB2 && color6 != colorB0) + product1b = + Q_INTERPOLATE (color6, color6, color6, color5); + else if (color5 == color2 && color5 == colorB2 + && colorB1 != color6 && color5 != colorB3) + product1b = + Q_INTERPOLATE (color6, color5, color5, color5); + else + product1b = INTERPOLATE (color5, color6); + } + + if (color5 == color3 && color2 != color6 && color4 == color5 + && color5 != colorA2) + product2a = INTERPOLATE (color2, color5); + else + if (color5 == color1 && color6 == color5 + && color4 != color2 && color5 != colorA0) + product2a = INTERPOLATE (color2, color5); + else + product2a = color2; + + if (color2 == color6 && color5 != color3 && color1 == color2 + && color2 != colorB2) + product1a = INTERPOLATE (color2, color5); + else + if (color4 == color2 && color3 == color2 + && color1 != color5 && color2 != colorB0) + product1a = INTERPOLATE (color2, color5); + else + product1a = color5; + *(dP) = product1a; + *(dP+1) = product1b; + *(dP + (dstPitch >> 2)) = product2a; + *(dP + (dstPitch >> 2) + 1) = product2b; + + bP += inc_bP; + dP += 2; + } // end of for ( finish= width etc..) + + srcPtr += srcPitch; + dstPtr += dstPitch << 1; + // deltaPtr += srcPitch; + } // endof: for (; height; height--) +} + +void SuperEagle (uint8_t *srcPtr, uint32_t srcPitch, uint8_t *deltaPtr, + uint8_t *dstPtr, uint32_t dstPitch, int width, int height) +{ + uint8_t *dP; + uint16_t *bP; + uint16_t *xP; + uint32_t inc_bP; + + { + inc_bP = 1; + + uint32_t Nextline = srcPitch >> 1; + + for (; height; height--) { + bP = (uint16_t *) srcPtr; + xP = (uint16_t *) deltaPtr; + dP = dstPtr; + for (uint32_t finish = width; finish; finish -= inc_bP) { + uint32_t color4, color5, color6; + uint32_t color1, color2, color3; + uint32_t colorA1, colorA2, colorB1, colorB2, colorS1, colorS2; + uint32_t product1a, product1b, product2a, product2b; + + colorB1 = *(bP - Nextline); + colorB2 = *(bP - Nextline + 1); + + color4 = *(bP - 1); + color5 = *(bP); + color6 = *(bP + 1); + colorS2 = *(bP + 2); + + color1 = *(bP + Nextline - 1); + color2 = *(bP + Nextline); + color3 = *(bP + Nextline + 1); + colorS1 = *(bP + Nextline + 2); + + colorA1 = *(bP + Nextline + Nextline); + colorA2 = *(bP + Nextline + Nextline + 1); + + // -------------------------------------- + if (color2 == color6 && color5 != color3) { + product1b = product2a = color2; + if ((color1 == color2) || (color6 == colorB2)) { + product1a = INTERPOLATE (color2, color5); + product1a = INTERPOLATE (color2, product1a); + // product1a = color2; + } else { + product1a = INTERPOLATE (color5, color6); + } + + if ((color6 == colorS2) || (color2 == colorA1)) { + product2b = INTERPOLATE (color2, color3); + product2b = INTERPOLATE (color2, product2b); + // product2b = color2; + } else { + product2b = INTERPOLATE (color2, color3); + } + } else if (color5 == color3 && color2 != color6) { + product2b = product1a = color5; + + if ((colorB1 == color5) || (color3 == colorS1)) { + product1b = INTERPOLATE (color5, color6); + product1b = INTERPOLATE (color5, product1b); + // product1b = color5; + } else { + product1b = INTERPOLATE (color5, color6); + } + + if ((color3 == colorA2) || (color4 == color5)) { + product2a = INTERPOLATE (color5, color2); + product2a = INTERPOLATE (color5, product2a); + // product2a = color5; + } else { + product2a = INTERPOLATE (color2, color3); + } + + } else if (color5 == color3 && color2 == color6) { + register int r = 0; + + r += GetResult (color6, color5, color1, colorA1); + r += GetResult (color6, color5, color4, colorB1); + r += GetResult (color6, color5, colorA2, colorS1); + r += GetResult (color6, color5, colorB2, colorS2); + + if (r > 0) { + product1b = product2a = color2; + product1a = product2b = INTERPOLATE (color5, color6); + } else if (r < 0) { + product2b = product1a = color5; + product1b = product2a = INTERPOLATE (color5, color6); + } else { + product2b = product1a = color5; + product1b = product2a = color2; + } + } else { + product2b = product1a = INTERPOLATE (color2, color6); + product2b = + Q_INTERPOLATE (color3, color3, color3, product2b); + product1a = + Q_INTERPOLATE (color5, color5, color5, product1a); + + product2a = product1b = INTERPOLATE (color5, color3); + product2a = + Q_INTERPOLATE (color2, color2, color2, product2a); + product1b = + Q_INTERPOLATE (color6, color6, color6, product1b); + + // product1a = color5; + // product1b = color6; + // product2a = color2; + // product2b = color3; + } +#ifdef WORDS_BIGENDIAN + product1a = (product1a << 16) | product1b; + product2a = (product2a << 16) | product2b; +#else + product1a = product1a | (product1b << 16); + product2a = product2a | (product2b << 16); +#endif + + *((uint32_t *) dP) = product1a; + *((uint32_t *) (dP + dstPitch)) = product2a; + *xP = color5; + + bP += inc_bP; + xP += inc_bP; + dP += sizeof (uint32_t); + } // end of for ( finish= width etc..) + + srcPtr += srcPitch; + dstPtr += dstPitch << 1; + deltaPtr += srcPitch; + } // endof: for (height; height; height--) + } +} + +void SuperEagle32 (uint8_t *srcPtr, uint32_t srcPitch, uint8_t */*deltaPtr*/, + uint8_t *dstPtr, uint32_t dstPitch, int width, int height) +{ + uint32_t *dP; + uint32_t *bP; + //uint32_t *xP; + uint32_t inc_bP; + + inc_bP = 1; + + uint32_t Nextline = srcPitch >> 2; + + for (; height; height--) { + bP = (uint32_t *) srcPtr; + //xP = (uint32_t *) deltaPtr; + dP = (uint32_t *)dstPtr; + for (uint32_t finish = width; finish; finish -= inc_bP) { + uint32_t color4, color5, color6; + uint32_t color1, color2, color3; + uint32_t colorA1, colorA2, colorB1, colorB2, colorS1, colorS2; + uint32_t product1a, product1b, product2a, product2b; + + colorB1 = *(bP - Nextline); + colorB2 = *(bP - Nextline + 1); + + color4 = *(bP - 1); + color5 = *(bP); + color6 = *(bP + 1); + colorS2 = *(bP + 2); + + color1 = *(bP + Nextline - 1); + color2 = *(bP + Nextline); + color3 = *(bP + Nextline + 1); + colorS1 = *(bP + Nextline + 2); + + colorA1 = *(bP + Nextline + Nextline); + colorA2 = *(bP + Nextline + Nextline + 1); + + // -------------------------------------- + if (color2 == color6 && color5 != color3) { + product1b = product2a = color2; + if ((color1 == color2) || (color6 == colorB2)) { + product1a = INTERPOLATE (color2, color5); + product1a = INTERPOLATE (color2, product1a); + // product1a = color2; + } else { + product1a = INTERPOLATE (color5, color6); + } + + if ((color6 == colorS2) || (color2 == colorA1)) { + product2b = INTERPOLATE (color2, color3); + product2b = INTERPOLATE (color2, product2b); + // product2b = color2; + } else { + product2b = INTERPOLATE (color2, color3); + } + } else if (color5 == color3 && color2 != color6) { + product2b = product1a = color5; + + if ((colorB1 == color5) || (color3 == colorS1)) { + product1b = INTERPOLATE (color5, color6); + product1b = INTERPOLATE (color5, product1b); + // product1b = color5; + } else { + product1b = INTERPOLATE (color5, color6); + } + + if ((color3 == colorA2) || (color4 == color5)) { + product2a = INTERPOLATE (color5, color2); + product2a = INTERPOLATE (color5, product2a); + // product2a = color5; + } else { + product2a = INTERPOLATE (color2, color3); + } + + } else if (color5 == color3 && color2 == color6) { + register int r = 0; + + r += GetResult (color6, color5, color1, colorA1); + r += GetResult (color6, color5, color4, colorB1); + r += GetResult (color6, color5, colorA2, colorS1); + r += GetResult (color6, color5, colorB2, colorS2); + + if (r > 0) { + product1b = product2a = color2; + product1a = product2b = INTERPOLATE (color5, color6); + } else if (r < 0) { + product2b = product1a = color5; + product1b = product2a = INTERPOLATE (color5, color6); + } else { + product2b = product1a = color5; + product1b = product2a = color2; + } + } else { + product2b = product1a = INTERPOLATE (color2, color6); + product2b = + Q_INTERPOLATE (color3, color3, color3, product2b); + product1a = + Q_INTERPOLATE (color5, color5, color5, product1a); + + product2a = product1b = INTERPOLATE (color5, color3); + product2a = + Q_INTERPOLATE (color2, color2, color2, product2a); + product1b = + Q_INTERPOLATE (color6, color6, color6, product1b); + + // product1a = color5; + // product1b = color6; + // product2a = color2; + // product2b = color3; + } + *(dP) = product1a; + *(dP+1) = product1b; + *(dP + (dstPitch >> 2)) = product2a; + *(dP + (dstPitch >> 2) +1) = product2b; + //*xP = color5; + + bP += inc_bP; + //xP += inc_bP; + dP += 2; + } // end of for ( finish= width etc..) + + srcPtr += srcPitch; + dstPtr += dstPitch << 1; + //deltaPtr += srcPitch; + } // endof: for (height; height; height--) +} + +void _2xSaI (uint8_t *srcPtr, uint32_t srcPitch, uint8_t *deltaPtr, + uint8_t *dstPtr, uint32_t dstPitch, int width, int height) +{ + uint8_t *dP; + uint16_t *bP; + uint32_t inc_bP; + + { + inc_bP = 1; + + uint32_t Nextline = srcPitch >> 1; + + for (; height; height--) { + bP = (uint16_t *) srcPtr; + dP = dstPtr; + + for (uint32_t finish = width; finish; finish -= inc_bP) { + + register uint32_t colorA, colorB; + uint32_t colorC, colorD, + colorE, colorF, colorG, colorH, + colorI, colorJ, colorK, colorL, + + colorM, colorN, colorO, colorP; + uint32_t product, product1, product2; + + //--------------------------------------- + // Map of the pixels: I|E F|J + // G|A B|K + // H|C D|L + // M|N O|P + colorI = *(bP - Nextline - 1); + colorE = *(bP - Nextline); + colorF = *(bP - Nextline + 1); + colorJ = *(bP - Nextline + 2); + + colorG = *(bP - 1); + colorA = *(bP); + colorB = *(bP + 1); + colorK = *(bP + 2); + + colorH = *(bP + Nextline - 1); + colorC = *(bP + Nextline); + colorD = *(bP + Nextline + 1); + colorL = *(bP + Nextline + 2); + + colorM = *(bP + Nextline + Nextline - 1); + colorN = *(bP + Nextline + Nextline); + colorO = *(bP + Nextline + Nextline + 1); + colorP = *(bP + Nextline + Nextline + 2); + + if ((colorA == colorD) && (colorB != colorC)) { + if (((colorA == colorE) && (colorB == colorL)) || + ((colorA == colorC) && (colorA == colorF) + && (colorB != colorE) && (colorB == colorJ))) { + product = colorA; + } else { + product = INTERPOLATE (colorA, colorB); + } + + if (((colorA == colorG) && (colorC == colorO)) || + ((colorA == colorB) && (colorA == colorH) + && (colorG != colorC) && (colorC == colorM))) { + product1 = colorA; + } else { + product1 = INTERPOLATE (colorA, colorC); + } + product2 = colorA; + } else if ((colorB == colorC) && (colorA != colorD)) { + if (((colorB == colorF) && (colorA == colorH)) || + ((colorB == colorE) && (colorB == colorD) + && (colorA != colorF) && (colorA == colorI))) { + product = colorB; + } else { + product = INTERPOLATE (colorA, colorB); + } + + if (((colorC == colorH) && (colorA == colorF)) || + ((colorC == colorG) && (colorC == colorD) + && (colorA != colorH) && (colorA == colorI))) { + product1 = colorC; + } else { + product1 = INTERPOLATE (colorA, colorC); + } + product2 = colorB; + } else if ((colorA == colorD) && (colorB == colorC)) { + if (colorA == colorB) { + product = colorA; + product1 = colorA; + product2 = colorA; + } else { + register int r = 0; + + product1 = INTERPOLATE (colorA, colorC); + product = INTERPOLATE (colorA, colorB); + + r += + GetResult1 (colorA, colorB, colorG, colorE, + colorI); + r += + GetResult2 (colorB, colorA, colorK, colorF, + colorJ); + r += + GetResult2 (colorB, colorA, colorH, colorN, + colorM); + r += + GetResult1 (colorA, colorB, colorL, colorO, + colorP); + + if (r > 0) + product2 = colorA; + else if (r < 0) + product2 = colorB; + else { + product2 = + Q_INTERPOLATE (colorA, colorB, colorC, + colorD); + } + } + } else { + product2 = Q_INTERPOLATE (colorA, colorB, colorC, colorD); + + if ((colorA == colorC) && (colorA == colorF) + && (colorB != colorE) && (colorB == colorJ)) { + product = colorA; + } else if ((colorB == colorE) && (colorB == colorD) + && (colorA != colorF) && (colorA == colorI)) { + product = colorB; + } else { + product = INTERPOLATE (colorA, colorB); + } + + if ((colorA == colorB) && (colorA == colorH) + && (colorG != colorC) && (colorC == colorM)) { + product1 = colorA; + } else if ((colorC == colorG) && (colorC == colorD) + && (colorA != colorH) && (colorA == colorI)) { + product1 = colorC; + } else { + product1 = INTERPOLATE (colorA, colorC); + } + } + +#ifdef WORDS_BIGENDIAN + product = (colorA << 16) | product ; + product1 = (product1 << 16) | product2 ; +#else + product = colorA | (product << 16); + product1 = product1 | (product2 << 16); +#endif + *((int32_t *) dP) = product; + *((uint32_t *) (dP + dstPitch)) = product1; + + bP += inc_bP; + dP += sizeof (uint32_t); + } // end of for ( finish= width etc..) + + srcPtr += srcPitch; + dstPtr += dstPitch << 1; + deltaPtr += srcPitch; + } // endof: for (height; height; height--) + } +} + +void _2xSaI32 (uint8_t *srcPtr, uint32_t srcPitch, uint8_t * /* deltaPtr */, + uint8_t *dstPtr, uint32_t dstPitch, int width, int height) +{ + uint32_t *dP; + uint32_t *bP; + uint32_t inc_bP = 1; + + uint32_t Nextline = srcPitch >> 2; + + for (; height; height--) { + bP = (uint32_t *) srcPtr; + dP = (uint32_t *) dstPtr; + + for (uint32_t finish = width; finish; finish -= inc_bP) { + register uint32_t colorA, colorB; + uint32_t colorC, colorD, + colorE, colorF, colorG, colorH, + colorI, colorJ, colorK, colorL, + + colorM, colorN, colorO, colorP; + uint32_t product, product1, product2; + + //--------------------------------------- + // Map of the pixels: I|E F|J + // G|A B|K + // H|C D|L + // M|N O|P + colorI = *(bP - Nextline - 1); + colorE = *(bP - Nextline); + colorF = *(bP - Nextline + 1); + colorJ = *(bP - Nextline + 2); + + colorG = *(bP - 1); + colorA = *(bP); + colorB = *(bP + 1); + colorK = *(bP + 2); + + colorH = *(bP + Nextline - 1); + colorC = *(bP + Nextline); + colorD = *(bP + Nextline + 1); + colorL = *(bP + Nextline + 2); + + colorM = *(bP + Nextline + Nextline - 1); + colorN = *(bP + Nextline + Nextline); + colorO = *(bP + Nextline + Nextline + 1); + colorP = *(bP + Nextline + Nextline + 2); + + if ((colorA == colorD) && (colorB != colorC)) { + if (((colorA == colorE) && (colorB == colorL)) || + ((colorA == colorC) && (colorA == colorF) + && (colorB != colorE) && (colorB == colorJ))) { + product = colorA; + } else { + product = INTERPOLATE (colorA, colorB); + } + + if (((colorA == colorG) && (colorC == colorO)) || + ((colorA == colorB) && (colorA == colorH) + && (colorG != colorC) && (colorC == colorM))) { + product1 = colorA; + } else { + product1 = INTERPOLATE (colorA, colorC); + } + product2 = colorA; + } else if ((colorB == colorC) && (colorA != colorD)) { + if (((colorB == colorF) && (colorA == colorH)) || + ((colorB == colorE) && (colorB == colorD) + && (colorA != colorF) && (colorA == colorI))) { + product = colorB; + } else { + product = INTERPOLATE (colorA, colorB); + } + + if (((colorC == colorH) && (colorA == colorF)) || + ((colorC == colorG) && (colorC == colorD) + && (colorA != colorH) && (colorA == colorI))) { + product1 = colorC; + } else { + product1 = INTERPOLATE (colorA, colorC); + } + product2 = colorB; + } else if ((colorA == colorD) && (colorB == colorC)) { + if (colorA == colorB) { + product = colorA; + product1 = colorA; + product2 = colorA; + } else { + register int r = 0; + + product1 = INTERPOLATE (colorA, colorC); + product = INTERPOLATE (colorA, colorB); + + r += + GetResult1 (colorA, colorB, colorG, colorE, + colorI); + r += + GetResult2 (colorB, colorA, colorK, colorF, + colorJ); + r += + GetResult2 (colorB, colorA, colorH, colorN, + colorM); + r += + GetResult1 (colorA, colorB, colorL, colorO, + colorP); + + if (r > 0) + product2 = colorA; + else if (r < 0) + product2 = colorB; + else { + product2 = + Q_INTERPOLATE (colorA, colorB, colorC, + colorD); + } + } + } else { + product2 = Q_INTERPOLATE (colorA, colorB, colorC, colorD); + + if ((colorA == colorC) && (colorA == colorF) + && (colorB != colorE) && (colorB == colorJ)) { + product = colorA; + } else if ((colorB == colorE) && (colorB == colorD) + && (colorA != colorF) && (colorA == colorI)) { + product = colorB; + } else { + product = INTERPOLATE (colorA, colorB); + } + + if ((colorA == colorB) && (colorA == colorH) + && (colorG != colorC) && (colorC == colorM)) { + product1 = colorA; + } else if ((colorC == colorG) && (colorC == colorD) + && (colorA != colorH) && (colorA == colorI)) { + product1 = colorC; + } else { + product1 = INTERPOLATE (colorA, colorC); + } + } + *(dP) = colorA; + *(dP + 1) = product; + *(dP + (dstPitch >> 2)) = product1; + *(dP + (dstPitch >> 2) + 1) = product2; + + bP += inc_bP; + dP += 2; + } // end of for ( finish= width etc..) + + srcPtr += srcPitch; + dstPtr += dstPitch << 1; + // deltaPtr += srcPitch; + } // endof: for (height; height; height--) +} + +static uint32_t Bilinear (uint32_t A, uint32_t B, uint32_t x) +{ + unsigned long areaA, areaB; + unsigned long result; + + if (A == B) + return A; + + areaB = (x >> 11) & 0x1f; // reduce 16 bit fraction to 5 bits + areaA = 0x20 - areaB; + + A = (A & redblueMask) | ((A & greenMask) << 16); + B = (B & redblueMask) | ((B & greenMask) << 16); + + result = ((areaA * A) + (areaB * B)) >> 5; + + return (result & redblueMask) | ((result >> 16) & greenMask); +} + +static uint32_t Bilinear4 (uint32_t A, uint32_t B, uint32_t C, uint32_t D, uint32_t x, + uint32_t y) +{ + unsigned long areaA, areaB, areaC, areaD; + unsigned long result, xy; + + x = (x >> 11) & 0x1f; + y = (y >> 11) & 0x1f; + xy = (x * y) >> 5; + + A = (A & redblueMask) | ((A & greenMask) << 16); + B = (B & redblueMask) | ((B & greenMask) << 16); + C = (C & redblueMask) | ((C & greenMask) << 16); + D = (D & redblueMask) | ((D & greenMask) << 16); + + areaA = 0x20 + xy - x - y; + areaB = x - xy; + areaC = y - xy; + areaD = xy; + + result = ((areaA * A) + (areaB * B) + (areaC * C) + (areaD * D)) >> 5; + + return (result & redblueMask) | ((result >> 16) & greenMask); +} + +void Scale_2xSaI (uint8_t *srcPtr, uint32_t srcPitch, uint8_t * /* deltaPtr */, + uint8_t *dstPtr, uint32_t dstPitch, + uint32_t dstWidth, uint32_t dstHeight, int width, int height) +{ + uint8_t *dP; + uint16_t *bP; + + uint32_t w; + uint32_t h; + uint32_t dw; + uint32_t dh; + uint32_t hfinish; + uint32_t wfinish; + + uint32_t Nextline = srcPitch >> 1; + + wfinish = (width - 1) << 16; // convert to fixed point + dw = wfinish / (dstWidth - 1); + hfinish = (height - 1) << 16; // convert to fixed point + dh = hfinish / (dstHeight - 1); + + for (h = 0; h < hfinish; h += dh) { + uint32_t y1, y2; + + y1 = h & 0xffff; // fraction part of fixed point + bP = (uint16_t *) (srcPtr + ((h >> 16) * srcPitch)); + dP = dstPtr; + y2 = 0x10000 - y1; + + w = 0; + + for (; w < wfinish;) { + uint32_t A, B, C, D; + uint32_t E, F, G, H; + uint32_t I, J, K, L; + uint32_t x1, x2, a1, f1, f2; + uint32_t position, product1; + + position = w >> 16; + A = bP[position]; // current pixel + B = bP[position + 1]; // next pixel + C = bP[position + Nextline]; + D = bP[position + Nextline + 1]; + E = bP[position - Nextline]; + F = bP[position - Nextline + 1]; + G = bP[position - 1]; + H = bP[position + Nextline - 1]; + I = bP[position + 2]; + J = bP[position + Nextline + 2]; + K = bP[position + Nextline + Nextline]; + L = bP[position + Nextline + Nextline + 1]; + + x1 = w & 0xffff; // fraction part of fixed point + x2 = 0x10000 - x1; + + /*0*/ + if (A == B && C == D && A == C) + product1 = A; + else /*1*/ if (A == D && B != C) { + f1 = (x1 >> 1) + (0x10000 >> 2); + f2 = (y1 >> 1) + (0x10000 >> 2); + if (y1 <= f1 && A == J && A != E) // close to B + { + a1 = f1 - y1; + product1 = Bilinear (A, B, a1); + } else if (y1 >= f1 && A == G && A != L) // close to C + { + a1 = y1 - f1; + product1 = Bilinear (A, C, a1); + } + else if (x1 >= f2 && A == E && A != J) // close to B + { + a1 = x1 - f2; + product1 = Bilinear (A, B, a1); + } + else if (x1 <= f2 && A == L && A != G) // close to C + { + a1 = f2 - x1; + product1 = Bilinear (A, C, a1); + } + else if (y1 >= x1) // close to C + { + a1 = y1 - x1; + product1 = Bilinear (A, C, a1); + } + else if (y1 <= x1) // close to B + { + a1 = x1 - y1; + product1 = Bilinear (A, B, a1); + } + } + else + /*2*/ + if (B == C && A != D) + { + f1 = (x1 >> 1) + (0x10000 >> 2); + f2 = (y1 >> 1) + (0x10000 >> 2); + if (y2 >= f1 && B == H && B != F) // close to A + { + a1 = y2 - f1; + product1 = Bilinear (B, A, a1); + } + else if (y2 <= f1 && B == I && B != K) // close to D + { + a1 = f1 - y2; + product1 = Bilinear (B, D, a1); + } + else if (x2 >= f2 && B == F && B != H) // close to A + { + a1 = x2 - f2; + product1 = Bilinear (B, A, a1); + } + else if (x2 <= f2 && B == K && B != I) // close to D + { + a1 = f2 - x2; + product1 = Bilinear (B, D, a1); + } + else if (y2 >= x1) // close to A + { + a1 = y2 - x1; + product1 = Bilinear (B, A, a1); + } + else if (y2 <= x1) // close to D + { + a1 = x1 - y2; + product1 = Bilinear (B, D, a1); + } + } + /*3*/ + else + { + product1 = Bilinear4 (A, B, C, D, x1, y1); + } + + //end First Pixel + *(uint32_t *) dP = product1; + dP += 2; + w += dw; + } + dstPtr += dstPitch; + } +} diff --git a/snesfilter/Makefile b/snesfilter/Makefile new file mode 100644 index 00000000..089b86ee --- /dev/null +++ b/snesfilter/Makefile @@ -0,0 +1,89 @@ +include nall/Makefile + +qtlibs := QtCore QtGui +include nall/qt/Makefile + +c := $(compiler) -std=gnu99 +cpp := $(subst cc,++,$(compiler)) -std=gnu++0x +flags := -O3 -I. -Iobj -fomit-frame-pointer $(qtinc) +link := + +ifeq ($(platform),x) + flags := -fPIC -fopenmp $(flags) + link += -s -fopenmp -lpthread -lgomp +else ifeq ($(platform),osx) + flags := -fPIC -fopenmp $(flags) + link += -fopenmp -lpthread -lgomp +else ifeq ($(platform),win) + flags := -fopenmp $(flags) + link += -fopenmp -lpthread +endif + +objects := snesfilter + +compile = \ + $(strip \ + $(if $(filter %.c,$<), \ + $(c) $(flags) $1 -c $< -o $@, \ + $(if $(filter %.cpp,$<), \ + $(cpp) $(flags) $1 -c $< -o $@ \ + ) \ + ) \ + ) + +%.o: $<; $(call compile) + +all: build; + +objects := $(patsubst %,obj/%.o,$(objects)) +moc_headers := $(call rwildcard,./,%.moc.hpp) +moc_objects := $(foreach f,$(moc_headers),obj/$(notdir $(patsubst %.moc.hpp,%.moc,$f))) + +# automatically run moc on all .moc.hpp (MOC header) files +%.moc: $<; $(moc) -i $< -o $@ + +# automatically generate %.moc build rules +__list = $(moc_headers) +$(foreach f,$(moc_objects), \ + $(eval __file = $(word 1,$(__list))) \ + $(eval __list = $(wordlist 2,$(words $(__list)),$(__list))) \ + $(eval $f: $(__file)) \ +) + +################## +### snesfilter ### +################## + +obj/snesfilter.o: snesfilter.cpp * + +############### +### targets ### +############### + +build: $(moc_objects) $(objects) +ifeq ($(platform),x) + ar rcs libsnesfilter.a $(objects) + $(cpp) $(link) -o libsnesfilter.so -shared -Wl,-soname,libsnesfilter.so.1 $(objects) $(qtlib) +else ifeq ($(platform),osx) + ar rcs libsnesfilter.a $(objects) + $(cpp) $(link) -o libsnesfilter.dylib -shared -dynamiclib $(objects) $(qtlib) +else ifeq ($(platform),win) + $(cpp) $(link) -o snesfilter.dll -shared -Wl,--out-implib,libsnesfilter.a $(objects) $(qtlib) +endif + +install: +ifeq ($(platform),x) + install -D -m 755 libsnesfilter.a $(DESTDIR)$(prefix)/lib + install -D -m 755 libsnesfilter.so $(DESTDIR)$(prefix)/lib + ldconfig -n $(DESTDIR)$(prefix)/lib +else ifeq ($(platform),osx) + cp libsnesfilter.dylib /usr/local/lib/libsnesfilter.dylib +endif + +clean: + -@$(call delete,obj/*.o) + -@$(call delete,obj/*.moc) + -@$(call delete,libsnesfilter.a) + -@$(call delete,libsnesfilter.so) + -@$(call delete,libsnesfilter.dylib) + -@$(call delete,snesfilter.dll) diff --git a/snesfilter/cc.bat b/snesfilter/cc.bat new file mode 100644 index 00000000..8359a530 --- /dev/null +++ b/snesfilter/cc.bat @@ -0,0 +1,2 @@ +@mingw32-make +@pause \ No newline at end of file diff --git a/snesfilter/clean.bat b/snesfilter/clean.bat new file mode 100644 index 00000000..d8bb7e0b --- /dev/null +++ b/snesfilter/clean.bat @@ -0,0 +1 @@ +@mingw32-make clean diff --git a/snesfilter/direct/direct.cpp b/snesfilter/direct/direct.cpp new file mode 100644 index 00000000..7d582b16 --- /dev/null +++ b/snesfilter/direct/direct.cpp @@ -0,0 +1,23 @@ +#include "direct.hpp" + +void DirectFilter::size(unsigned &outwidth, unsigned &outheight, unsigned width, unsigned height) { + outwidth = width; + outheight = height; +} + +void DirectFilter::render( + uint32_t *output, unsigned outpitch, const uint16_t *input, unsigned pitch, + unsigned width, unsigned height +) { + pitch >>= 1; + outpitch >>= 2; + + for(unsigned y = 0; y < height; y++) { + for(unsigned x = 0; x < width; x++) { + uint16_t p = *input++; + *output++ = colortable[p]; + } + input += pitch - width; + output += outpitch - width; + } +} diff --git a/snesfilter/direct/direct.hpp b/snesfilter/direct/direct.hpp new file mode 100644 index 00000000..588a76e4 --- /dev/null +++ b/snesfilter/direct/direct.hpp @@ -0,0 +1,5 @@ +class DirectFilter { +public: + void size(unsigned&, unsigned&, unsigned, unsigned); + void render(uint32_t*, unsigned, const uint16_t*, unsigned, unsigned, unsigned); +} filter_direct; diff --git a/snesfilter/hq2x/hq2x.cpp b/snesfilter/hq2x/hq2x.cpp new file mode 100644 index 00000000..6e38feb0 --- /dev/null +++ b/snesfilter/hq2x/hq2x.cpp @@ -0,0 +1,195 @@ +//HQ2x filter +//authors: byuu and blargg +//license: public domain +// +//note: this is a clean reimplementation of the original HQ2x filter, which was +//written by Maxim Stepin (MaxSt). it is not 100% identical, but very similar. + +#include "hq2x.hpp" + +void HQ2xFilter::size(unsigned &outwidth, unsigned &outheight, unsigned width, unsigned height) { + if(width > 256 || height > 240) return filter_direct.size(outwidth, outheight, width, height); + outwidth = width * 2; + outheight = height * 2; +} + +void HQ2xFilter::render( + uint32_t *output, unsigned outpitch, + const uint16_t *input, unsigned pitch, unsigned width, unsigned height +) { + if(width > 256 || height > 240) { + filter_direct.render(output, outpitch, input, pitch, width, height); + return; + } + + pitch >>= 1; + outpitch >>= 2; + + #pragma omp parallel for + for(unsigned y = 0; y < height; y++) { + const uint16_t *in = input + y * pitch; + uint32_t *out0 = output + y * outpitch * 2; + uint32_t *out1 = output + y * outpitch * 2 + outpitch; + + int prevline = (y == 0 ? 0 : pitch); + int nextline = (y == height - 1 ? 0 : pitch); + + in++; + *out0++ = 0; *out0++ = 0; + *out1++ = 0; *out1++ = 0; + + for(unsigned x = 1; x < 256 - 1; x++) { + uint16_t A = *(in - prevline - 1); + uint16_t B = *(in - prevline + 0); + uint16_t C = *(in - prevline + 1); + uint16_t D = *(in - 1); + uint16_t E = *(in + 0); + uint16_t F = *(in + 1); + uint16_t G = *(in + nextline - 1); + uint16_t H = *(in + nextline + 0); + uint16_t I = *(in + nextline + 1); + uint32_t e = yuvTable[E] + diff_offset; + + uint8_t pattern; + pattern = diff(e, A) << 0; + pattern |= diff(e, B) << 1; + pattern |= diff(e, C) << 2; + pattern |= diff(e, D) << 3; + pattern |= diff(e, F) << 4; + pattern |= diff(e, G) << 5; + pattern |= diff(e, H) << 6; + pattern |= diff(e, I) << 7; + + *(out0 + 0) = colortable[blend(hqTable[pattern], E, A, B, D, F, H)]; pattern = rotate[pattern]; + *(out0 + 1) = colortable[blend(hqTable[pattern], E, C, F, B, H, D)]; pattern = rotate[pattern]; + *(out1 + 1) = colortable[blend(hqTable[pattern], E, I, H, F, D, B)]; pattern = rotate[pattern]; + *(out1 + 0) = colortable[blend(hqTable[pattern], E, G, D, H, B, F)]; + + in++; + out0 += 2; + out1 += 2; + } + + in++; + *out0++ = 0; *out0++ = 0; + *out1++ = 0; *out1++ = 0; + } +} + +HQ2xFilter::HQ2xFilter() { + yuvTable = new uint32_t[32768]; + + for(unsigned i = 0; i < 32768; i++) { + uint8_t R = (i >> 0) & 31; + uint8_t G = (i >> 5) & 31; + uint8_t B = (i >> 10) & 31; + + //bgr555->bgr888 + double r = (R << 3) | (R >> 2); + double g = (G << 3) | (G >> 2); + double b = (B << 3) | (B >> 2); + + //bgr888->yuv888 + double y = (r + g + b) * (0.25f * (63.5f / 48.0f)); + double u = ((r - b) * 0.25f + 128.0f) * (7.5f / 7.0f); + double v = ((g * 2.0f - r - b) * 0.125f + 128.0f) * (7.5f / 6.0f); + + yuvTable[i] = ((unsigned)y << 21) + ((unsigned)u << 11) + ((unsigned)v); + } + + for(unsigned n = 0; n < 256; n++) { + rotate[n] = ((n >> 2) & 0x11) | ((n << 2) & 0x88) + | ((n & 0x01) << 5) | ((n & 0x08) << 3) + | ((n & 0x10) >> 3) | ((n & 0x80) >> 5); + } +} + +HQ2xFilter::~HQ2xFilter() { + delete[] yuvTable; +} + +bool HQ2xFilter::same(uint16_t x, uint16_t y) { + return !((yuvTable[x] - yuvTable[y] + diff_offset) & diff_mask); +} + +bool HQ2xFilter::diff(uint32_t x, uint16_t y) { + return ((x - yuvTable[y]) & diff_mask); +} + +void HQ2xFilter::grow(uint32_t &n) { n |= n << 16; n &= 0x03e07c1f; } +uint16_t HQ2xFilter::pack(uint32_t n) { n &= 0x03e07c1f; return n | (n >> 16); } + +uint16_t HQ2xFilter::blend1(uint32_t A, uint32_t B) { + grow(A); grow(B); + A = (A * 3 + B) >> 2; + return pack(A); +} + +uint16_t HQ2xFilter::blend2(uint32_t A, uint32_t B, uint32_t C) { + grow(A); grow(B); grow(C); + return pack((A * 2 + B + C) >> 2); +} + +uint16_t HQ2xFilter::blend3(uint32_t A, uint32_t B, uint32_t C) { + grow(A); grow(B); grow(C); + return pack((A * 5 + B * 2 + C) >> 3); +} + +uint16_t HQ2xFilter::blend4(uint32_t A, uint32_t B, uint32_t C) { + grow(A); grow(B); grow(C); + return pack((A * 6 + B + C) >> 3); +} + +uint16_t HQ2xFilter::blend5(uint32_t A, uint32_t B, uint32_t C) { + grow(A); grow(B); grow(C); + return pack((A * 2 + (B + C) * 3) >> 3); +} + +uint16_t HQ2xFilter::blend6(uint32_t A, uint32_t B, uint32_t C) { + grow(A); grow(B); grow(C); + return pack((A * 14 + B + C) >> 4); +} + +uint16_t HQ2xFilter::blend(unsigned rule, uint16_t E, uint16_t A, uint16_t B, uint16_t D, uint16_t F, uint16_t H) { + switch(rule) { default: + case 0: return E; + case 1: return blend1(E, A); + case 2: return blend1(E, D); + case 3: return blend1(E, B); + case 4: return blend2(E, D, B); + case 5: return blend2(E, A, B); + case 6: return blend2(E, A, D); + case 7: return blend3(E, B, D); + case 8: return blend3(E, D, B); + case 9: return blend4(E, D, B); + case 10: return blend5(E, D, B); + case 11: return blend6(E, D, B); + case 12: return same(B, D) ? blend2(E, D, B) : E; + case 13: return same(B, D) ? blend5(E, D, B) : E; + case 14: return same(B, D) ? blend6(E, D, B) : E; + case 15: return same(B, D) ? blend2(E, D, B) : blend1(E, A); + case 16: return same(B, D) ? blend4(E, D, B) : blend1(E, A); + case 17: return same(B, D) ? blend5(E, D, B) : blend1(E, A); + case 18: return same(B, F) ? blend3(E, B, D) : blend1(E, D); + case 19: return same(D, H) ? blend3(E, D, B) : blend1(E, B); + } +} + +const uint8_t HQ2xFilter::hqTable[256] = { + 4, 4, 6, 2, 4, 4, 6, 2, 5, 3, 15, 12, 5, 3, 17, 13, + 4, 4, 6, 18, 4, 4, 6, 18, 5, 3, 12, 12, 5, 3, 1, 12, + 4, 4, 6, 2, 4, 4, 6, 2, 5, 3, 17, 13, 5, 3, 16, 14, + 4, 4, 6, 18, 4, 4, 6, 18, 5, 3, 16, 12, 5, 3, 1, 14, + 4, 4, 6, 2, 4, 4, 6, 2, 5, 19, 12, 12, 5, 19, 16, 12, + 4, 4, 6, 2, 4, 4, 6, 2, 5, 3, 16, 12, 5, 3, 16, 12, + 4, 4, 6, 2, 4, 4, 6, 2, 5, 19, 1, 12, 5, 19, 1, 14, + 4, 4, 6, 2, 4, 4, 6, 18, 5, 3, 16, 12, 5, 19, 1, 14, + 4, 4, 6, 2, 4, 4, 6, 2, 5, 3, 15, 12, 5, 3, 17, 13, + 4, 4, 6, 2, 4, 4, 6, 2, 5, 3, 16, 12, 5, 3, 16, 12, + 4, 4, 6, 2, 4, 4, 6, 2, 5, 3, 17, 13, 5, 3, 16, 14, + 4, 4, 6, 2, 4, 4, 6, 2, 5, 3, 16, 13, 5, 3, 1, 14, + 4, 4, 6, 2, 4, 4, 6, 2, 5, 3, 16, 12, 5, 3, 16, 13, + 4, 4, 6, 2, 4, 4, 6, 2, 5, 3, 16, 12, 5, 3, 1, 12, + 4, 4, 6, 2, 4, 4, 6, 2, 5, 3, 16, 12, 5, 3, 1, 14, + 4, 4, 6, 2, 4, 4, 6, 2, 5, 3, 1, 12, 5, 3, 1, 14, +}; diff --git a/snesfilter/hq2x/hq2x.hpp b/snesfilter/hq2x/hq2x.hpp new file mode 100644 index 00000000..f48c47cd --- /dev/null +++ b/snesfilter/hq2x/hq2x.hpp @@ -0,0 +1,30 @@ +class HQ2xFilter { +public: + void size(unsigned&, unsigned&, unsigned, unsigned); + void render(uint32_t*, unsigned, const uint16_t*, unsigned, unsigned, unsigned); + + HQ2xFilter(); + ~HQ2xFilter(); + +private: + enum { + diff_offset = (0x440 << 21) + (0x207 << 11) + 0x407, + diff_mask = (0x380 << 21) + (0x1f0 << 11) + 0x3f0, + }; + + static const uint8_t hqTable[256]; + uint32_t *yuvTable; + uint8_t rotate[256]; + + alwaysinline bool same(uint16_t x, uint16_t y); + alwaysinline bool diff(uint32_t x, uint16_t y); + alwaysinline void grow(uint32_t &n); + alwaysinline uint16_t pack(uint32_t n); + alwaysinline uint16_t blend1(uint32_t A, uint32_t B); + alwaysinline uint16_t blend2(uint32_t A, uint32_t B, uint32_t C); + alwaysinline uint16_t blend3(uint32_t A, uint32_t B, uint32_t C); + alwaysinline uint16_t blend4(uint32_t A, uint32_t B, uint32_t C); + alwaysinline uint16_t blend5(uint32_t A, uint32_t B, uint32_t C); + alwaysinline uint16_t blend6(uint32_t A, uint32_t B, uint32_t C); + alwaysinline uint16_t blend(unsigned rule, uint16_t E, uint16_t A, uint16_t B, uint16_t D, uint16_t F, uint16_t H); +} filter_hq2x; diff --git a/snesfilter/lq2x/lq2x.cpp b/snesfilter/lq2x/lq2x.cpp new file mode 100644 index 00000000..560913e7 --- /dev/null +++ b/snesfilter/lq2x/lq2x.cpp @@ -0,0 +1,53 @@ +#include "lq2x.hpp" + +void LQ2xFilter::size(unsigned &outwidth, unsigned &outheight, unsigned width, unsigned height) { + if(width > 256 || height > 240) return filter_direct.size(outwidth, outheight, width, height); + outwidth = width * 2; + outheight = height * 2; +} + +void LQ2xFilter::render( + uint32_t *output, unsigned outpitch, + const uint16_t *input, unsigned pitch, unsigned width, unsigned height +) { + if(width > 256 || height > 240) { + filter_direct.render(output, outpitch, input, pitch, width, height); + return; + } + + pitch >>= 1; + outpitch >>= 2; + + uint32_t *out0 = output; + uint32_t *out1 = output + outpitch; + + for(unsigned y = 0; y < height; y++) { + int prevline = (y == 0 ? 0 : pitch); + int nextline = (y == height - 1 ? 0 : pitch); + + for(unsigned x = 0; x < width; x++) { + uint16_t A = *(input - prevline); + uint16_t B = (x > 0) ? *(input - 1) : *input; + uint16_t C = *input; + uint16_t D = (x < 255) ? *(input + 1) : *input; + uint16_t E = *(input++ + nextline); + uint32_t c = colortable[C]; + + if(A != E && B != D) { + *out0++ = (A == B ? colortable[C + A - ((C ^ A) & 0x0421) >> 1] : c); + *out0++ = (A == D ? colortable[C + A - ((C ^ A) & 0x0421) >> 1] : c); + *out1++ = (E == B ? colortable[C + E - ((C ^ E) & 0x0421) >> 1] : c); + *out1++ = (E == D ? colortable[C + E - ((C ^ E) & 0x0421) >> 1] : c); + } else { + *out0++ = c; + *out0++ = c; + *out1++ = c; + *out1++ = c; + } + } + + input += pitch - width; + out0 += outpitch + outpitch - 512; + out1 += outpitch + outpitch - 512; + } +} diff --git a/snesfilter/lq2x/lq2x.hpp b/snesfilter/lq2x/lq2x.hpp new file mode 100644 index 00000000..5b1a0e41 --- /dev/null +++ b/snesfilter/lq2x/lq2x.hpp @@ -0,0 +1,5 @@ +class LQ2xFilter { +public: + void size(unsigned&, unsigned&, unsigned, unsigned); + void render(uint32_t*, unsigned, const uint16_t*, unsigned, unsigned, unsigned); +} filter_lq2x; diff --git a/snesfilter/nall/Makefile b/snesfilter/nall/Makefile new file mode 100644 index 00000000..8149bf15 --- /dev/null +++ b/snesfilter/nall/Makefile @@ -0,0 +1,107 @@ +# Makefile +# author: byuu +# license: public domain + +[A-Z] = A B C D E F G H I J K L M N O P Q R S T U V W X Y Z +[a-z] = a b c d e f g h i j k l m n o p q r s t u v w x y z +[0-9] = 0 1 2 3 4 5 6 7 8 9 +[markup] = ` ~ ! @ \# $$ % ^ & * ( ) - _ = + [ { ] } \ | ; : ' " , < . > / ? +[all] = $([A-Z]) $([a-z]) $([0-9]) $([markup]) +[space] := +[space] += + +##### +# platform detection +##### + +ifeq ($(platform),) + uname := $(shell uname -a) + ifeq ($(uname),) + platform := win + delete = del $(subst /,\,$1) + else ifneq ($(findstring Darwin,$(uname)),) + platform := osx + delete = rm -f $1 + else + platform := x + delete = rm -f $1 + endif +endif + +ifeq ($(compiler),) + ifeq ($(platform),osx) + compiler := gcc-4.2 + else + compiler := gcc + endif +endif + +ifeq ($(prefix),) + prefix := /usr/local +endif + +##### +# function rwildcard(directory, pattern) +##### +rwildcard = \ + $(strip \ + $(filter $(if $2,$2,%), \ + $(foreach f, \ + $(wildcard $1*), \ + $(eval t = $(call rwildcard,$f/)) \ + $(if $t,$t,$f) \ + ) \ + ) \ + ) + +##### +# function strtr(source, from, to) +##### +strtr = \ + $(eval __temp := $1) \ + $(strip \ + $(foreach c, \ + $(join $(addsuffix :,$2),$3), \ + $(eval __temp := \ + $(subst $(word 1,$(subst :, ,$c)),$(word 2,$(subst :, ,$c)),$(__temp)) \ + ) \ + ) \ + $(__temp) \ + ) + +##### +# function strupper(source) +##### +strupper = $(call strtr,$1,$([a-z]),$([A-Z])) + +##### +# function strlower(source) +##### +strlower = $(call strtr,$1,$([A-Z]),$([a-z])) + +##### +# function strlen(source) +##### +strlen = \ + $(eval __temp := $(subst $([space]),_,$1)) \ + $(words \ + $(strip \ + $(foreach c, \ + $([all]), \ + $(eval __temp := \ + $(subst $c,$c ,$(__temp)) \ + ) \ + ) \ + $(__temp) \ + ) \ + ) + +##### +# function streq(source) +##### +streq = $(if $(filter-out xx,x$(subst $1,,$2)$(subst $2,,$1)x),,1) + +##### +# function strne(source) +##### +strne = $(if $(filter-out xx,x$(subst $1,,$2)$(subst $2,,$1)x),1,) diff --git a/snesfilter/nall/algorithm.hpp b/snesfilter/nall/algorithm.hpp new file mode 100644 index 00000000..cdc48dcf --- /dev/null +++ b/snesfilter/nall/algorithm.hpp @@ -0,0 +1,23 @@ +#ifndef NALL_ALGORITHM_HPP +#define NALL_ALGORITHM_HPP + +#undef min +#undef max + +namespace nall { + template T min(const T &t, const U &u) { + return t < u ? t : u; + } + + template T max(const T &t, const U &u) { + return t > u ? t : u; + } + + //pseudo-random number generator + inline unsigned prng() { + static unsigned n = 0; + return n = (n >> 1) ^ (((n & 1) - 1) & 0xedb88320); + } +} + +#endif diff --git a/snesfilter/nall/any.hpp b/snesfilter/nall/any.hpp new file mode 100644 index 00000000..b31cff3c --- /dev/null +++ b/snesfilter/nall/any.hpp @@ -0,0 +1,74 @@ +#ifndef NALL_ANY_HPP +#define NALL_ANY_HPP + +#include +#include +#include + +namespace nall { + class any { + public: + bool empty() const { return container; } + const std::type_info& type() const { return container ? container->type() : typeid(void); } + + template any& operator=(const T& value_) { + typedef typename static_if< + std::is_array::value, + typename std::remove_extent::type>::type*, + T + >::type auto_t; + + if(type() == typeid(auto_t)) { + static_cast*>(container)->value = (auto_t)value_; + } else { + if(container) delete container; + container = new holder((auto_t)value_); + } + + return *this; + } + + any() : container(0) {} + template any(const T& value_) : container(0) { operator=(value_); } + + private: + struct placeholder { + virtual const std::type_info& type() const = 0; + } *container; + + template struct holder : placeholder { + T value; + const std::type_info& type() const { return typeid(T); } + holder(const T& value_) : value(value_) {} + }; + + template friend T any_cast(any&); + template friend T any_cast(const any&); + template friend T* any_cast(any*); + template friend const T* any_cast(const any*); + }; + + template T any_cast(any &value) { + typedef typename std::remove_reference::type nonref; + if(value.type() != typeid(nonref)) throw; + return static_cast*>(value.container)->value; + } + + template T any_cast(const any &value) { + typedef const typename std::remove_reference::type nonref; + if(value.type() != typeid(nonref)) throw; + return static_cast*>(value.container)->value; + } + + template T* any_cast(any *value) { + if(!value || value->type() != typeid(T)) return 0; + return &static_cast*>(value->container)->value; + } + + template const T* any_cast(const any *value) { + if(!value || value->type() != typeid(T)) return 0; + return &static_cast*>(value->container)->value; + } +} + +#endif diff --git a/snesfilter/nall/array.hpp b/snesfilter/nall/array.hpp new file mode 100644 index 00000000..c1d33fd1 --- /dev/null +++ b/snesfilter/nall/array.hpp @@ -0,0 +1,120 @@ +#ifndef NALL_ARRAY_HPP +#define NALL_ARRAY_HPP + +#include +#include +#include +#include +#include +#include +#include + +namespace nall { + //dynamic vector array + //neither constructor nor destructor is ever invoked; + //thus, this should only be used for POD objects. + template class array { + protected: + T *pool; + unsigned poolsize, buffersize; + + public: + unsigned size() const { return buffersize; } + unsigned capacity() const { return poolsize; } + + void reset() { + if(pool) free(pool); + pool = 0; + poolsize = 0; + buffersize = 0; + } + + void reserve(unsigned newsize) { + if(newsize == poolsize) return; + + pool = (T*)realloc(pool, newsize * sizeof(T)); + poolsize = newsize; + buffersize = min(buffersize, newsize); + } + + void resize(unsigned newsize) { + if(newsize > poolsize) reserve(bit::round(newsize)); //round reserve size up to power of 2 + buffersize = newsize; + } + + T* get(unsigned minsize = 0) { + if(minsize > buffersize) resize(minsize); + if(minsize > buffersize) throw "array[] out of bounds"; + return pool; + } + + void add(const T data) { + operator[](buffersize) = data; + } + + signed find(const T data) { + for(unsigned i = 0; i < size(); i++) if(pool[i] == data) return i; + return -1; //not found + } + + void clear() { + memset(pool, 0, buffersize * sizeof(T)); + } + + array() : pool(0), poolsize(0), buffersize(0) { + } + + array(std::initializer_list list) : pool(0), poolsize(0), buffersize(0) { + for(const T *p = list.begin(); p != list.end(); ++p) add(*p); + } + + ~array() { + reset(); + } + + //copy + array& operator=(const array &source) { + if(pool) free(pool); + buffersize = source.buffersize; + poolsize = source.poolsize; + pool = (T*)malloc(sizeof(T) * poolsize); //allocate entire pool size, + memcpy(pool, source.pool, sizeof(T) * buffersize); //... but only copy used pool objects + return *this; + } + + array(const array &source) : pool(0), poolsize(0), buffersize(0) { + operator=(source); + } + + //move + array& operator=(array &&source) { + if(pool) free(pool); + pool = source.pool; + poolsize = source.poolsize; + buffersize = source.buffersize; + source.pool = 0; + source.reset(); + return *this; + } + + array(array &&source) : pool(0), poolsize(0), buffersize(0) { + operator=(std::move(source)); + } + + //index + inline T& operator[](unsigned index) { + if(index >= buffersize) resize(index + 1); + if(index >= buffersize) throw "array[] out of bounds"; + return pool[index]; + } + + inline const T& operator[](unsigned index) const { + if(index >= buffersize) throw "array[] out of bounds"; + return pool[index]; + } + }; + + template struct has_size> { enum { value = true }; }; +} + +#endif diff --git a/snesfilter/nall/base64.hpp b/snesfilter/nall/base64.hpp new file mode 100644 index 00000000..e41c87b7 --- /dev/null +++ b/snesfilter/nall/base64.hpp @@ -0,0 +1,90 @@ +#ifndef NALL_BASE64_HPP +#define NALL_BASE64_HPP + +#include +#include + +namespace nall { + class base64 { + public: + static bool encode(char *&output, const uint8_t* input, unsigned inlength) { + output = new char[inlength * 8 / 6 + 6](); + + unsigned i = 0, o = 0; + while(i < inlength) { + switch(i % 3) { + case 0: { + output[o++] = enc(input[i] >> 2); + output[o] = enc((input[i] & 3) << 4); + } break; + + case 1: { + uint8_t prev = dec(output[o]); + output[o++] = enc(prev + (input[i] >> 4)); + output[o] = enc((input[i] & 15) << 2); + } break; + + case 2: { + uint8_t prev = dec(output[o]); + output[o++] = enc(prev + (input[i] >> 6)); + output[o++] = enc(input[i] & 63); + } break; + } + + i++; + } + + return true; + } + + static bool decode(uint8_t *&output, unsigned &outlength, const char *input) { + unsigned inlength = strlen(input), infix = 0; + output = new uint8_t[inlength](); + + unsigned i = 0, o = 0; + while(i < inlength) { + uint8_t x = dec(input[i]); + + switch(i++ & 3) { + case 0: { + output[o] = x << 2; + } break; + + case 1: { + output[o++] |= x >> 4; + output[o] = (x & 15) << 4; + } break; + + case 2: { + output[o++] |= x >> 2; + output[o] = (x & 3) << 6; + } break; + + case 3: { + output[o++] |= x; + } break; + } + } + + outlength = o; + return true; + } + + private: + static char enc(uint8_t n) { + static char lookup_table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; + return lookup_table[n & 63]; + } + + static uint8_t dec(char n) { + if(n >= 'A' && n <= 'Z') return n - 'A'; + if(n >= 'a' && n <= 'z') return n - 'a' + 26; + if(n >= '0' && n <= '9') return n - '0' + 52; + if(n == '-') return 62; + if(n == '_') return 63; + return 0; + } + }; +} + +#endif diff --git a/snesfilter/nall/bit.hpp b/snesfilter/nall/bit.hpp new file mode 100644 index 00000000..169fc144 --- /dev/null +++ b/snesfilter/nall/bit.hpp @@ -0,0 +1,51 @@ +#ifndef NALL_BIT_HPP +#define NALL_BIT_HPP + +namespace nall { + template inline unsigned uclamp(const unsigned x) { + enum { y = (1U << bits) - 1 }; + return y + ((x - y) & -(x < y)); //min(x, y); + } + + template inline unsigned uclip(const unsigned x) { + enum { m = (1U << bits) - 1 }; + return (x & m); + } + + template inline signed sclamp(const signed x) { + enum { b = 1U << (bits - 1), m = (1U << (bits - 1)) - 1 }; + return (x > m) ? m : (x < -b) ? -b : x; + } + + template inline signed sclip(const signed x) { + enum { b = 1U << (bits - 1), m = (1U << bits) - 1 }; + return ((x & m) ^ b) - b; + } + + namespace bit { + //lowest(0b1110) == 0b0010 + template inline T lowest(const T x) { + return x & -x; + } + + //clear_lowest(0b1110) == 0b1100 + template inline T clear_lowest(const T x) { + return x & (x - 1); + } + + //set_lowest(0b0101) == 0b0111 + template inline T set_lowest(const T x) { + return x | (x + 1); + } + + //round up to next highest single bit: + //round(15) == 16, round(16) == 16, round(17) == 32 + inline unsigned round(unsigned x) { + if((x & (x - 1)) == 0) return x; + while(x & (x - 1)) x &= x - 1; + return x << 1; + } + } +} + +#endif diff --git a/snesfilter/nall/concept.hpp b/snesfilter/nall/concept.hpp new file mode 100644 index 00000000..2949cd5e --- /dev/null +++ b/snesfilter/nall/concept.hpp @@ -0,0 +1,15 @@ +#ifndef NALL_CONCEPT_HPP +#define NALL_CONCEPT_HPP + +namespace nall { + //unsigned count() const; + template struct has_count { enum { value = false }; }; + + //unsigned length() const; + template struct has_length { enum { value = false }; }; + + //unsigned size() const; + template struct has_size { enum { value = false }; }; +} + +#endif diff --git a/snesfilter/nall/config.hpp b/snesfilter/nall/config.hpp new file mode 100644 index 00000000..c713d0b0 --- /dev/null +++ b/snesfilter/nall/config.hpp @@ -0,0 +1,123 @@ +#ifndef NALL_CONFIG_HPP +#define NALL_CONFIG_HPP + +#include +#include +#include + +namespace nall { + namespace configuration_traits { + template struct is_boolean { enum { value = false }; }; + template<> struct is_boolean { enum { value = true }; }; + + template struct is_signed { enum { value = false }; }; + template<> struct is_signed { enum { value = true }; }; + + template struct is_unsigned { enum { value = false }; }; + template<> struct is_unsigned { enum { value = true }; }; + + template struct is_double { enum { value = false }; }; + template<> struct is_double { enum { value = true }; }; + + template struct is_string { enum { value = false }; }; + template<> struct is_string { enum { value = true }; }; + } + + class configuration { + public: + enum type_t { boolean_t, signed_t, unsigned_t, double_t, string_t, unknown_t }; + struct item_t { + uintptr_t data; + string name; + string desc; + type_t type; + + string get() const { + switch(type) { + case boolean_t: return string() << *(bool*)data; + case signed_t: return string() << *(signed*)data; + case unsigned_t: return string() << *(unsigned*)data; + case double_t: return string() << *(double*)data; + case string_t: return string() << "\"" << *(string*)data << "\""; + } + return "???"; + } + + void set(string s) { + switch(type) { + case boolean_t: *(bool*)data = (s == "true"); break; + case signed_t: *(signed*)data = strsigned(s); break; + case unsigned_t: *(unsigned*)data = strunsigned(s); break; + case double_t: *(double*)data = strdouble(s); break; + case string_t: trim(s, "\""); *(string*)data = s; break; + } + } + }; + linear_vector list; + + template + void attach(T &data, const char *name, const char *desc = "") { + unsigned n = list.size(); + list[n].data = (uintptr_t)&data; + list[n].name = name; + list[n].desc = desc; + + if(configuration_traits::is_boolean::value) list[n].type = boolean_t; + else if(configuration_traits::is_signed::value) list[n].type = signed_t; + else if(configuration_traits::is_unsigned::value) list[n].type = unsigned_t; + else if(configuration_traits::is_double::value) list[n].type = double_t; + else if(configuration_traits::is_string::value) list[n].type = string_t; + else list[n].type = unknown_t; + } + + virtual bool load(const char *filename) { + string data; + if(data.readfile(filename) == true) { + data.replace("\r", ""); + lstring line; + line.split("\n", data); + + for(unsigned i = 0; i < line.size(); i++) { + if(auto position = qstrpos(line[i], "#")) line[i][position()] = 0; + if(!qstrpos(line[i], " = ")) continue; + + lstring part; + part.qsplit(" = ", line[i]); + trim(part[0]); + trim(part[1]); + + for(unsigned n = 0; n < list.size(); n++) { + if(part[0] == list[n].name) { + list[n].set(part[1]); + break; + } + } + } + + return true; + } else { + return false; + } + } + + virtual bool save(const char *filename) const { + file fp; + if(fp.open(filename, file::mode_write)) { + for(unsigned i = 0; i < list.size(); i++) { + string output; + output << list[i].name << " = " << list[i].get(); + if(list[i].desc != "") output << " # " << list[i].desc; + output << "\r\n"; + fp.print(output); + } + + fp.close(); + return true; + } else { + return false; + } + } + }; +} + +#endif diff --git a/snesfilter/nall/crc32.hpp b/snesfilter/nall/crc32.hpp new file mode 100644 index 00000000..ad36fbf6 --- /dev/null +++ b/snesfilter/nall/crc32.hpp @@ -0,0 +1,66 @@ +#ifndef NALL_CRC32_HPP +#define NALL_CRC32_HPP + +#include + +namespace nall { + const uint32_t crc32_table[256] = { + 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, + 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, + 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2, + 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, + 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, + 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, + 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c, + 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, + 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, + 0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, + 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106, + 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, + 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, + 0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, + 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, + 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, + 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, + 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, + 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, + 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, + 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, + 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, + 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84, + 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, + 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, + 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, + 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e, + 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, + 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, + 0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, + 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28, + 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, + 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, + 0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, + 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242, + 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, + 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, + 0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, + 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, + 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, + 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, + 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, + 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d + }; + + inline uint32_t crc32_adjust(uint32_t crc32, uint8_t input) { + return ((crc32 >> 8) & 0x00ffffff) ^ crc32_table[(crc32 ^ input) & 0xff]; + } + + inline uint32_t crc32_calculate(const uint8_t *data, unsigned length) { + uint32_t crc32 = ~0; + for(unsigned i = 0; i < length; i++) { + crc32 = crc32_adjust(crc32, data[i]); + } + return ~crc32; + } +} + +#endif diff --git a/snesfilter/nall/detect.hpp b/snesfilter/nall/detect.hpp new file mode 100644 index 00000000..b4991aaf --- /dev/null +++ b/snesfilter/nall/detect.hpp @@ -0,0 +1,30 @@ +#ifndef NALL_DETECT_HPP +#define NALL_DETECT_HPP + +/* Compiler detection */ + +#if defined(__GNUC__) + #define COMPILER_GCC +#elif defined(_MSC_VER) + #define COMPILER_VISUALC +#endif + +/* Platform detection */ + +#if defined(_WIN32) + #define PLATFORM_WIN +#elif defined(__APPLE__) + #define PLATFORM_OSX +#elif defined(linux) || defined(__sun__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) + #define PLATFORM_X +#endif + +/* Endian detection */ + +#if defined(__i386__) || defined(__amd64__) || defined(_M_IX86) || defined(_M_AMD64) + #define ARCH_LSB +#elif defined(__powerpc__) || defined(_M_PPC) || defined(__BIG_ENDIAN__) + #define ARCH_MSB +#endif + +#endif diff --git a/snesfilter/nall/dictionary.hpp b/snesfilter/nall/dictionary.hpp new file mode 100644 index 00000000..9e0a1620 --- /dev/null +++ b/snesfilter/nall/dictionary.hpp @@ -0,0 +1,75 @@ +#ifndef NALL_DICTIONARY_HPP +#define NALL_DICTIONARY_HPP + +#include +#include +#include + +namespace nall { + class dictionary { + public: + string operator[](const char *input) { + for(unsigned i = 0; i < index_input.size(); i++) { + if(index_input[i] == input) return index_output[i]; + } + + //no match, use input; remove input identifier, if one exists + if(strbegin(input, "{{")) { + if(auto pos = strpos(input, "}}")) { + string temp = substr(input, pos() + 2); + return temp; + } + } + + return input; + } + + bool import(const char *filename) { + string data; + if(data.readfile(filename) == false) return false; + ltrim_once(data, "\xef\xbb\xbf"); //remove UTF-8 marker, if it exists + data.replace("\r", ""); + + lstring line; + line.split("\n", data); + for(unsigned i = 0; i < line.size(); i++) { + lstring part; + //format: "Input" = "Output" + part.qsplit("=", line[i]); + if(part.size() != 2) continue; + + //remove whitespace + trim(part[0]); + trim(part[1]); + + //remove quotes + trim_once(part[0], "\""); + trim_once(part[1], "\""); + + unsigned n = index_input.size(); + index_input[n] = part[0]; + index_output[n] = part[1]; + } + + return true; + } + + void reset() { + index_input.reset(); + index_output.reset(); + } + + ~dictionary() { + reset(); + } + + dictionary& operator=(const dictionary&) = delete; + dictionary(const dictionary&) = delete; + + protected: + lstring index_input; + lstring index_output; + }; +} + +#endif diff --git a/snesfilter/nall/dl.hpp b/snesfilter/nall/dl.hpp new file mode 100644 index 00000000..22acf51f --- /dev/null +++ b/snesfilter/nall/dl.hpp @@ -0,0 +1,119 @@ +#ifndef NALL_DL_HPP +#define NALL_DL_HPP + +//dynamic linking support + +#include +#include +#include +#include + +#if defined(PLATFORM_X) || defined(PLATFORM_OSX) + #include +#elif defined(PLATFORM_WIN) + #include + #include +#endif + +namespace nall { + struct library { + bool opened() const { return handle; } + bool open(const char*); + void* sym(const char*); + void close(); + + library() : handle(0) {} + ~library() { close(); } + + library& operator=(const library&) = delete; + library(const library&) = delete; + + private: + uintptr_t handle; + }; + + #if defined(PLATFORM_X) + inline bool library::open(const char *name) { + if(handle) close(); + char *t = new char[strlen(name) + 256]; + strcpy(t, "lib"); + strcat(t, name); + strcat(t, ".so"); + handle = (uintptr_t)dlopen(t, RTLD_LAZY); + if(!handle) { + strcpy(t, "/usr/local/lib/lib"); + strcat(t, name); + strcat(t, ".so"); + handle = (uintptr_t)dlopen(t, RTLD_LAZY); + } + delete[] t; + return handle; + } + + inline void* library::sym(const char *name) { + if(!handle) return 0; + return dlsym((void*)handle, name); + } + + inline void library::close() { + if(!handle) return; + dlclose((void*)handle); + handle = 0; + } + #elif defined(PLATFORM_OSX) + inline bool library::open(const char *name) { + if(handle) close(); + char *t = new char[strlen(name) + 256]; + strcpy(t, "lib"); + strcat(t, name); + strcat(t, ".dylib"); + handle = (uintptr_t)dlopen(t, RTLD_LAZY); + if(!handle) { + strcpy(t, "/usr/local/lib/lib"); + strcat(t, name); + strcat(t, ".dylib"); + handle = (uintptr_t)dlopen(t, RTLD_LAZY); + } + delete[] t; + return handle; + } + + inline void* library::sym(const char *name) { + if(!handle) return 0; + return dlsym((void*)handle, name); + } + + inline void library::close() { + if(!handle) return; + dlclose((void*)handle); + handle = 0; + } + #elif defined(PLATFORM_WIN) + inline bool library::open(const char *name) { + if(handle) close(); + char *t = new char[strlen(name) + 8]; + strcpy(t, name); + strcat(t, ".dll"); + handle = (uintptr_t)LoadLibraryW(utf16_t(t)); + delete[] t; + return handle; + } + + inline void* library::sym(const char *name) { + if(!handle) return 0; + return (void*)GetProcAddress((HMODULE)handle, name); + } + + inline void library::close() { + if(!handle) return; + FreeLibrary((HMODULE)handle); + handle = 0; + } + #else + inline bool library::open(const char*) { return false; } + inline void* library::sym(const char*) { return 0; } + inline void library::close() {} + #endif +}; + +#endif diff --git a/snesfilter/nall/endian.hpp b/snesfilter/nall/endian.hpp new file mode 100644 index 00000000..40d15633 --- /dev/null +++ b/snesfilter/nall/endian.hpp @@ -0,0 +1,38 @@ +#ifndef NALL_ENDIAN_HPP +#define NALL_ENDIAN_HPP + +#if !defined(ARCH_MSB) + //little-endian: uint8_t[] { 0x01, 0x02, 0x03, 0x04 } == 0x04030201 + #define order_lsb2(a,b) a,b + #define order_lsb3(a,b,c) a,b,c + #define order_lsb4(a,b,c,d) a,b,c,d + #define order_lsb5(a,b,c,d,e) a,b,c,d,e + #define order_lsb6(a,b,c,d,e,f) a,b,c,d,e,f + #define order_lsb7(a,b,c,d,e,f,g) a,b,c,d,e,f,g + #define order_lsb8(a,b,c,d,e,f,g,h) a,b,c,d,e,f,g,h + #define order_msb2(a,b) b,a + #define order_msb3(a,b,c) c,b,a + #define order_msb4(a,b,c,d) d,c,b,a + #define order_msb5(a,b,c,d,e) e,d,c,b,a + #define order_msb6(a,b,c,d,e,f) f,e,d,c,b,a + #define order_msb7(a,b,c,d,e,f,g) g,f,e,d,c,b,a + #define order_msb8(a,b,c,d,e,f,g,h) h,g,f,e,d,c,b,a +#else + //big-endian: uint8_t[] { 0x01, 0x02, 0x03, 0x04 } == 0x01020304 + #define order_lsb2(a,b) b,a + #define order_lsb3(a,b,c) c,b,a + #define order_lsb4(a,b,c,d) d,c,b,a + #define order_lsb5(a,b,c,d,e) e,d,c,b,a + #define order_lsb6(a,b,c,d,e,f) f,e,d,c,b,a + #define order_lsb7(a,b,c,d,e,f,g) g,f,e,d,c,b,a + #define order_lsb8(a,b,c,d,e,f,g,h) h,g,f,e,d,c,b,a + #define order_msb2(a,b) a,b + #define order_msb3(a,b,c) a,b,c + #define order_msb4(a,b,c,d) a,b,c,d + #define order_msb5(a,b,c,d,e) a,b,c,d,e + #define order_msb6(a,b,c,d,e,f) a,b,c,d,e,f + #define order_msb7(a,b,c,d,e,f,g) a,b,c,d,e,f,g + #define order_msb8(a,b,c,d,e,f,g,h) a,b,c,d,e,f,g,h +#endif + +#endif diff --git a/snesfilter/nall/file.hpp b/snesfilter/nall/file.hpp new file mode 100644 index 00000000..4c8ca8ee --- /dev/null +++ b/snesfilter/nall/file.hpp @@ -0,0 +1,259 @@ +#ifndef NALL_FILE_HPP +#define NALL_FILE_HPP + +#include +#include + +#if !defined(_WIN32) + #include +#else + #include +#endif + +#include +#include +#include + +namespace nall { + inline FILE* fopen_utf8(const char *utf8_filename, const char *mode) { + #if !defined(_WIN32) + return fopen(utf8_filename, mode); + #else + return _wfopen(utf16_t(utf8_filename), utf16_t(mode)); + #endif + } + + class file { + public: + enum FileMode { mode_read, mode_write, mode_readwrite, mode_writeread }; + enum SeekMode { seek_absolute, seek_relative }; + + uint8_t read() { + if(!fp) return 0xff; //file not open + if(file_mode == mode_write) return 0xff; //reads not permitted + if(file_offset >= file_size) return 0xff; //cannot read past end of file + buffer_sync(); + return buffer[(file_offset++) & buffer_mask]; + } + + uintmax_t readl(unsigned length = 1) { + uintmax_t data = 0; + for(int i = 0; i < length; i++) { + data |= (uintmax_t)read() << (i << 3); + } + return data; + } + + uintmax_t readm(unsigned length = 1) { + uintmax_t data = 0; + while(length--) { + data <<= 8; + data |= read(); + } + return data; + } + + void read(uint8_t *buffer, unsigned length) { + while(length--) *buffer++ = read(); + } + + void write(uint8_t data) { + if(!fp) return; //file not open + if(file_mode == mode_read) return; //writes not permitted + buffer_sync(); + buffer[(file_offset++) & buffer_mask] = data; + buffer_dirty = true; + if(file_offset > file_size) file_size = file_offset; + } + + void writel(uintmax_t data, unsigned length = 1) { + while(length--) { + write(data); + data >>= 8; + } + } + + void writem(uintmax_t data, unsigned length = 1) { + for(int i = length - 1; i >= 0; i--) { + write(data >> (i << 3)); + } + } + + void write(const uint8_t *buffer, unsigned length) { + while(length--) write(*buffer++); + } + + void print(const char *string) { + if(!string) return; + while(*string) write(*string++); + } + + void flush() { + buffer_flush(); + fflush(fp); + } + + void seek(int offset, SeekMode mode = seek_absolute) { + if(!fp) return; //file not open + buffer_flush(); + + uintmax_t req_offset = file_offset; + switch(mode) { + case seek_absolute: req_offset = offset; break; + case seek_relative: req_offset += offset; break; + } + + if(req_offset < 0) req_offset = 0; //cannot seek before start of file + if(req_offset > file_size) { + if(file_mode == mode_read) { //cannot seek past end of file + req_offset = file_size; + } else { //pad file to requested location + file_offset = file_size; + while(file_size < req_offset) write(0x00); + } + } + + file_offset = req_offset; + } + + int offset() { + if(!fp) return -1; //file not open + return file_offset; + } + + int size() { + if(!fp) return -1; //file not open + return file_size; + } + + bool truncate(unsigned size) { + if(!fp) return false; //file not open + #if !defined(_WIN32) + return ftruncate(fileno(fp), size) == 0; + #else + return _chsize(fileno(fp), size) == 0; + #endif + } + + bool end() { + if(!fp) return true; //file not open + return file_offset >= file_size; + } + + static bool exists(const char *fn) { + #if !defined(_WIN32) + FILE *fp = fopen(fn, "rb"); + #else + FILE *fp = _wfopen(utf16_t(fn), L"rb"); + #endif + if(fp) { + fclose(fp); + return true; + } + return false; + } + + static unsigned size(const char *fn) { + #if !defined(_WIN32) + FILE *fp = fopen(fn, "rb"); + #else + FILE *fp = _wfopen(utf16_t(fn), L"rb"); + #endif + unsigned filesize = 0; + if(fp) { + fseek(fp, 0, SEEK_END); + filesize = ftell(fp); + fclose(fp); + } + return filesize; + } + + bool open() { + return fp; + } + + bool open(const char *fn, FileMode mode) { + if(fp) return false; + + switch(file_mode = mode) { + #if !defined(_WIN32) + case mode_read: fp = fopen(fn, "rb"); break; + case mode_write: fp = fopen(fn, "wb+"); break; //need read permission for buffering + case mode_readwrite: fp = fopen(fn, "rb+"); break; + case mode_writeread: fp = fopen(fn, "wb+"); break; + #else + case mode_read: fp = _wfopen(utf16_t(fn), L"rb"); break; + case mode_write: fp = _wfopen(utf16_t(fn), L"wb+"); break; + case mode_readwrite: fp = _wfopen(utf16_t(fn), L"rb+"); break; + case mode_writeread: fp = _wfopen(utf16_t(fn), L"wb+"); break; + #endif + } + if(!fp) return false; + buffer_offset = -1; //invalidate buffer + file_offset = 0; + fseek(fp, 0, SEEK_END); + file_size = ftell(fp); + fseek(fp, 0, SEEK_SET); + return true; + } + + void close() { + if(!fp) return; + buffer_flush(); + fclose(fp); + fp = 0; + } + + file() { + memset(buffer, 0, sizeof buffer); + buffer_offset = -1; + buffer_dirty = false; + fp = 0; + file_offset = 0; + file_size = 0; + file_mode = mode_read; + } + + ~file() { + close(); + } + + file& operator=(const file&) = delete; + file(const file&) = delete; + + private: + enum { buffer_size = 1 << 12, buffer_mask = buffer_size - 1 }; + char buffer[buffer_size]; + int buffer_offset; + bool buffer_dirty; + FILE *fp; + unsigned file_offset; + unsigned file_size; + FileMode file_mode; + + void buffer_sync() { + if(!fp) return; //file not open + if(buffer_offset != (file_offset & ~buffer_mask)) { + buffer_flush(); + buffer_offset = file_offset & ~buffer_mask; + fseek(fp, buffer_offset, SEEK_SET); + unsigned length = (buffer_offset + buffer_size) <= file_size ? buffer_size : (file_size & buffer_mask); + if(length) unsigned unused = fread(buffer, 1, length, fp); + } + } + + void buffer_flush() { + if(!fp) return; //file not open + if(file_mode == mode_read) return; //buffer cannot be written to + if(buffer_offset < 0) return; //buffer unused + if(buffer_dirty == false) return; //buffer unmodified since read + fseek(fp, buffer_offset, SEEK_SET); + unsigned length = (buffer_offset + buffer_size) <= file_size ? buffer_size : (file_size & buffer_mask); + if(length) unsigned unused = fwrite(buffer, 1, length, fp); + buffer_offset = -1; //invalidate buffer + buffer_dirty = false; + } + }; +} + +#endif diff --git a/snesfilter/nall/filemap.hpp b/snesfilter/nall/filemap.hpp new file mode 100644 index 00000000..a05f0eb7 --- /dev/null +++ b/snesfilter/nall/filemap.hpp @@ -0,0 +1,190 @@ +#ifndef NALL_FILEMAP_HPP +#define NALL_FILEMAP_HPP + +#include +#include + +#include +#include +#if defined(_WIN32) + #include +#else + #include + #include + #include + #include + #include +#endif + +namespace nall { + class filemap { + public: + enum filemode { mode_read, mode_write, mode_readwrite, mode_writeread }; + + bool open(const char *filename, filemode mode) { return p_open(filename, mode); } + void close() { return p_close(); } + unsigned size() const { return p_size; } + uint8_t* handle() { return p_handle; } + const uint8_t* handle() const { return p_handle; } + filemap() : p_size(0), p_handle(0) { p_ctor(); } + ~filemap() { p_dtor(); } + + private: + unsigned p_size; + uint8_t *p_handle; + + #if defined(_WIN32) + //============= + //MapViewOfFile + //============= + + HANDLE p_filehandle, p_maphandle; + + bool p_open(const char *filename, filemode mode) { + int desired_access, creation_disposition, flprotect, map_access; + + switch(mode) { + default: return false; + case mode_read: + desired_access = GENERIC_READ; + creation_disposition = OPEN_EXISTING; + flprotect = PAGE_READONLY; + map_access = FILE_MAP_READ; + break; + case mode_write: + //write access requires read access + desired_access = GENERIC_WRITE; + creation_disposition = CREATE_ALWAYS; + flprotect = PAGE_READWRITE; + map_access = FILE_MAP_ALL_ACCESS; + break; + case mode_readwrite: + desired_access = GENERIC_READ | GENERIC_WRITE; + creation_disposition = OPEN_EXISTING; + flprotect = PAGE_READWRITE; + map_access = FILE_MAP_ALL_ACCESS; + break; + case mode_writeread: + desired_access = GENERIC_READ | GENERIC_WRITE; + creation_disposition = CREATE_NEW; + flprotect = PAGE_READWRITE; + map_access = FILE_MAP_ALL_ACCESS; + break; + } + + p_filehandle = CreateFileW(utf16_t(filename), desired_access, FILE_SHARE_READ, NULL, + creation_disposition, FILE_ATTRIBUTE_NORMAL, NULL); + if(p_filehandle == INVALID_HANDLE_VALUE) return false; + + p_size = GetFileSize(p_filehandle, NULL); + + p_maphandle = CreateFileMapping(p_filehandle, NULL, flprotect, 0, p_size, NULL); + if(p_maphandle == INVALID_HANDLE_VALUE) { + CloseHandle(p_filehandle); + p_filehandle = INVALID_HANDLE_VALUE; + return false; + } + + p_handle = (uint8_t*)MapViewOfFile(p_maphandle, map_access, 0, 0, p_size); + return p_handle; + } + + void p_close() { + if(p_handle) { + UnmapViewOfFile(p_handle); + p_handle = 0; + } + + if(p_maphandle != INVALID_HANDLE_VALUE) { + CloseHandle(p_maphandle); + p_maphandle = INVALID_HANDLE_VALUE; + } + + if(p_filehandle != INVALID_HANDLE_VALUE) { + CloseHandle(p_filehandle); + p_filehandle = INVALID_HANDLE_VALUE; + } + } + + void p_ctor() { + p_filehandle = INVALID_HANDLE_VALUE; + p_maphandle = INVALID_HANDLE_VALUE; + } + + void p_dtor() { + close(); + } + + #else + //==== + //mmap + //==== + + int p_fd; + + bool p_open(const char *filename, filemode mode) { + int open_flags, mmap_flags; + + switch(mode) { + default: return false; + case mode_read: + open_flags = O_RDONLY; + mmap_flags = PROT_READ; + break; + case mode_write: + open_flags = O_RDWR | O_CREAT; //mmap() requires read access + mmap_flags = PROT_WRITE; + break; + case mode_readwrite: + open_flags = O_RDWR; + mmap_flags = PROT_READ | PROT_WRITE; + break; + case mode_writeread: + open_flags = O_RDWR | O_CREAT; + mmap_flags = PROT_READ | PROT_WRITE; + break; + } + + p_fd = ::open(filename, open_flags); + if(p_fd < 0) return false; + + struct stat p_stat; + fstat(p_fd, &p_stat); + p_size = p_stat.st_size; + + p_handle = (uint8_t*)mmap(0, p_size, mmap_flags, MAP_SHARED, p_fd, 0); + if(p_handle == MAP_FAILED) { + p_handle = 0; + ::close(p_fd); + p_fd = -1; + return false; + } + + return p_handle; + } + + void p_close() { + if(p_handle) { + munmap(p_handle, p_size); + p_handle = 0; + } + + if(p_fd >= 0) { + ::close(p_fd); + p_fd = -1; + } + } + + void p_ctor() { + p_fd = -1; + } + + void p_dtor() { + p_close(); + } + + #endif + }; +} + +#endif diff --git a/snesfilter/nall/foreach.hpp b/snesfilter/nall/foreach.hpp new file mode 100644 index 00000000..ea975b84 --- /dev/null +++ b/snesfilter/nall/foreach.hpp @@ -0,0 +1,31 @@ +#ifndef NALL_FOREACH_HPP +#define NALL_FOREACH_HPP + +#undef foreach +#define foreach(iter, object) \ + for(unsigned foreach_counter = 0, foreach_limit = foreach_size(object), foreach_once = 0, foreach_broken = 0; foreach_counter < foreach_limit && foreach_broken == 0; foreach_counter++, foreach_once = 0) \ + for(auto &iter = object[foreach_counter]; foreach_once == 0 && (foreach_broken = 1); foreach_once++, foreach_broken = 0) + +#include +#include +#include + +namespace nall { + template unsigned foreach_size(const T& object, typename mp_enable_if>::type = 0) { + return object.count(); + } + + template unsigned foreach_size(const T& object, typename mp_enable_if>::type = 0) { + return object.length(); + } + + template unsigned foreach_size(const T& object, typename mp_enable_if>::type = 0) { + return object.size(); + } + + template unsigned foreach_size(const T& object, typename mp_enable_if>::type = 0) { + return sizeof(T) / sizeof(typename std::remove_extent::type); + } +} + +#endif diff --git a/snesfilter/nall/function.hpp b/snesfilter/nall/function.hpp new file mode 100644 index 00000000..3f0f704e --- /dev/null +++ b/snesfilter/nall/function.hpp @@ -0,0 +1,102 @@ +#ifndef NALL_FUNCTION_HPP +#define NALL_FUNCTION_HPP + +#include +#include + +namespace nall { + template class function; + + template + class function { + private: + struct base1 { virtual void func1(P...) {} }; + struct base2 { virtual void func2(P...) {} }; + struct derived : base1, virtual base2 {}; + + struct data_t { + R (*callback)(const data_t&, P...); + union { + R (*callback_global)(P...); + struct { + R (derived::*callback_member)(P...); + void *object; + }; + }; + } data; + + static R callback_global(const data_t &data, P... p) { + return data.callback_global(p...); + } + + template + static R callback_member(const data_t &data, P... p) { + return (((C*)data.object)->*((R (C::*&)(P...))data.callback_member))(p...); + } + + public: + R operator()(P... p) const { return data.callback(data, p...); } + operator bool() const { return data.callback; } + void reset() { data.callback = 0; } + + function& operator=(const function &source) { memcpy(&data, &source.data, sizeof(data_t)); return *this; } + function(const function &source) { operator=(source); } + + //no pointer + function() { + data.callback = 0; + } + + //symbolic link pointer (nall/dl.hpp::sym, etc) + function(void *callback) { + data.callback = callback ? &callback_global : 0; + data.callback_global = (R (*)(P...))callback; + } + + //global function pointer + function(R (*callback)(P...)) { + data.callback = &callback_global; + data.callback_global = callback; + } + + //member function pointer + template + function(R (C::*callback)(P...), C *object) { + static_assert(sizeof data.callback_member >= sizeof callback, "callback_member is too small"); + data.callback = &callback_member; + (R (C::*&)(P...))data.callback_member = callback; + data.object = object; + } + + //const member function pointer + template + function(R (C::*callback)(P...) const, C *object) { + static_assert(sizeof data.callback_member >= sizeof callback, "callback_member is too small"); + data.callback = &callback_member; + (R (C::*&)(P...))data.callback_member = (R (C::*&)(P...))callback; + data.object = object; + } + + //lambda function pointer + template + function(T callback) { + static_assert(std::is_same::type>::value, "lambda mismatch"); + data.callback = &callback_global; + data.callback_global = (R (*)(P...))callback; + } + }; + + //bind functions to ease construction and assignment of function() with more than one argument + + template + function bind(R (C::*callback)(P...), C *object) { + return function(callback, object); + } + + template + function bind(R (C::*callback)(P...) const, C *object) { + return function(callback, object); + } +} + +#endif diff --git a/snesfilter/nall/input.hpp b/snesfilter/nall/input.hpp new file mode 100644 index 00000000..83c4a484 --- /dev/null +++ b/snesfilter/nall/input.hpp @@ -0,0 +1,386 @@ +#ifndef NALL_INPUT_HPP +#define NALL_INPUT_HPP + +#include +#include +#include + +#include +#include + +namespace nall { + +struct Keyboard; +Keyboard& keyboard(unsigned = 0); + +static const char KeyboardScancodeName[][64] = { + "Escape", "F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12", + "PrintScreen", "ScrollLock", "Pause", "Tilde", + "Num1", "Num2", "Num3", "Num4", "Num5", "Num6", "Num7", "Num8", "Num9", "Num0", + "Dash", "Equal", "Backspace", + "Insert", "Delete", "Home", "End", "PageUp", "PageDown", + "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", + "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", + "LeftBracket", "RightBracket", "Backslash", "Semicolon", "Apostrophe", "Comma", "Period", "Slash", + "Keypad1", "Keypad2", "Keypad3", "Keypad4", "Keypad5", "Keypad6", "Keypad7", "Keypad8", "Keypad9", "Keypad0", + "Point", "Enter", "Add", "Subtract", "Multiply", "Divide", + "NumLock", "CapsLock", + "Up", "Down", "Left", "Right", + "Tab", "Return", "Spacebar", "Menu", + "Shift", "Control", "Alt", "Super", +}; + +struct Keyboard { + const unsigned ID; + enum { Base = 1 }; + enum { Count = 8, Size = 128 }; + + enum Scancode { + Escape, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, + PrintScreen, ScrollLock, Pause, Tilde, + Num1, Num2, Num3, Num4, Num5, Num6, Num7, Num8, Num9, Num0, + Dash, Equal, Backspace, + Insert, Delete, Home, End, PageUp, PageDown, + A, B, C, D, E, F, G, H, I, J, K, L, M, + N, O, P, Q, R, S, T, U, V, W, X, Y, Z, + LeftBracket, RightBracket, Backslash, Semicolon, Apostrophe, Comma, Period, Slash, + Keypad1, Keypad2, Keypad3, Keypad4, Keypad5, Keypad6, Keypad7, Keypad8, Keypad9, Keypad0, + Point, Enter, Add, Subtract, Multiply, Divide, + NumLock, CapsLock, + Up, Down, Left, Right, + Tab, Return, Spacebar, Menu, + Shift, Control, Alt, Super, + Limit, + }; + + static signed numberDecode(uint16_t scancode) { + for(unsigned i = 0; i < Count; i++) { + if(keyboard(i).belongsTo(scancode)) return i; + } + return -1; + } + + static signed keyDecode(uint16_t scancode) { + for(unsigned i = 0; i < Count; i++) { + if(keyboard(i).isKey(scancode)) return scancode - keyboard(i).key(Escape); + } + return -1; + } + + static signed modifierDecode(uint16_t scancode) { + for(unsigned i = 0; i < Count; i++) { + if(keyboard(i).isModifier(scancode)) return scancode - keyboard(i).key(Shift); + } + return -1; + } + + static bool isAnyKey(uint16_t scancode) { + for(unsigned i = 0; i < Count; i++) { + if(keyboard(i).isKey(scancode)) return true; + } + return false; + } + + static bool isAnyModifier(uint16_t scancode) { + for(unsigned i = 0; i < Count; i++) { + if(keyboard(i).isModifier(scancode)) return true; + } + return false; + } + + static uint16_t decode(const char *name) { + string s(name); + if(!strbegin(name, "KB")) return 0; + ltrim(s, "KB"); + unsigned id = strunsigned(s); + auto pos = strpos(s, "::"); + if(!pos) return 0; + s = substr(s, pos() + 2); + for(unsigned i = 0; i < Limit; i++) { + if(s == KeyboardScancodeName[i]) return Base + Size * id + i; + } + return 0; + } + + string encode(uint16_t code) const { + unsigned index = 0; + for(unsigned i = 0; i < Count; i++) { + if(code >= Base + Size * i && code < Base + Size * (i + 1)) { + index = code - (Base + Size * i); + break; + } + } + return string() << "KB" << ID << "::" << KeyboardScancodeName[index]; + } + + uint16_t operator[](Scancode code) const { return Base + ID * Size + code; } + uint16_t key(unsigned id) const { return Base + Size * ID + id; } + bool isKey(unsigned id) const { return id >= key(Escape) && id <= key(Menu); } + bool isModifier(unsigned id) const { return id >= key(Shift) && id <= key(Super); } + bool belongsTo(uint16_t scancode) const { return isKey(scancode) || isModifier(scancode); } + + Keyboard(unsigned ID_) : ID(ID_) {} +}; + +inline Keyboard& keyboard(unsigned id) { + static Keyboard kb0(0), kb1(1), kb2(2), kb3(3), kb4(4), kb5(5), kb6(6), kb7(7); + switch(id) { default: + case 0: return kb0; case 1: return kb1; case 2: return kb2; case 3: return kb3; + case 4: return kb4; case 5: return kb5; case 6: return kb6; case 7: return kb7; + } +} + +static const char MouseScancodeName[][64] = { + "Xaxis", "Yaxis", "Zaxis", + "Button0", "Button1", "Button2", "Button3", "Button4", "Button5", "Button6", "Button7", +}; + +struct Mouse; +Mouse& mouse(unsigned = 0); + +struct Mouse { + const unsigned ID; + enum { Base = Keyboard::Base + Keyboard::Size * Keyboard::Count }; + enum { Count = 8, Size = 16 }; + enum { Axes = 3, Buttons = 8 }; + + enum Scancode { + Xaxis, Yaxis, Zaxis, + Button0, Button1, Button2, Button3, Button4, Button5, Button6, Button7, + Limit, + }; + + static signed numberDecode(uint16_t scancode) { + for(unsigned i = 0; i < Count; i++) { + if(mouse(i).belongsTo(scancode)) return i; + } + return -1; + } + + static signed axisDecode(uint16_t scancode) { + for(unsigned i = 0; i < Count; i++) { + if(mouse(i).isAxis(scancode)) return scancode - mouse(i).axis(0); + } + return -1; + } + + static signed buttonDecode(uint16_t scancode) { + for(unsigned i = 0; i < Count; i++) { + if(mouse(i).isButton(scancode)) return scancode - mouse(i).button(0); + } + return -1; + } + + static bool isAnyAxis(uint16_t scancode) { + for(unsigned i = 0; i < Count; i++) { + if(mouse(i).isAxis(scancode)) return true; + } + return false; + } + + static bool isAnyButton(uint16_t scancode) { + for(unsigned i = 0; i < Count; i++) { + if(mouse(i).isButton(scancode)) return true; + } + return false; + } + + static uint16_t decode(const char *name) { + string s(name); + if(!strbegin(name, "MS")) return 0; + ltrim(s, "MS"); + unsigned id = strunsigned(s); + auto pos = strpos(s, "::"); + if(!pos) return 0; + s = substr(s, pos() + 2); + for(unsigned i = 0; i < Limit; i++) { + if(s == MouseScancodeName[i]) return Base + Size * id + i; + } + return 0; + } + + string encode(uint16_t code) const { + unsigned index = 0; + for(unsigned i = 0; i < Count; i++) { + if(code >= Base + Size * i && code < Base + Size * (i + 1)) { + index = code - (Base + Size * i); + break; + } + } + return string() << "MS" << ID << "::" << MouseScancodeName[index]; + } + + uint16_t operator[](Scancode code) const { return Base + ID * Size + code; } + uint16_t axis(unsigned id) const { return Base + Size * ID + Xaxis + id; } + uint16_t button(unsigned id) const { return Base + Size * ID + Button0 + id; } + bool isAxis(unsigned id) const { return id >= axis(0) && id <= axis(2); } + bool isButton(unsigned id) const { return id >= button(0) && id <= button(7); } + bool belongsTo(uint16_t scancode) const { return isAxis(scancode) || isButton(scancode); } + + Mouse(unsigned ID_) : ID(ID_) {} +}; + +inline Mouse& mouse(unsigned id) { + static Mouse ms0(0), ms1(1), ms2(2), ms3(3), ms4(4), ms5(5), ms6(6), ms7(7); + switch(id) { default: + case 0: return ms0; case 1: return ms1; case 2: return ms2; case 3: return ms3; + case 4: return ms4; case 5: return ms5; case 6: return ms6; case 7: return ms7; + } +} + +static const char JoypadScancodeName[][64] = { + "Hat0", "Hat1", "Hat2", "Hat3", "Hat4", "Hat5", "Hat6", "Hat7", + "Axis0", "Axis1", "Axis2", "Axis3", "Axis4", "Axis5", "Axis6", "Axis7", + "Axis8", "Axis9", "Axis10", "Axis11", "Axis12", "Axis13", "Axis14", "Axis15", + "Button0", "Button1", "Button2", "Button3", "Button4", "Button5", "Button6", "Button7", + "Button8", "Button9", "Button10", "Button11", "Button12", "Button13", "Button14", "Button15", + "Button16", "Button17", "Button18", "Button19", "Button20", "Button21", "Button22", "Button23", + "Button24", "Button25", "Button26", "Button27", "Button28", "Button29", "Button30", "Button31", +}; + +struct Joypad; +Joypad& joypad(unsigned = 0); + +struct Joypad { + const unsigned ID; + enum { Base = Mouse::Base + Mouse::Size * Mouse::Count }; + enum { Count = 8, Size = 64 }; + enum { Hats = 8, Axes = 16, Buttons = 32 }; + + enum Scancode { + Hat0, Hat1, Hat2, Hat3, Hat4, Hat5, Hat6, Hat7, + Axis0, Axis1, Axis2, Axis3, Axis4, Axis5, Axis6, Axis7, + Axis8, Axis9, Axis10, Axis11, Axis12, Axis13, Axis14, Axis15, + Button0, Button1, Button2, Button3, Button4, Button5, Button6, Button7, + Button8, Button9, Button10, Button11, Button12, Button13, Button14, Button15, + Button16, Button17, Button18, Button19, Button20, Button21, Button22, Button23, + Button24, Button25, Button26, Button27, Button28, Button29, Button30, Button31, + Limit, + }; + + enum Hat { HatCenter = 0, HatUp = 1, HatRight = 2, HatDown = 4, HatLeft = 8 }; + + static signed numberDecode(uint16_t scancode) { + for(unsigned i = 0; i < Count; i++) { + if(joypad(i).belongsTo(scancode)) return i; + } + return -1; + } + + static signed hatDecode(uint16_t scancode) { + for(unsigned i = 0; i < Count; i++) { + if(joypad(i).isHat(scancode)) return scancode - joypad(i).hat(0); + } + return -1; + } + + static signed axisDecode(uint16_t scancode) { + for(unsigned i = 0; i < Count; i++) { + if(joypad(i).isAxis(scancode)) return scancode - joypad(i).axis(0); + } + return -1; + } + + static signed buttonDecode(uint16_t scancode) { + for(unsigned i = 0; i < Count; i++) { + if(joypad(i).isButton(scancode)) return scancode - joypad(i).button(0); + } + return -1; + } + + static bool isAnyHat(uint16_t scancode) { + for(unsigned i = 0; i < Count; i++) { + if(joypad(i).isHat(scancode)) return true; + } + return false; + } + + static bool isAnyAxis(uint16_t scancode) { + for(unsigned i = 0; i < Count; i++) { + if(joypad(i).isAxis(scancode)) return true; + } + return false; + } + + static bool isAnyButton(uint16_t scancode) { + for(unsigned i = 0; i < Count; i++) { + if(joypad(i).isButton(scancode)) return true; + } + return false; + } + + static uint16_t decode(const char *name) { + string s(name); + if(!strbegin(name, "JP")) return 0; + ltrim(s, "JP"); + unsigned id = strunsigned(s); + auto pos = strpos(s, "::"); + if(!pos) return 0; + s = substr(s, pos() + 2); + for(unsigned i = 0; i < Limit; i++) { + if(s == JoypadScancodeName[i]) return Base + Size * id + i; + } + return 0; + } + + string encode(uint16_t code) const { + unsigned index = 0; + for(unsigned i = 0; i < Count; i++) { + if(code >= Base + Size * i && code < Base + Size * (i + 1)) { + index = code - (Base + Size * i); + } + } + return string() << "JP" << ID << "::" << JoypadScancodeName[index]; + } + + uint16_t operator[](Scancode code) const { return Base + ID * Size + code; } + uint16_t hat(unsigned id) const { return Base + Size * ID + Hat0 + id; } + uint16_t axis(unsigned id) const { return Base + Size * ID + Axis0 + id; } + uint16_t button(unsigned id) const { return Base + Size * ID + Button0 + id; } + bool isHat(unsigned id) const { return id >= hat(0) && id <= hat(7); } + bool isAxis(unsigned id) const { return id >= axis(0) && id <= axis(15); } + bool isButton(unsigned id) const { return id >= button(0) && id <= button(31); } + bool belongsTo(uint16_t scancode) const { return isHat(scancode) || isAxis(scancode) || isButton(scancode); } + + Joypad(unsigned ID_) : ID(ID_) {} +}; + +inline Joypad& joypad(unsigned id) { + static Joypad jp0(0), jp1(1), jp2(2), jp3(3), jp4(4), jp5(5), jp6(6), jp7(7); + switch(id) { default: + case 0: return jp0; case 1: return jp1; case 2: return jp2; case 3: return jp3; + case 4: return jp4; case 5: return jp5; case 6: return jp6; case 7: return jp7; + } +} + +struct Scancode { + enum { None = 0, Limit = Joypad::Base + Joypad::Size * Joypad::Count }; + + static uint16_t decode(const char *name) { + uint16_t code; + code = Keyboard::decode(name); + if(code) return code; + code = Mouse::decode(name); + if(code) return code; + code = Joypad::decode(name); + if(code) return code; + return None; + } + + static string encode(uint16_t code) { + for(unsigned i = 0; i < Keyboard::Count; i++) { + if(keyboard(i).belongsTo(code)) return keyboard(i).encode(code); + } + for(unsigned i = 0; i < Mouse::Count; i++) { + if(mouse(i).belongsTo(code)) return mouse(i).encode(code); + } + for(unsigned i = 0; i < Joypad::Count; i++) { + if(joypad(i).belongsTo(code)) return joypad(i).encode(code); + } + return "None"; + } +}; + +} + +#endif diff --git a/snesfilter/nall/lzss.hpp b/snesfilter/nall/lzss.hpp new file mode 100644 index 00000000..202bc814 --- /dev/null +++ b/snesfilter/nall/lzss.hpp @@ -0,0 +1,81 @@ +#ifndef NALL_LZSS_HPP +#define NALL_LZSS_HPP + +#include +#include +#include + +namespace nall { + class lzss { + public: + static bool encode(uint8_t *&output, unsigned &outlength, const uint8_t *input, unsigned inlength) { + output = new(zeromemory) uint8_t[inlength * 9 / 8 + 9]; + + unsigned i = 0, o = 0; + while(i < inlength) { + unsigned flagoffset = o++; + uint8_t flag = 0x00; + + for(unsigned b = 0; b < 8 && i < inlength; b++) { + unsigned longest = 0, pointer; + for(unsigned index = 1; index < 4096; index++) { + unsigned count = 0; + while(true) { + if(count >= 15 + 3) break; //verify pattern match is not longer than max length + if(i + count >= inlength) break; //verify pattern match does not read past end of input + if(i + count < index) break; //verify read is not before start of input + if(input[i + count] != input[i + count - index]) break; //verify pattern still matches + count++; + } + + if(count > longest) { + longest = count; + pointer = index; + } + } + + if(longest < 3) output[o++] = input[i++]; + else { + flag |= 1 << b; + uint16_t x = ((longest - 3) << 12) + pointer; + output[o++] = x; + output[o++] = x >> 8; + i += longest; + } + } + + output[flagoffset] = flag; + } + + outlength = o; + return true; + } + + static bool decode(uint8_t *&output, const uint8_t *input, unsigned length) { + output = new(zeromemory) uint8_t[length]; + + unsigned i = 0, o = 0; + while(o < length) { + uint8_t flag = input[i++]; + + for(unsigned b = 0; b < 8 && o < length; b++) { + if(!(flag & (1 << b))) output[o++] = input[i++]; + else { + uint16_t offset = input[i++]; + offset += input[i++] << 8; + uint16_t lookuplength = (offset >> 12) + 3; + offset &= 4095; + for(unsigned index = 0; index < lookuplength && o + index < length; index++) { + output[o + index] = output[o + index - offset]; + } + o += lookuplength; + } + } + } + + return true; + } + }; +} + +#endif diff --git a/snesfilter/nall/moduloarray.hpp b/snesfilter/nall/moduloarray.hpp new file mode 100644 index 00000000..be549ae9 --- /dev/null +++ b/snesfilter/nall/moduloarray.hpp @@ -0,0 +1,40 @@ +#ifndef NALL_MODULO_HPP +#define NALL_MODULO_HPP + +#include + +namespace nall { + template class modulo_array { + public: + inline T operator[](int index) const { + return buffer[size + index]; + } + + inline T read(int index) const { + return buffer[size + index]; + } + + inline void write(unsigned index, const T value) { + buffer[index] = + buffer[index + size] = + buffer[index + size + size] = value; + } + + void serialize(serializer &s) { + s.array(buffer, size * 3); + } + + modulo_array() { + buffer = new T[size * 3](); + } + + ~modulo_array() { + delete[] buffer; + } + + private: + T *buffer; + }; +} + +#endif diff --git a/snesfilter/nall/platform.hpp b/snesfilter/nall/platform.hpp new file mode 100644 index 00000000..68ed37ce --- /dev/null +++ b/snesfilter/nall/platform.hpp @@ -0,0 +1,80 @@ +#ifndef NALL_PLATFORM_HPP +#define NALL_PLATFORM_HPP + +#include + +//========================= +//standard platform headers +//========================= + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(_WIN32) + #include + #include + #include + #undef interface +#else + #include + #include + #include +#endif + +//================== +//warning supression +//================== + +//Visual C++ +#if defined(_MSC_VER) + //disable libc "deprecation" warnings + #pragma warning(disable:4996) +#endif + +//================ +//POSIX compliance +//================ + +#if defined(_MSC_VER) + #define PATH_MAX _MAX_PATH + #define va_copy(dest, src) ((dest) = (src)) +#endif + +#if defined(_WIN32) + #define getcwd _getcwd + #define ftruncate _chsize + #define putenv _putenv + #define mkdir(n, m) _wmkdir(nall::utf16_t(n)) + #define rmdir _rmdir + #define vsnprintf _vsnprintf + #define usleep(n) Sleep(n / 1000) +#endif + +//================ +//inline expansion +//================ + +#if defined(__GNUC__) + #define noinline __attribute__((noinline)) + #define inline inline + #define alwaysinline inline __attribute__((always_inline)) +#elif defined(_MSC_VER) + #define noinline __declspec(noinline) + #define inline inline + #define alwaysinline inline __forceinline +#else + #define noinline + #define inline inline + #define alwaysinline inline +#endif + +#endif + diff --git a/snesfilter/nall/priorityqueue.hpp b/snesfilter/nall/priorityqueue.hpp new file mode 100644 index 00000000..7104e791 --- /dev/null +++ b/snesfilter/nall/priorityqueue.hpp @@ -0,0 +1,109 @@ +#ifndef NALL_PRIORITYQUEUE_HPP +#define NALL_PRIORITYQUEUE_HPP + +#include +#include +#include +#include + +namespace nall { + template void priority_queue_nocallback(type_t) {} + + //priority queue implementation using binary min-heap array; + //does not require normalize() function. + //O(1) find (tick) + //O(log n) insert (enqueue) + //O(log n) remove (dequeue) + template class priority_queue { + public: + inline void tick(unsigned ticks) { + basecounter += ticks; + while(heapsize && gte(basecounter, heap[0].counter)) callback(dequeue()); + } + + //counter is relative to current time (eg enqueue(64, ...) fires in 64 ticks); + //counter cannot exceed std::numeric_limits::max() >> 1. + void enqueue(unsigned counter, type_t event) { + unsigned child = heapsize++; + counter += basecounter; + + while(child) { + unsigned parent = (child - 1) >> 1; + if(gte(counter, heap[parent].counter)) break; + + heap[child].counter = heap[parent].counter; + heap[child].event = heap[parent].event; + child = parent; + } + + heap[child].counter = counter; + heap[child].event = event; + } + + type_t dequeue() { + type_t event(heap[0].event); + unsigned parent = 0; + unsigned counter = heap[--heapsize].counter; + + while(true) { + unsigned child = (parent << 1) + 1; + if(child >= heapsize) break; + if(child + 1 < heapsize && gte(heap[child].counter, heap[child + 1].counter)) child++; + if(gte(heap[child].counter, counter)) break; + + heap[parent].counter = heap[child].counter; + heap[parent].event = heap[child].event; + parent = child; + } + + heap[parent].counter = counter; + heap[parent].event = heap[heapsize].event; + return event; + } + + void reset() { + basecounter = 0; + heapsize = 0; + } + + void serialize(serializer &s) { + s.integer(basecounter); + s.integer(heapsize); + for(unsigned n = 0; n < heapcapacity; n++) { + s.integer(heap[n].counter); + s.integer(heap[n].event); + } + } + + priority_queue(unsigned size, function callback_ = &priority_queue_nocallback) + : callback(callback_) { + heap = new heap_t[size]; + heapcapacity = size; + reset(); + } + + ~priority_queue() { + delete[] heap; + } + + priority_queue& operator=(const priority_queue&) = delete; + priority_queue(const priority_queue&) = delete; + + private: + function callback; + unsigned basecounter; + unsigned heapsize; + unsigned heapcapacity; + struct heap_t { + unsigned counter; + type_t event; + } *heap; + + //return true if x is greater than or equal to y + inline bool gte(unsigned x, unsigned y) { + return x - y < (std::numeric_limits::max() >> 1); + } + }; +} + +#endif diff --git a/snesfilter/nall/property.hpp b/snesfilter/nall/property.hpp new file mode 100644 index 00000000..6fd33acd --- /dev/null +++ b/snesfilter/nall/property.hpp @@ -0,0 +1,91 @@ +#ifndef NALL_PROPERTY_HPP +#define NALL_PROPERTY_HPP + +//nall::property implements ownership semantics into container classes +//example: property::readonly implies that only owner has full +//access to type; and all other code has readonly access. +// +//this code relies on extended friend semantics from C++0x to work, as it +//declares a friend class via a template paramter. it also exploits a bug in +//G++ 4.x to work even in C++98 mode. +// +//if compiling elsewhere, simply remove the friend class and private semantics + +//property can be used either of two ways: +//struct foo { +// property::readonly x; +// property::readwrite y; +//}; +//-or- +//struct foo : property { +// readonly x; +// readwrite y; +//}; + +//return types are const T& (byref) instead fo T (byval) to avoid major speed +//penalties for objects with expensive copy constructors + +//operator-> provides access to underlying object type: +//readonly foo; +//foo->bar(); +//... will call Object::bar(); + +//operator='s reference is constant so as to avoid leaking a reference handle +//that could bypass access restrictions + +//both constant and non-constant operators are provided, though it may be +//necessary to cast first, for instance: +//struct foo : property { readonly bar; } object; +//int main() { int value = const_cast(object); } + +//writeonly is useful for objects that have non-const reads, but const writes. +//however, to avoid leaking handles, the interface is very restricted. the only +//way to write is via operator=, which requires conversion via eg copy +//constructor. example: +//struct foo { +// foo(bool value) { ... } +//}; +//writeonly bar; +//bar = true; + +namespace nall { + template struct property { + template struct traits { typedef T type; }; + + template struct readonly { + const T* operator->() const { return &value; } + const T& operator()() const { return value; } + operator const T&() const { return value; } + private: + T* operator->() { return &value; } + operator T&() { return value; } + const T& operator=(const T& value_) { return value = value_; } + T value; + friend class traits::type; + }; + + template struct writeonly { + void operator=(const T& value_) { value = value_; } + private: + const T* operator->() const { return &value; } + const T& operator()() const { return value; } + operator const T&() const { return value; } + T* operator->() { return &value; } + operator T&() { return value; } + T value; + friend class traits::type; + }; + + template struct readwrite { + const T* operator->() const { return &value; } + const T& operator()() const { return value; } + operator const T&() const { return value; } + T* operator->() { return &value; } + operator T&() { return value; } + const T& operator=(const T& value_) { return value = value_; } + T value; + }; + }; +} + +#endif diff --git a/snesfilter/nall/qt/Makefile b/snesfilter/nall/qt/Makefile new file mode 100644 index 00000000..69e84960 --- /dev/null +++ b/snesfilter/nall/qt/Makefile @@ -0,0 +1,55 @@ +# requires nall/Makefile + +# imports: +# $(qtlibs) -- list of Qt components to link against + +# exports the following symbols: +# $(moc) -- meta-object compiler +# $(rcc) -- resource compiler +# $(qtinc) -- includes for compiling +# $(qtlib) -- libraries for linking + +ifeq ($(moc),) +moc := moc +endif + +ifeq ($(rcc),) +rcc := rcc +endif + +ifeq ($(platform),x) + qtinc := `pkg-config --cflags $(qtlibs)` + qtlib := `pkg-config --libs $(qtlibs)` +else ifeq ($(platform),osx) + qtinc := $(foreach lib,$(qtlibs),-I/Library/Frameworks/$(lib).framework/Versions/4/Headers) + + qtlib := -L/Library/Frameworks + qtlib += $(foreach lib,$(qtlibs),-framework $(lib)) + qtlib += -framework Carbon + qtlib += -framework Cocoa + qtlib += -framework OpenGL + qtlib += -framework AppKit + qtlib += -framework ApplicationServices +else ifeq ($(platform),win) + ifeq ($(qtpath),) + # find Qt install directory from PATH environment variable + qtpath := $(foreach path,$(subst ;, ,$(PATH)),$(if $(wildcard $(path)/$(moc).exe),$(path))) + qtpath := $(strip $(qtpath)) + qtpath := $(subst \,/,$(qtpath)) + qtpath := $(patsubst %/bin,%,$(qtpath)) + endif + + qtinc := -I$(qtpath)/include + qtinc += $(foreach lib,$(qtlibs),-I$(qtpath)/include/$(lib)) + + qtlib := -L$(qtpath)/lib + qtlib += -L$(qtpath)/plugins/imageformats + + qtlib += $(foreach lib,$(qtlibs),-l$(lib)4) + qtlib += -lmingw32 -lqtmain -lcomdlg32 -loleaut32 -limm32 -lwinmm + qtlib += -lwinspool -lmsimg32 -lole32 -ladvapi32 -lws2_32 -luuid -lgdi32 + qtlib += $(foreach lib,$(qtlibs),-l$(lib)4) + + # optional image-file support: + # qtlib += -lqjpeg -lqmng +endif diff --git a/snesfilter/nall/qt/check-action.moc.hpp b/snesfilter/nall/qt/check-action.moc.hpp new file mode 100644 index 00000000..db378fe9 --- /dev/null +++ b/snesfilter/nall/qt/check-action.moc.hpp @@ -0,0 +1,41 @@ +#ifndef NALL_QT_CHECKACTION_HPP +#define NALL_QT_CHECKACTION_HPP + +namespace nall { + +class CheckAction : public QAction { + Q_OBJECT + +public: + bool isChecked() const; + void setChecked(bool); + void toggleChecked(); + CheckAction(const QString&, QObject*); + +protected slots: + +protected: + bool checked; +}; + +inline bool CheckAction::isChecked() const { + return checked; +} + +inline void CheckAction::setChecked(bool checked_) { + checked = checked_; + if(checked) setIcon(QIcon(":/16x16/item-check-on.png")); + else setIcon(QIcon(":/16x16/item-check-off.png")); +} + +inline void CheckAction::toggleChecked() { + setChecked(!isChecked()); +} + +inline CheckAction::CheckAction(const QString &text, QObject *parent) : QAction(text, parent) { + setChecked(false); +} + +} + +#endif diff --git a/snesfilter/nall/qt/concept.hpp b/snesfilter/nall/qt/concept.hpp new file mode 100644 index 00000000..51cacef4 --- /dev/null +++ b/snesfilter/nall/qt/concept.hpp @@ -0,0 +1,10 @@ +#ifndef NALL_QT_CONCEPT_HPP +#define NALL_QT_CONCEPT_HPP + +#include + +namespace nall { + template struct has_count> { enum { value = true }; }; +} + +#endif diff --git a/snesfilter/nall/qt/file-dialog.moc.hpp b/snesfilter/nall/qt/file-dialog.moc.hpp new file mode 100644 index 00000000..6528289b --- /dev/null +++ b/snesfilter/nall/qt/file-dialog.moc.hpp @@ -0,0 +1,392 @@ +#ifndef NALL_QT_FILEDIALOG_HPP +#define NALL_QT_FILEDIALOG_HPP + +#include +#include +#include + +namespace nall { + +class FileDialog; + +class NewFolderDialog : public Window { + Q_OBJECT + +public: + void show(); + NewFolderDialog(FileDialog*); + +protected slots: + void createFolderAction(); + +protected: + FileDialog *parent; + QVBoxLayout *layout; + QLineEdit *folderNameEdit; + QHBoxLayout *controlLayout; + QPushButton *okButton; + QPushButton *cancelButton; +}; + +class FileView : public QListView { + Q_OBJECT + +protected: + void keyPressEvent(QKeyEvent*); + +signals: + void changed(const QModelIndex&); + void browseUp(); + +protected slots: + void currentChanged(const QModelIndex&, const QModelIndex&); +}; + +class FileDialog : public Window { + Q_OBJECT + +public: + void showLoad(); + void showSave(); + void showFolder(); + + void setPath(string path); + void setNameFilters(const string &filters); + FileDialog(); + +signals: + void changed(const string&); + void activated(const string&); + void accepted(const string&); + void rejected(); + +protected slots: + void fileViewChange(const QModelIndex&); + void fileViewActivate(const QModelIndex&); + void pathBoxChanged(); + void filterBoxChanged(); + void createNewFolder(); + void browseUp(); + void acceptAction(); + void rejectAction(); + +protected: + NewFolderDialog *newFolderDialog; + QVBoxLayout *layout; + QHBoxLayout *navigationLayout; + QComboBox *pathBox; + QPushButton *newFolderButton; + QPushButton *upFolderButton; + QHBoxLayout *browseLayout; + QFileSystemModel *fileSystemModel; + FileView *fileView; + QGroupBox *previewFrame; + QLineEdit *fileNameEdit; + QHBoxLayout *controlLayout; + QComboBox *filterBox; + QPushButton *optionsButton; + QPushButton *acceptButton; + QPushButton *rejectButton; + bool lock; + void createFolderAction(const string &name); + void closeEvent(QCloseEvent*); + + friend class NewFolderDialog; +}; + +inline void NewFolderDialog::show() { + folderNameEdit->setText(""); + Window::show(); + folderNameEdit->setFocus(); +} + +inline void NewFolderDialog::createFolderAction() { + string name = folderNameEdit->text().toUtf8().constData(); + if(name == "") { + folderNameEdit->setFocus(); + } else { + parent->createFolderAction(name); + close(); + } +} + +inline NewFolderDialog::NewFolderDialog(FileDialog *fileDialog) : parent(fileDialog) { + setMinimumWidth(240); + setWindowTitle("Create New Folder"); + + layout = new QVBoxLayout; + layout->setAlignment(Qt::AlignTop); + layout->setMargin(5); + layout->setSpacing(5); + setLayout(layout); + + folderNameEdit = new QLineEdit; + layout->addWidget(folderNameEdit); + + controlLayout = new QHBoxLayout; + controlLayout->setAlignment(Qt::AlignRight); + layout->addLayout(controlLayout); + + okButton = new QPushButton("Ok"); + controlLayout->addWidget(okButton); + + cancelButton = new QPushButton("Cancel"); + controlLayout->addWidget(cancelButton); + + connect(folderNameEdit, SIGNAL(returnPressed()), this, SLOT(createFolderAction())); + connect(okButton, SIGNAL(released()), this, SLOT(createFolderAction())); + connect(cancelButton, SIGNAL(released()), this, SLOT(close())); +} + +inline void FileView::currentChanged(const QModelIndex ¤t, const QModelIndex &previous) { + QAbstractItemView::currentChanged(current, previous); + emit changed(current); +} + +inline void FileView::keyPressEvent(QKeyEvent *event) { + //enhance consistency: force OS X to act like Windows and Linux; enter = activate item + if(event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter) { + emit activated(currentIndex()); + return; + } + + //simulate popular file manager behavior; backspace = go up one directory + if(event->key() == Qt::Key_Backspace) { + emit browseUp(); + return; + } + + //fallback: unrecognized keypresses get handled by the widget itself + QListView::keyPressEvent(event); +} + +inline void FileDialog::showLoad() { + acceptButton->setText("Load"); + fileNameEdit->hide(); + filterBox->show(); + show(); +} + +inline void FileDialog::showSave() { + acceptButton->setText("Save"); + fileNameEdit->show(); + filterBox->show(); + show(); +} + +inline void FileDialog::showFolder() { + acceptButton->setText("Choose"); + fileNameEdit->hide(); + filterBox->hide(); + setNameFilters("Folders ()"); + show(); +} + +inline void FileDialog::fileViewChange(const QModelIndex &index) { + string path = fileSystemModel->filePath(index).toUtf8().constData(); + if(path == fileSystemModel->rootPath().toUtf8().constData()) path = ""; + fileNameEdit->setText(notdir(path)); + emit changed(path); +} + +inline void FileDialog::fileViewActivate(const QModelIndex &index) { + string path = fileSystemModel->filePath(index).toUtf8().constData(); + if(fileSystemModel->isDir(index)) { + emit activated(path); + setPath(path); + } else { + emit activated(path); + close(); + } +} + +inline void FileDialog::pathBoxChanged() { + if(lock) return; + setPath(pathBox->currentText().toUtf8().constData()); +} + +inline void FileDialog::filterBoxChanged() { + if(lock) return; + string filters = filterBox->currentText().toUtf8().constData(); + if(filters.length() == 0) { + fileSystemModel->setNameFilters(QStringList() << "*"); + } else { + filters = substr(filters, strpos(filters, "(")()); + ltrim(filters, "("); + rtrim(filters, ")"); + lstring part; + part.split(" ", filters); + QStringList list; + for(unsigned i = 0; i < part.size(); i++) list << part[i]; + fileSystemModel->setNameFilters(list); + } +} + +inline void FileDialog::createNewFolder() { + newFolderDialog->show(); +} + +inline void FileDialog::browseUp() { + if(pathBox->count() > 1) pathBox->setCurrentIndex(1); +} + +inline void FileDialog::setPath(string path) { + lock = true; + newFolderDialog->close(); + + if(QDir(path).exists()) { + newFolderButton->setEnabled(true); + } else { + newFolderButton->setEnabled(false); + path = ""; + } + + fileSystemModel->setRootPath(path); + fileView->setRootIndex(fileSystemModel->index(path)); + fileView->setCurrentIndex(fileView->rootIndex()); + fileView->setFocus(); + + pathBox->clear(); + if(path.length() > 0) { + QDir directory(path); + while(true) { + pathBox->addItem(directory.absolutePath()); + if(directory.isRoot()) break; + directory.cdUp(); + } + } + pathBox->addItem(""); + fileNameEdit->setText(""); + + lock = false; +} + +inline void FileDialog::setNameFilters(const string &filters) { + lock = true; + + lstring list; + list.split("\n", filters); + + filterBox->clear(); + for(unsigned i = 0; i < list.size(); i++) { + filterBox->addItem(list[i]); + } + + lock = false; + filterBoxChanged(); +} + +inline void FileDialog::acceptAction() { + string path = fileSystemModel->rootPath().toUtf8().constData(); + path << "/" << notdir(fileNameEdit->text().toUtf8().constData()); + rtrim(path, "/"); + if(QDir(path).exists()) { + emit accepted(path); + setPath(path); + } else { + emit accepted(path); + close(); + } +} + +inline void FileDialog::rejectAction() { + emit rejected(); + close(); +} + +inline void FileDialog::createFolderAction(const string &name) { + string path = fileSystemModel->rootPath().toUtf8().constData(); + path << "/" << notdir(name); + mkdir(path, 0755); +} + +inline void FileDialog::closeEvent(QCloseEvent *event) { + newFolderDialog->close(); + Window::closeEvent(event); +} + +inline FileDialog::FileDialog() { + newFolderDialog = new NewFolderDialog(this); + resize(640, 360); + + layout = new QVBoxLayout; + layout->setMargin(5); + layout->setSpacing(5); + setLayout(layout); + + navigationLayout = new QHBoxLayout; + layout->addLayout(navigationLayout); + + pathBox = new QComboBox; + pathBox->setEditable(true); + pathBox->setMinimumContentsLength(16); + pathBox->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLength); + pathBox->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); + navigationLayout->addWidget(pathBox); + + newFolderButton = new QPushButton; + newFolderButton->setIconSize(QSize(16, 16)); + newFolderButton->setIcon(QIcon(":/16x16/folder-new.png")); + navigationLayout->addWidget(newFolderButton); + + upFolderButton = new QPushButton; + upFolderButton->setIconSize(QSize(16, 16)); + upFolderButton->setIcon(QIcon(":/16x16/go-up.png")); + navigationLayout->addWidget(upFolderButton); + + browseLayout = new QHBoxLayout; + layout->addLayout(browseLayout); + + fileSystemModel = new QFileSystemModel; + fileSystemModel->setFilter(QDir::AllDirs | QDir::Files | QDir::NoDotAndDotDot); + fileSystemModel->setNameFilterDisables(false); + + fileView = new FileView; + fileView->setMinimumWidth(320); + fileView->setModel(fileSystemModel); + fileView->setIconSize(QSize(16, 16)); + browseLayout->addWidget(fileView); + + previewFrame = new QGroupBox; + previewFrame->hide(); + browseLayout->addWidget(previewFrame); + + fileNameEdit = new QLineEdit; + layout->addWidget(fileNameEdit); + + controlLayout = new QHBoxLayout; + controlLayout->setAlignment(Qt::AlignRight); + layout->addLayout(controlLayout); + + filterBox = new QComboBox; + filterBox->setMinimumContentsLength(16); + filterBox->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLength); + filterBox->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); + controlLayout->addWidget(filterBox); + + optionsButton = new QPushButton("Options"); + optionsButton->hide(); + controlLayout->addWidget(optionsButton); + + acceptButton = new QPushButton("Ok"); + controlLayout->addWidget(acceptButton); + + rejectButton = new QPushButton("Cancel"); + controlLayout->addWidget(rejectButton); + + lock = false; + connect(pathBox, SIGNAL(currentIndexChanged(int)), this, SLOT(pathBoxChanged())); + connect(newFolderButton, SIGNAL(released()), this, SLOT(createNewFolder())); + connect(upFolderButton, SIGNAL(released()), this, SLOT(browseUp())); + connect(fileView, SIGNAL(changed(const QModelIndex&)), this, SLOT(fileViewChange(const QModelIndex&))); + connect(fileView, SIGNAL(activated(const QModelIndex&)), this, SLOT(fileViewActivate(const QModelIndex&))); + connect(fileView, SIGNAL(browseUp()), this, SLOT(browseUp())); + connect(fileNameEdit, SIGNAL(returnPressed()), this, SLOT(acceptAction())); + connect(filterBox, SIGNAL(currentIndexChanged(int)), this, SLOT(filterBoxChanged())); + connect(acceptButton, SIGNAL(released()), this, SLOT(acceptAction())); + connect(rejectButton, SIGNAL(released()), this, SLOT(rejectAction())); +} + +} + +#endif diff --git a/snesfilter/nall/qt/hex-editor.moc.hpp b/snesfilter/nall/qt/hex-editor.moc.hpp new file mode 100644 index 00000000..d59f4be9 --- /dev/null +++ b/snesfilter/nall/qt/hex-editor.moc.hpp @@ -0,0 +1,173 @@ +#ifndef NALL_QT_HEXEDITOR_HPP +#define NALL_QT_HEXEDITOR_HPP + +#include +#include +#include + +namespace nall { + +class HexEditor : public QTextEdit { + Q_OBJECT + +public: + function reader; + function writer; + + void setColumns(unsigned columns); + void setRows(unsigned rows); + void setOffset(unsigned offset); + void setSize(unsigned size); + unsigned lineWidth() const; + void refresh(); + + HexEditor(); + +protected slots: + void scrolled(); + +protected: + QHBoxLayout *layout; + QScrollBar *scrollBar; + unsigned editorColumns; + unsigned editorRows; + unsigned editorOffset; + unsigned editorSize; + bool lock; + + void keyPressEvent(QKeyEvent*); +}; + +inline void HexEditor::keyPressEvent(QKeyEvent *event) { + QTextCursor cursor = textCursor(); + unsigned x = cursor.position() % lineWidth(); + unsigned y = cursor.position() / lineWidth(); + + int hexCode = -1; + switch(event->key()) { + case Qt::Key_0: hexCode = 0; break; + case Qt::Key_1: hexCode = 1; break; + case Qt::Key_2: hexCode = 2; break; + case Qt::Key_3: hexCode = 3; break; + case Qt::Key_4: hexCode = 4; break; + case Qt::Key_5: hexCode = 5; break; + case Qt::Key_6: hexCode = 6; break; + case Qt::Key_7: hexCode = 7; break; + case Qt::Key_8: hexCode = 8; break; + case Qt::Key_9: hexCode = 9; break; + case Qt::Key_A: hexCode = 10; break; + case Qt::Key_B: hexCode = 11; break; + case Qt::Key_C: hexCode = 12; break; + case Qt::Key_D: hexCode = 13; break; + case Qt::Key_E: hexCode = 14; break; + case Qt::Key_F: hexCode = 15; break; + } + + if(cursor.hasSelection() == false && hexCode != -1) { + bool cursorOffsetValid = (x >= 11 && ((x - 11) % 3) != 2); + if(cursorOffsetValid) { + bool nibble = (x - 11) % 3; //0 = top nibble, 1 = bottom nibble + unsigned cursorOffset = y * editorColumns + ((x - 11) / 3); + unsigned effectiveOffset = editorOffset + cursorOffset; + if(effectiveOffset >= editorSize) effectiveOffset %= editorSize; + + uint8_t data = reader ? reader(effectiveOffset) : 0x00; + data &= (nibble == 0 ? 0x0f : 0xf0); + data |= (nibble == 0 ? (hexCode << 4) : (hexCode << 0)); + if(writer) writer(effectiveOffset, data); + refresh(); + + cursor.setPosition(y * lineWidth() + x + 1); //advance cursor + setTextCursor(cursor); + } + } else { + //allow navigation keys to move cursor, but block text input + setTextInteractionFlags(Qt::TextSelectableByKeyboard | Qt::TextSelectableByMouse); + QTextEdit::keyPressEvent(event); + setTextInteractionFlags(Qt::TextEditorInteraction); + } +} + +inline void HexEditor::setColumns(unsigned columns) { + editorColumns = columns; +} + +inline void HexEditor::setRows(unsigned rows) { + editorRows = rows; + scrollBar->setPageStep(editorRows); +} + +inline void HexEditor::setOffset(unsigned offset) { + lock = true; + editorOffset = offset; + scrollBar->setSliderPosition(editorOffset / editorColumns); + lock = false; +} + +inline void HexEditor::setSize(unsigned size) { + editorSize = size; + bool indivisible = (editorSize % editorColumns) != 0; //add one for incomplete row + scrollBar->setRange(0, editorSize / editorColumns + indivisible - editorRows); +} + +inline unsigned HexEditor::lineWidth() const { + return 11 + 3 * editorColumns; +} + +inline void HexEditor::refresh() { + string output; + char temp[256]; + unsigned offset = editorOffset; + + for(unsigned y = 0; y < editorRows; y++) { + if(offset >= editorSize) break; + sprintf(temp, "%.4x:%.4x", (offset >> 16) & 0xffff, (offset >> 0) & 0xffff); + output << "" << temp << "  "; + + for(unsigned x = 0; x < editorColumns; x++) { + if(offset >= editorSize) break; + sprintf(temp, "%.2x", reader ? reader(offset) : 0x00); + offset++; + output << "" << temp << ""; + if(x != (editorColumns - 1)) output << " "; + } + + if(y != (editorRows - 1)) output << "
    "; + } + + setHtml(output); +} + +inline void HexEditor::scrolled() { + if(lock) return; + unsigned offset = scrollBar->sliderPosition(); + editorOffset = offset * editorColumns; + refresh(); +} + +inline HexEditor::HexEditor() { + setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + + layout = new QHBoxLayout; + layout->setAlignment(Qt::AlignRight); + layout->setMargin(0); + layout->setSpacing(0); + setLayout(layout); + + scrollBar = new QScrollBar(Qt::Vertical); + scrollBar->setSingleStep(1); + layout->addWidget(scrollBar); + + lock = false; + connect(scrollBar, SIGNAL(actionTriggered(int)), this, SLOT(scrolled())); + + setColumns(16); + setRows(16); + setSize(0); + setOffset(0); +} + +} + +#endif diff --git a/snesfilter/nall/qt/radio-action.moc.hpp b/snesfilter/nall/qt/radio-action.moc.hpp new file mode 100644 index 00000000..a2bbca48 --- /dev/null +++ b/snesfilter/nall/qt/radio-action.moc.hpp @@ -0,0 +1,41 @@ +#ifndef NALL_QT_RADIOACTION_HPP +#define NALL_QT_RADIOACTION_HPP + +namespace nall { + +class RadioAction : public QAction { + Q_OBJECT + +public: + bool isChecked() const; + void setChecked(bool); + void toggleChecked(); + RadioAction(const QString&, QObject*); + +protected slots: + +protected: + bool checked; +}; + +inline bool RadioAction::isChecked() const { + return checked; +} + +inline void RadioAction::setChecked(bool checked_) { + checked = checked_; + if(checked) setIcon(QIcon(":/16x16/item-radio-on.png")); + else setIcon(QIcon(":/16x16/item-radio-off.png")); +} + +inline void RadioAction::toggleChecked() { + setChecked(!isChecked()); +} + +inline RadioAction::RadioAction(const QString &text, QObject *parent) : QAction(text, parent) { + setChecked(false); +} + +} + +#endif diff --git a/snesfilter/nall/qt/window.moc.hpp b/snesfilter/nall/qt/window.moc.hpp new file mode 100644 index 00000000..0d3bf390 --- /dev/null +++ b/snesfilter/nall/qt/window.moc.hpp @@ -0,0 +1,105 @@ +#ifndef NALL_QT_WINDOW_HPP +#define NALL_QT_WINDOW_HPP + +#include +#include + +namespace nall { + +class Window : public QWidget { + Q_OBJECT + +public: + void setGeometryString(string *geometryString); + void setCloseOnEscape(bool); + void show(); + void hide(); + void shrink(); + + Window(); + +protected slots: + +protected: + string *geometryString; + bool closeOnEscape; + void keyReleaseEvent(QKeyEvent *event); + void closeEvent(QCloseEvent *event); +}; + +inline void Window::setGeometryString(string *geometryString_) { + geometryString = geometryString_; + if(geometryString && isVisible() == false) { + uint8_t *data; + unsigned length; + base64::decode(data, length, *geometryString); + QByteArray array((const char*)data, length); + delete[] data; + restoreGeometry(array); + } +} + +inline void Window::setCloseOnEscape(bool value) { + closeOnEscape = value; +} + +inline void Window::show() { + if(geometryString && isVisible() == false) { + uint8_t *data; + unsigned length; + base64::decode(data, length, *geometryString); + QByteArray array((const char*)data, length); + delete[] data; + restoreGeometry(array); + } + QWidget::show(); + QApplication::processEvents(); + activateWindow(); + raise(); +} + +inline void Window::hide() { + if(geometryString && isVisible() == true) { + char *data; + QByteArray geometry = saveGeometry(); + base64::encode(data, (const uint8_t*)geometry.data(), geometry.length()); + *geometryString = data; + delete[] data; + } + QWidget::hide(); +} + +inline void Window::shrink() { + if(isFullScreen()) return; + + for(unsigned i = 0; i < 2; i++) { + resize(0, 0); + usleep(2000); + QApplication::processEvents(); + } +} + +inline void Window::keyReleaseEvent(QKeyEvent *event) { + if(closeOnEscape && (event->key() == Qt::Key_Escape)) close(); + QWidget::keyReleaseEvent(event); +} + +inline void Window::closeEvent(QCloseEvent *event) { + if(geometryString) { + char *data; + QByteArray geometry = saveGeometry(); + base64::encode(data, (const uint8_t*)geometry.data(), geometry.length()); + *geometryString = data; + delete[] data; + } + QWidget::closeEvent(event); +} + +inline Window::Window() { + geometryString = 0; + closeOnEscape = true; +} + +} + +#endif diff --git a/snesfilter/nall/serial.hpp b/snesfilter/nall/serial.hpp new file mode 100644 index 00000000..6f5cf6d6 --- /dev/null +++ b/snesfilter/nall/serial.hpp @@ -0,0 +1,80 @@ +#ifndef NALL_SERIAL_HPP +#define NALL_SERIAL_HPP + +#include +#include +#include +#include + +#include + +namespace nall { + class serial { + public: + //-1 on error, otherwise return bytes read + int read(uint8_t *data, unsigned length) { + if(port_open == false) return -1; + return ::read(port, (void*)data, length); + } + + //-1 on error, otherwise return bytes written + int write(const uint8_t *data, unsigned length) { + if(port_open == false) return -1; + return ::write(port, (void*)data, length); + } + + bool open(const char *portname, unsigned rate) { + close(); + + port = ::open(portname, O_RDWR | O_NOCTTY | O_NDELAY | O_NONBLOCK); + if(port == -1) return false; + + if(ioctl(port, TIOCEXCL) == -1) { close(); return false; } + if(fcntl(port, F_SETFL, 0) == -1) { close(); return false; } + if(tcgetattr(port, &original_attr) == -1) { close(); return false; } + + termios attr = original_attr; + cfmakeraw(&attr); + cfsetspeed(&attr, rate); + + attr.c_lflag &=~ (ECHO | ECHONL | ISIG | ICANON | IEXTEN); + attr.c_iflag &=~ (BRKINT | PARMRK | INPCK | ISTRIP | INLCR | IGNCR | ICRNL | IXON | IXOFF | IXANY); + attr.c_iflag |= (IGNBRK | IGNPAR); + attr.c_oflag &=~ (OPOST); + attr.c_cflag &=~ (CSIZE | CSTOPB | PARENB); + attr.c_cflag |= (CS8 | CREAD | CLOCAL); + attr.c_cc[VTIME] = attr.c_cc[VMIN] = 0; + + if(tcsetattr(port, TCSANOW, &attr) == -1) { close(); return false; } + return port_open = true; + } + + void close() { + if(port != -1) { + tcdrain(port); + if(port_open == true) { + tcsetattr(port, TCSANOW, &original_attr); + port_open = false; + } + ::close(port); + port = -1; + } + } + + serial() { + port = -1; + port_open = false; + } + + ~serial() { + close(); + } + + private: + int port; + bool port_open; + termios original_attr; + }; +} + +#endif diff --git a/snesfilter/nall/serializer.hpp b/snesfilter/nall/serializer.hpp new file mode 100644 index 00000000..9f816dfe --- /dev/null +++ b/snesfilter/nall/serializer.hpp @@ -0,0 +1,145 @@ +#ifndef NALL_SERIALIZER_HPP +#define NALL_SERIALIZER_HPP + +#include +#include +#include +#include + +namespace nall { + //serializer: a class designed to save and restore the state of classes. + // + //benefits: + //- data() will be portable in size (it is not necessary to specify type sizes.) + //- data() will be portable in endianness (always stored internally as little-endian.) + //- one serialize function can both save and restore class states. + // + //caveats: + //- only plain-old-data can be stored. complex classes must provide serialize(serializer&); + //- floating-point usage is not portable across platforms + + class serializer { + public: + enum mode_t { Load, Save, Size }; + + mode_t mode() const { + return imode; + } + + const uint8_t* data() const { + return idata; + } + + unsigned size() const { + return isize; + } + + unsigned capacity() const { + return icapacity; + } + + template void floatingpoint(T &value) { + enum { size = sizeof(T) }; + //this is rather dangerous, and not cross-platform safe; + //but there is no standardized way to export FP-values + uint8_t *p = (uint8_t*)&value; + if(imode == Save) { + for(unsigned n = 0; n < size; n++) idata[isize++] = p[n]; + } else if(imode == Load) { + for(unsigned n = 0; n < size; n++) p[n] = idata[isize++]; + } else { + isize += size; + } + } + + template void integer(T &value) { + enum { size = std::is_same::value ? 1 : sizeof(T) }; + if(imode == Save) { + for(unsigned n = 0; n < size; n++) idata[isize++] = value >> (n << 3); + } else if(imode == Load) { + value = 0; + for(unsigned n = 0; n < size; n++) value |= idata[isize++] << (n << 3); + } else if(imode == Size) { + isize += size; + } + } + + template void array(T &array) { + enum { size = sizeof(T) / sizeof(typename std::remove_extent::type) }; + for(unsigned n = 0; n < size; n++) integer(array[n]); + } + + template void array(T array, unsigned size) { + for(unsigned n = 0; n < size; n++) integer(array[n]); + } + + //copy + serializer& operator=(const serializer &s) { + if(idata) delete[] idata; + + imode = s.imode; + idata = new uint8_t[s.icapacity]; + isize = s.isize; + icapacity = s.icapacity; + + memcpy(idata, s.idata, s.icapacity); + return *this; + } + + serializer(const serializer &s) : idata(0) { + operator=(s); + } + + //move + serializer& operator=(serializer &&s) { + if(idata) delete[] idata; + + imode = s.imode; + idata = s.idata; + isize = s.isize; + icapacity = s.icapacity; + + s.idata = 0; + return *this; + } + + serializer(serializer &&s) { + operator=(std::move(s)); + } + + //construction + serializer() { + imode = Size; + idata = 0; + isize = 0; + } + + serializer(unsigned capacity) { + imode = Save; + idata = new uint8_t[capacity](); + isize = 0; + icapacity = capacity; + } + + serializer(const uint8_t *data, unsigned capacity) { + imode = Load; + idata = new uint8_t[capacity]; + isize = 0; + icapacity = capacity; + memcpy(idata, data, capacity); + } + + ~serializer() { + if(idata) delete[] idata; + } + + private: + mode_t imode; + uint8_t *idata; + unsigned isize; + unsigned icapacity; + }; + +}; + +#endif diff --git a/snesfilter/nall/sha256.hpp b/snesfilter/nall/sha256.hpp new file mode 100644 index 00000000..7f41f04e --- /dev/null +++ b/snesfilter/nall/sha256.hpp @@ -0,0 +1,143 @@ +#ifndef NALL_SHA256_HPP +#define NALL_SHA256_HPP + +//author: vladitx + +namespace nall { + #define PTR(t, a) ((t*)(a)) + + #define SWAP32(x) ((uint32_t)( \ + (((uint32_t)(x) & 0x000000ff) << 24) | \ + (((uint32_t)(x) & 0x0000ff00) << 8) | \ + (((uint32_t)(x) & 0x00ff0000) >> 8) | \ + (((uint32_t)(x) & 0xff000000) >> 24) \ + )) + + #define ST32(a, d) *PTR(uint32_t, a) = (d) + #define ST32BE(a, d) ST32(a, SWAP32(d)) + + #define LD32(a) *PTR(uint32_t, a) + #define LD32BE(a) SWAP32(LD32(a)) + + #define LSL32(x, n) ((uint32_t)(x) << (n)) + #define LSR32(x, n) ((uint32_t)(x) >> (n)) + #define ROR32(x, n) (LSR32(x, n) | LSL32(x, 32 - (n))) + + //first 32 bits of the fractional parts of the square roots of the first 8 primes 2..19 + static const uint32_t T_H[8] = { + 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19, + }; + + //first 32 bits of the fractional parts of the cube roots of the first 64 primes 2..311 + static const uint32_t T_K[64] = { + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, + 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, + 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, + 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, + 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, + 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2, + }; + + struct sha256_ctx { + uint8_t in[64]; + unsigned inlen; + + uint32_t w[64]; + uint32_t h[8]; + uint64_t len; + }; + + void sha256_init(sha256_ctx *p) { + memset(p, 0, sizeof(sha256_ctx)); + memcpy(p->h, T_H, sizeof(T_H)); + } + + static void sha256_block(sha256_ctx *p) { + unsigned i; + uint32_t s0, s1; + uint32_t a, b, c, d, e, f, g, h; + uint32_t t1, t2, maj, ch; + + for(i = 0; i < 16; i++) p->w[i] = LD32BE(p->in + i * 4); + + for(i = 16; i < 64; i++) { + s0 = ROR32(p->w[i - 15], 7) ^ ROR32(p->w[i - 15], 18) ^ LSR32(p->w[i - 15], 3); + s1 = ROR32(p->w[i - 2], 17) ^ ROR32(p->w[i - 2], 19) ^ LSR32(p->w[i - 2], 10); + p->w[i] = p->w[i - 16] + s0 + p->w[i - 7] + s1; + } + + a = p->h[0]; b = p->h[1]; c = p->h[2]; d = p->h[3]; + e = p->h[4]; f = p->h[5]; g = p->h[6]; h = p->h[7]; + + for(i = 0; i < 64; i++) { + s0 = ROR32(a, 2) ^ ROR32(a, 13) ^ ROR32(a, 22); + maj = (a & b) ^ (a & c) ^ (b & c); + t2 = s0 + maj; + s1 = ROR32(e, 6) ^ ROR32(e, 11) ^ ROR32(e, 25); + ch = (e & f) ^ (~e & g); + t1 = h + s1 + ch + T_K[i] + p->w[i]; + + h = g; g = f; f = e; e = d + t1; + d = c; c = b; b = a; a = t1 + t2; + } + + p->h[0] += a; p->h[1] += b; p->h[2] += c; p->h[3] += d; + p->h[4] += e; p->h[5] += f; p->h[6] += g; p->h[7] += h; + + //next block + p->inlen = 0; + } + + void sha256_chunk(sha256_ctx *p, const uint8_t *s, unsigned len) { + unsigned l; + p->len += len; + + while(len) { + l = 64 - p->inlen; + l = (len < l) ? len : l; + + memcpy(p->in + p->inlen, s, l); + s += l; + p->inlen += l; + len -= l; + + if(p->inlen == 64) sha256_block(p); + } + } + + void sha256_final(sha256_ctx *p) { + uint64_t len; + p->in[p->inlen++] = 0x80; + + if(p->inlen > 56) { + memset(p->in + p->inlen, 0, 64 - p->inlen); + sha256_block(p); + } + + memset(p->in + p->inlen, 0, 56 - p->inlen); + + len = p->len << 3; + ST32BE(p->in + 56, len >> 32); + ST32BE(p->in + 60, len); + sha256_block(p); + } + + void sha256_hash(sha256_ctx *p, uint8_t *s) { + uint32_t *t = (uint32_t*)s; + for(unsigned i = 0; i < 8; i++) ST32BE(t++, p->h[i]); + } + + #undef PTR + #undef SWAP32 + #undef ST32 + #undef ST32BE + #undef LD32 + #undef LD32BE + #undef LSL32 + #undef LSR32 + #undef ROR32 +} + +#endif diff --git a/snesfilter/nall/sort.hpp b/snesfilter/nall/sort.hpp new file mode 100644 index 00000000..23c317a5 --- /dev/null +++ b/snesfilter/nall/sort.hpp @@ -0,0 +1,62 @@ +#ifndef NALL_SORT_HPP +#define NALL_SORT_HPP + +#include + +//class: merge sort +//average: O(n log n) +//worst: O(n log n) +//memory: O(n) +//stack: O(log n) +//stable?: yes + +//notes: +//there are two primary reasons for choosing merge sort +//over the (usually) faster quick sort*: +//1: it is a stable sort. +//2: it lacks O(n^2) worst-case overhead. +//(* which is also O(n log n) in the average case.) + +namespace nall { + template + void sort(T list[], unsigned length) { + if(length <= 1) return; //nothing to sort + + //use insertion sort to quickly sort smaller blocks + if(length < 64) { + for(unsigned i = 0; i < length; i++) { + unsigned min = i; + for(unsigned j = i + 1; j < length; j++) { + if(list[j] < list[min]) min = j; + } + if(min != i) swap(list[i], list[min]); + } + return; + } + + //split list in half and recursively sort both + unsigned middle = length / 2; + sort(list, middle); + sort(list + middle, length - middle); + + //left and right are sorted here; perform merge sort + T *buffer = new T[length]; + unsigned offset = 0; + unsigned left = 0; + unsigned right = middle; + while(left < middle && right < length) { + if(list[left] < list[right]) { + buffer[offset++] = list[left++]; + } else { + buffer[offset++] = list[right++]; + } + } + while(left < middle) buffer[offset++] = list[left++]; + while(right < length) buffer[offset++] = list[right++]; + + for(unsigned i = 0; i < length; i++) list[i] = buffer[i]; + delete[] buffer; + } +} + +#endif diff --git a/snesfilter/nall/static.hpp b/snesfilter/nall/static.hpp new file mode 100644 index 00000000..4acb9fd0 --- /dev/null +++ b/snesfilter/nall/static.hpp @@ -0,0 +1,20 @@ +#ifndef NALL_STATIC_HPP +#define NALL_STATIC_HPP + +namespace nall { + template struct static_if { typedef T type; }; + template struct static_if { typedef F type; }; + template struct mp_static_if { typedef typename static_if::type type; }; + + template struct static_and { enum { value = false }; }; + template<> struct static_and { enum { value = true }; }; + template struct mp_static_and { enum { value = static_and::value }; }; + + template struct static_or { enum { value = false }; }; + template<> struct static_or { enum { value = true }; }; + template<> struct static_or { enum { value = true }; }; + template<> struct static_or { enum { value = true }; }; + template struct mp_static_or { enum { value = static_or::value }; }; +} + +#endif diff --git a/snesfilter/nall/stdint.hpp b/snesfilter/nall/stdint.hpp new file mode 100644 index 00000000..d8b6c788 --- /dev/null +++ b/snesfilter/nall/stdint.hpp @@ -0,0 +1,44 @@ +#ifndef NALL_STDINT_HPP +#define NALL_STDINT_HPP + +#include + +#if defined(_MSC_VER) + typedef signed char int8_t; + typedef signed short int16_t; + typedef signed int int32_t; + typedef signed long long int64_t; + typedef int64_t intmax_t; + #if defined(_WIN64) + typedef int64_t intptr_t; + #else + typedef int32_t intptr_t; + #endif + + typedef unsigned char uint8_t; + typedef unsigned short uint16_t; + typedef unsigned int uint32_t; + typedef unsigned long long uint64_t; + typedef uint64_t uintmax_t; + #if defined(_WIN64) + typedef uint64_t uintptr_t; + #else + typedef uint32_t uintptr_t; + #endif +#else + #include +#endif + +namespace nall { + static_assert(sizeof(int8_t) == 1, "int8_t is not of the correct size" ); + static_assert(sizeof(int16_t) == 2, "int16_t is not of the correct size"); + static_assert(sizeof(int32_t) == 4, "int32_t is not of the correct size"); + static_assert(sizeof(int64_t) == 8, "int64_t is not of the correct size"); + + static_assert(sizeof(uint8_t) == 1, "int8_t is not of the correct size" ); + static_assert(sizeof(uint16_t) == 2, "int16_t is not of the correct size"); + static_assert(sizeof(uint32_t) == 4, "int32_t is not of the correct size"); + static_assert(sizeof(uint64_t) == 8, "int64_t is not of the correct size"); +} + +#endif diff --git a/snesfilter/nall/string.hpp b/snesfilter/nall/string.hpp new file mode 100644 index 00000000..3ff0392c --- /dev/null +++ b/snesfilter/nall/string.hpp @@ -0,0 +1,27 @@ +#ifndef NALL_STRING_HPP +#define NALL_STRING_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace nall { + template<> struct has_length { enum { value = true }; }; + template<> struct has_size { enum { value = true }; }; +} + +#endif diff --git a/snesfilter/nall/string/base.hpp b/snesfilter/nall/string/base.hpp new file mode 100644 index 00000000..40d0e98c --- /dev/null +++ b/snesfilter/nall/string/base.hpp @@ -0,0 +1,136 @@ +#ifndef NALL_STRING_BASE_HPP +#define NALL_STRING_BASE_HPP + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace nall { + class string; + template inline string to_string(T); + + class string { + public: + inline void reserve(unsigned); + inline unsigned length() const; + + inline string& assign(const char*); + inline string& append(const char*); + template inline string& operator= (T value); + template inline string& operator<<(T value); + + inline operator const char*() const; + inline char* operator()(); + inline char& operator[](int); + + inline bool operator==(const char*) const; + inline bool operator!=(const char*) const; + inline bool operator< (const char*) const; + inline bool operator<=(const char*) const; + inline bool operator> (const char*) const; + inline bool operator>=(const char*) const; + + inline string& operator=(const string&); + inline string& operator=(string&&); + + inline string(); + inline string(const char*); + inline string(const string&); + inline string(string&&); + inline ~string(); + + inline bool readfile(const char*); + inline string& replace (const char*, const char*); + inline string& qreplace(const char*, const char*); + + protected: + char *data; + unsigned size; + + #if defined(QT_CORE_LIB) + public: + inline operator QString() const; + #endif + }; + + class lstring : public linear_vector { + public: + template inline lstring& operator<<(T value); + + inline int find(const char*); + inline void split (const char*, const char*, unsigned = 0); + inline void qsplit(const char*, const char*, unsigned = 0); + + lstring(); + lstring(std::initializer_list); + }; + + //compare.hpp + inline char chrlower(char c); + inline char chrupper(char c); + inline int stricmp(const char *dest, const char *src); + inline bool strbegin (const char *str, const char *key); + inline bool stribegin(const char *str, const char *key); + inline bool strend (const char *str, const char *key); + inline bool striend(const char *str, const char *key); + + //convert.hpp + inline char* strlower(char *str); + inline char* strupper(char *str); + inline char* strtr(char *dest, const char *before, const char *after); + inline uintmax_t strhex (const char *str); + inline intmax_t strsigned (const char *str); + inline uintmax_t strunsigned(const char *str); + inline uintmax_t strbin (const char *str); + inline double strdouble (const char *str); + + //match.hpp + inline bool match(const char *pattern, const char *str); + + //math.hpp + inline bool strint (const char *str, int &result); + inline bool strmath(const char *str, int &result); + + //strl.hpp + inline unsigned strlcpy(char *dest, const char *src, unsigned length); + inline unsigned strlcat(char *dest, const char *src, unsigned length); + + //trim.hpp + inline char* ltrim(char *str, const char *key = " "); + inline char* rtrim(char *str, const char *key = " "); + inline char* trim (char *str, const char *key = " "); + inline char* ltrim_once(char *str, const char *key = " "); + inline char* rtrim_once(char *str, const char *key = " "); + inline char* trim_once (char *str, const char *key = " "); + + //utility.hpp + inline unsigned strlcpy(string &dest, const char *src, unsigned length); + inline unsigned strlcat(string &dest, const char *src, unsigned length); + inline string substr(const char *src, unsigned start = 0, unsigned length = 0); + inline string& strlower(string &str); + inline string& strupper(string &str); + inline string& strtr(string &dest, const char *before, const char *after); + inline string& ltrim(string &str, const char *key = " "); + inline string& rtrim(string &str, const char *key = " "); + inline string& trim (string &str, const char *key = " "); + inline string& ltrim_once(string &str, const char *key = " "); + inline string& rtrim_once(string &str, const char *key = " "); + inline string& trim_once (string &str, const char *key = " "); + template inline string strhex(uintmax_t value); + template inline string strsigned(intmax_t value); + template inline string strunsigned(uintmax_t value); + template inline string strbin(uintmax_t value); + inline unsigned strdouble(char *str, double value); + inline string strdouble(double value); + + //variadic.hpp + template inline string sprint(Args... args); + template inline void print(Args... args); +}; + +#endif diff --git a/snesfilter/nall/string/cast.hpp b/snesfilter/nall/string/cast.hpp new file mode 100644 index 00000000..7b48eda0 --- /dev/null +++ b/snesfilter/nall/string/cast.hpp @@ -0,0 +1,32 @@ +#ifndef NALL_STRING_CAST_HPP +#define NALL_STRING_CAST_HPP + +namespace nall { + +//this is needed, as C++0x does not support explicit template specialization inside classes +template<> inline string to_string (bool v) { return v ? "true" : "false"; } +template<> inline string to_string (signed int v) { return strsigned(v); } +template<> inline string to_string (unsigned int v) { return strunsigned(v); } +template<> inline string to_string (double v) { return strdouble(v); } +template<> inline string to_string (char *v) { return v; } +template<> inline string to_string (const char *v) { return v; } +template<> inline string to_string (string v) { return v; } +template<> inline string to_string(const string &v) { return v; } + +template string& string::operator= (T value) { return assign(to_string(value)); } +template string& string::operator<<(T value) { return append(to_string(value)); } + +template lstring& lstring::operator<<(T value) { + operator[](size()).assign(to_string(value)); + return *this; +} + +#if defined(QT_CORE_LIB) +template<> inline string to_string(QString v) { return v.toUtf8().constData(); } +template<> inline string to_string(const QString &v) { return v.toUtf8().constData(); } +string::operator QString() const { return QString::fromUtf8(*this); } +#endif + +} + +#endif diff --git a/snesfilter/nall/string/compare.hpp b/snesfilter/nall/string/compare.hpp new file mode 100644 index 00000000..bd289753 --- /dev/null +++ b/snesfilter/nall/string/compare.hpp @@ -0,0 +1,72 @@ +#ifndef NALL_STRING_COMPARE_HPP +#define NALL_STRING_COMPARE_HPP + +namespace nall { + +char chrlower(char c) { + return (c >= 'A' && c <= 'Z') ? c + ('a' - 'A') : c; +} + +char chrupper(char c) { + return (c >= 'a' && c <= 'z') ? c - ('a' - 'A') : c; +} + +int stricmp(const char *dest, const char *src) { + while(*dest) { + if(chrlower(*dest) != chrlower(*src)) break; + dest++; + src++; + } + + return (int)chrlower(*dest) - (int)chrlower(*src); +} + +bool strbegin(const char *str, const char *key) { + int i, ssl = strlen(str), ksl = strlen(key); + + if(ksl > ssl) return false; + return (!memcmp(str, key, ksl)); +} + +bool stribegin(const char *str, const char *key) { + int ssl = strlen(str), ksl = strlen(key); + + if(ksl > ssl) return false; + for(int i = 0; i < ksl; i++) { + if(str[i] >= 'A' && str[i] <= 'Z') { + if(str[i] != key[i] && str[i]+0x20 != key[i])return false; + } else if(str[i] >= 'a' && str[i] <= 'z') { + if(str[i] != key[i] && str[i]-0x20 != key[i])return false; + } else { + if(str[i] != key[i])return false; + } + } + return true; +} + +bool strend(const char *str, const char *key) { + int ssl = strlen(str), ksl = strlen(key); + + if(ksl > ssl) return false; + return (!memcmp(str + ssl - ksl, key, ksl)); +} + +bool striend(const char *str, const char *key) { + int ssl = strlen(str), ksl = strlen(key); + + if(ksl > ssl) return false; + for(int i = ssl - ksl, z = 0; i < ssl; i++, z++) { + if(str[i] >= 'A' && str[i] <= 'Z') { + if(str[i] != key[z] && str[i]+0x20 != key[z])return false; + } else if(str[i] >= 'a' && str[i] <= 'z') { + if(str[i] != key[z] && str[i]-0x20 != key[z])return false; + } else { + if(str[i] != key[z])return false; + } + } + return true; +} + +} + +#endif diff --git a/snesfilter/nall/string/convert.hpp b/snesfilter/nall/string/convert.hpp new file mode 100644 index 00000000..3ff134be --- /dev/null +++ b/snesfilter/nall/string/convert.hpp @@ -0,0 +1,153 @@ +#ifndef NALL_STRING_CONVERT_HPP +#define NALL_STRING_CONVERT_HPP + +namespace nall { + +char* strlower(char *str) { + if(!str) return 0; + int i = 0; + while(str[i]) { + str[i] = chrlower(str[i]); + i++; + } + return str; +} + +char* strupper(char *str) { + if(!str) return 0; + int i = 0; + while(str[i]) { + str[i] = chrupper(str[i]); + i++; + } + return str; +} + +char* strtr(char *dest, const char *before, const char *after) { + if(!dest || !before || !after) return dest; + int sl = strlen(dest), bsl = strlen(before), asl = strlen(after); + + if(bsl != asl || bsl == 0) return dest; //patterns must be the same length for 1:1 replace + for(unsigned i = 0; i < sl; i++) { + for(unsigned l = 0; l < bsl; l++) { + if(dest[i] == before[l]) { + dest[i] = after[l]; + break; + } + } + } + + return dest; +} + +uintmax_t strhex(const char *str) { + if(!str) return 0; + uintmax_t result = 0; + + //skip hex identifiers 0x and $, if present + if(*str == '0' && (*(str + 1) == 'X' || *(str + 1) == 'x')) str += 2; + else if(*str == '$') str++; + + while(*str) { + uint8_t x = *str++; + if(x >= '0' && x <= '9') x -= '0'; + else if(x >= 'A' && x <= 'F') x -= 'A' - 10; + else if(x >= 'a' && x <= 'f') x -= 'a' - 10; + else break; //stop at first invalid character + result = result * 16 + x; + } + + return result; +} + +intmax_t strsigned(const char *str) { + if(!str) return 0; + intmax_t result = 0; + bool negate = false; + + //check for negation + if(*str == '-') { + negate = true; + str++; + } + + while(*str) { + uint8_t x = *str++; + if(x >= '0' && x <= '9') x -= '0'; + else break; //stop at first invalid character + result = result * 10 + x; + } + + return !negate ? result : -result; +} + +uintmax_t strunsigned(const char *str) { + if(!str) return 0; + uintmax_t result = 0; + + while(*str) { + uint8_t x = *str++; + if(x >= '0' && x <= '9') x -= '0'; + else break; //stop at first invalid character + result = result * 10 + x; + } + + return result; +} + +uintmax_t strbin(const char *str) { + if(!str) return 0; + uintmax_t result = 0; + + //skip bin identifiers 0b and %, if present + if(*str == '0' && (*(str + 1) == 'B' || *(str + 1) == 'b')) str += 2; + else if(*str == '%') str++; + + while(*str) { + uint8_t x = *str++; + if(x == '0' || x == '1') x -= '0'; + else break; //stop at first invalid character + result = result * 2 + x; + } + + return result; +} + +double strdouble(const char *str) { + if(!str) return 0.0; + bool negate = false; + + //check for negation + if(*str == '-') { + negate = true; + str++; + } + + intmax_t result_integral = 0; + while(*str) { + uint8_t x = *str++; + if(x >= '0' && x <= '9') x -= '0'; + else if(x == '.') break; //break loop and read fractional part + else return (double)result_integral; //invalid value, assume no fractional part + result_integral = result_integral * 10 + x; + } + + intmax_t result_fractional = 0; + while(*str) { + uint8_t x = *str++; + if(x >= '0' && x <= '9') x -= '0'; + else break; //stop at first invalid character + result_fractional = result_fractional * 10 + x; + } + + //calculate fractional portion + double result = (double)result_fractional; + while((uintmax_t)result > 0) result /= 10.0; + result += (double)result_integral; + + return !negate ? result : -result; +} + +} + +#endif diff --git a/snesfilter/nall/string/core.hpp b/snesfilter/nall/string/core.hpp new file mode 100644 index 00000000..f69802e4 --- /dev/null +++ b/snesfilter/nall/string/core.hpp @@ -0,0 +1,133 @@ +#ifndef NALL_STRING_CORE_HPP +#define NALL_STRING_CORE_HPP + +namespace nall { + +void string::reserve(unsigned size_) { + if(size_ > size) { + size = size_; + data = (char*)realloc(data, size + 1); + data[size] = 0; + } +} + +unsigned string::length() const { + return strlen(data); +} + +string& string::assign(const char *s) { + unsigned length = strlen(s); + reserve(length); + strcpy(data, s); + return *this; +} + +string& string::append(const char *s) { + unsigned length = strlen(data) + strlen(s); + reserve(length); + strcat(data, s); + return *this; +} + +string::operator const char*() const { + return data; +} + +char* string::operator()() { + return data; +} + +char& string::operator[](int index) { + reserve(index); + return data[index]; +} + +bool string::operator==(const char *str) const { return strcmp(data, str) == 0; } +bool string::operator!=(const char *str) const { return strcmp(data, str) != 0; } +bool string::operator< (const char *str) const { return strcmp(data, str) < 0; } +bool string::operator<=(const char *str) const { return strcmp(data, str) <= 0; } +bool string::operator> (const char *str) const { return strcmp(data, str) > 0; } +bool string::operator>=(const char *str) const { return strcmp(data, str) >= 0; } + +string& string::operator=(const string &value) { + assign(value); + return *this; +} + +string& string::operator=(string &&source) { + if(data) free(data); + size = source.size; + data = source.data; + source.data = 0; + source.size = 0; + return *this; +} + +string::string() { + size = 64; + data = (char*)malloc(size + 1); + *data = 0; +} + +string::string(const char *value) { + size = strlen(value); + data = strdup(value); +} + +string::string(const string &value) { + size = strlen(value); + data = strdup(value); +} + +string::string(string &&source) { + size = source.size; + data = source.data; + source.data = 0; +} + +string::~string() { + free(data); +} + +bool string::readfile(const char *filename) { + assign(""); + + #if !defined(_WIN32) + FILE *fp = fopen(filename, "rb"); + #else + FILE *fp = _wfopen(utf16_t(filename), L"rb"); + #endif + if(!fp) return false; + + fseek(fp, 0, SEEK_END); + unsigned size = ftell(fp); + rewind(fp); + char *fdata = new char[size + 1]; + unsigned unused = fread(fdata, 1, size, fp); + fclose(fp); + fdata[size] = 0; + assign(fdata); + delete[] fdata; + + return true; +} + +int lstring::find(const char *key) { + for(unsigned i = 0; i < size(); i++) { + if(operator[](i) == key) return i; + } + return -1; +} + +inline lstring::lstring() { +} + +inline lstring::lstring(std::initializer_list list) { + for(const string *s = list.begin(); s != list.end(); ++s) { + operator<<(*s); + } +} + +} + +#endif diff --git a/snesfilter/nall/string/filename.hpp b/snesfilter/nall/string/filename.hpp new file mode 100644 index 00000000..f3750760 --- /dev/null +++ b/snesfilter/nall/string/filename.hpp @@ -0,0 +1,61 @@ +#ifndef NALL_FILENAME_HPP +#define NALL_FILENAME_HPP + +namespace nall { + +// "foo/bar.c" -> "foo/", "bar.c" -> "./" +inline string dir(char const *name) { + string result = name; + for(signed i = strlen(result); i >= 0; i--) { + if(result[i] == '/' || result[i] == '\\') { + result[i + 1] = 0; + break; + } + if(i == 0) result = "./"; + } + return result; +} + +// "foo/bar.c" -> "bar.c" +inline string notdir(char const *name) { + for(signed i = strlen(name); i >= 0; i--) { + if(name[i] == '/' || name[i] == '\\') { + name += i + 1; + break; + } + } + string result = name; + return result; +} + +// "foo/bar.c" -> "foo/bar" +inline string basename(char const *name) { + string result = name; + for(signed i = strlen(result); i >= 0; i--) { + if(result[i] == '/' || result[i] == '\\') { + //file has no extension + break; + } + if(result[i] == '.') { + result[i] = 0; + break; + } + } + return result; +} + +// "foo/bar.c" -> "c" +inline string extension(char const *name) { + for(signed i = strlen(name); i >= 0; i--) { + if(name[i] == '.') { + name += i + 1; + break; + } + } + string result = name; + return result; +} + +} + +#endif diff --git a/snesfilter/nall/string/match.hpp b/snesfilter/nall/string/match.hpp new file mode 100644 index 00000000..d8cf702d --- /dev/null +++ b/snesfilter/nall/string/match.hpp @@ -0,0 +1,76 @@ +#ifndef NALL_STRING_MATCH_HPP +#define NALL_STRING_MATCH_HPP + +namespace nall { + +bool match(const char *p, const char *s) { + const char *p_ = 0, *s_ = 0; + + for(;;) { + if(!*s) { + while(*p == '*') p++; + return !*p; + } + + //wildcard match + if(*p == '*') { + p_ = p++, s_ = s; + continue; + } + + //any match + if(*p == '?') { + p++, s++; + continue; + } + + //ranged match + if(*p == '{') { + #define pattern(name_, rule_) \ + if(strbegin(p, name_)) { \ + if(rule_) { \ + p += sizeof(name_) - 1, s++; \ + continue; \ + } \ + goto failure; \ + } + + pattern("{alpha}", (*s >= 'A' && *s <= 'Z') || (*s >= 'a' && *s <= 'z')) + pattern("{alphanumeric}", (*s >= 'A' && *s <= 'Z') || (*s >= 'a' && *s <= 'z') || (*s >= '0' && *s <= '9')) + pattern("{binary}", (*s == '0' || *s == '1')) + pattern("{hex}", (*s >= '0' && *s <= '9') || (*s >= 'A' && *s <= 'F') || (*s >= 'a' && *s <= 'f')) + pattern("{lowercase}", (*s >= 'a' && *s <= 'z')) + pattern("{numeric}", (*s >= '0' && *s <= '9')) + pattern("{uppercase}", (*s >= 'A' && *s <= 'Z')) + pattern("{whitespace}", (*s == ' ' || *s == '\t')) + + #undef pattern + goto failure; + } + + //reserved character match + if(*p == '\\') { + p++; + //fallthrough + } + + //literal match + if(*p == *s) { + p++, *s++; + continue; + } + + //attempt wildcard rematch + failure: + if(p_) { + p = p_, s = s_ + 1; + continue; + } + + return false; + } +} + +} + +#endif diff --git a/snesfilter/nall/string/math.hpp b/snesfilter/nall/string/math.hpp new file mode 100644 index 00000000..ea8b99c8 --- /dev/null +++ b/snesfilter/nall/string/math.hpp @@ -0,0 +1,164 @@ +#ifndef NALL_STRING_MATH_HPP +#define NALL_STRING_MATH_HPP + +namespace nall { + +static int eval_integer(const char *&s) { + if(!*s) throw "unrecognized_integer"; + int value = 0, x = *s, y = *(s + 1); + + //hexadecimal + if(x == '0' && (y == 'X' || y == 'x')) { + s += 2; + while(true) { + if(*s >= '0' && *s <= '9') { value = value * 16 + (*s++ - '0'); continue; } + if(*s >= 'A' && *s <= 'F') { value = value * 16 + (*s++ - 'A' + 10); continue; } + if(*s >= 'a' && *s <= 'f') { value = value * 16 + (*s++ - 'a' + 10); continue; } + return value; + } + } + + //binary + if(x == '0' && (y == 'B' || y == 'b')) { + s += 2; + while(true) { + if(*s == '0' || *s == '1') { value = value * 2 + (*s++ - '0'); continue; } + return value; + } + } + + //octal (or decimal '0') + if(x == '0') { + s += 1; + while(true) { + if(*s >= '0' && *s <= '7') { value = value * 8 + (*s++ - '0'); continue; } + return value; + } + } + + //decimal + if(x >= '0' && x <= '9') { + while(true) { + if(*s >= '0' && *s <= '9') { value = value * 10 + (*s++ - '0'); continue; } + return value; + } + } + + //char + if(x == '\'' && y != '\'') { + s += 1; + while(true) { + value = value * 256 + *s++; + if(*s == '\'') { s += 1; return value; } + if(!*s) throw "mismatched_char"; + } + } + + throw "unrecognized_integer"; +} + +static int eval(const char *&s, int depth = 0) { + while(*s == ' ' || *s == '\t') s++; //trim whitespace + if(!*s) throw "unrecognized_token"; + int value = 0, x = *s, y = *(s + 1); + + if(*s == '(') { + value = eval(++s, 1); + if(*s++ != ')') throw "mismatched_group"; + } + + else if(x == '!') value = !eval(++s, 13); + else if(x == '~') value = ~eval(++s, 13); + else if(x == '+') value = +eval(++s, 13); + else if(x == '-') value = -eval(++s, 13); + + else if((x >= '0' && x <= '9') || x == '\'') value = eval_integer(s); + + else throw "unrecognized_token"; + + while(true) { + while(*s == ' ' || *s == '\t') s++; //trim whitespace + if(!*s) break; + x = *s, y = *(s + 1); + + if(depth >= 13) break; + if(x == '*') { value *= eval(++s, 13); continue; } + if(x == '/') { value /= eval(++s, 13); continue; } + if(x == '%') { value %= eval(++s, 13); continue; } + + if(depth >= 12) break; + if(x == '+') { value += eval(++s, 12); continue; } + if(x == '-') { value -= eval(++s, 12); continue; } + + if(depth >= 11) break; + if(x == '<' && y == '<') { value <<= eval(++++s, 11); continue; } + if(x == '>' && y == '>') { value >>= eval(++++s, 11); continue; } + + if(depth >= 10) break; + if(x == '<' && y == '=') { value = value <= eval(++++s, 10); continue; } + if(x == '>' && y == '=') { value = value >= eval(++++s, 10); continue; } + if(x == '<') { value = value < eval(++s, 10); continue; } + if(x == '>') { value = value > eval(++s, 10); continue; } + + if(depth >= 9) break; + if(x == '=' && y == '=') { value = value == eval(++++s, 9); continue; } + if(x == '!' && y == '=') { value = value != eval(++++s, 9); continue; } + + if(depth >= 8) break; + if(x == '&' && y != '&') { value = value & eval(++s, 8); continue; } + + if(depth >= 7) break; + if(x == '^' && y != '^') { value = value ^ eval(++s, 7); continue; } + + if(depth >= 6) break; + if(x == '|' && y != '|') { value = value | eval(++s, 6); continue; } + + if(depth >= 5) break; + if(x == '&' && y == '&') { value = eval(++++s, 5) && value; continue; } + + if(depth >= 4) break; + if(x == '^' && y == '^') { value = (!eval(++++s, 4) != !value); continue; } + + if(depth >= 3) break; + if(x == '|' && y == '|') { value = eval(++++s, 3) || value; continue; } + + if(x == '?') { + int lhs = eval(++s, 2); + if(*s != ':') throw "mismatched_ternary"; + int rhs = eval(++s, 2); + value = value ? lhs : rhs; + continue; + } + if(depth >= 2) break; + + if(depth > 0 && x == ')') break; + + throw "unrecognized_token"; + } + + return value; +} + +bool strint(const char *s, int &result) { + try { + result = eval_integer(s); + return true; + } catch(const char*) { + result = 0; + return false; + } +} + +bool strmath(const char *s, int &result) { + try { + result = eval(s); + return true; + } catch(const char*) { + result = 0; + return false; + } +} + +} + +#endif diff --git a/snesfilter/nall/string/replace.hpp b/snesfilter/nall/string/replace.hpp new file mode 100644 index 00000000..db405a9b --- /dev/null +++ b/snesfilter/nall/string/replace.hpp @@ -0,0 +1,103 @@ +#ifndef NALL_STRING_REPLACE_HPP +#define NALL_STRING_REPLACE_HPP + +namespace nall { + +string& string::replace(const char *key, const char *token) { + int i, z, ksl = strlen(key), tsl = strlen(token), ssl = length(); + unsigned int replace_count = 0, size = ssl; + char *buffer; + + if(ksl <= ssl) { + if(tsl > ksl) { //the new string may be longer than the old string... + for(i = 0; i <= ssl - ksl;) { //so let's find out how big of a string we'll need... + if(!memcmp(data + i, key, ksl)) { + replace_count++; + i += ksl; + } else i++; + } + size = ssl + ((tsl - ksl) * replace_count); + reserve(size); + } + + buffer = new char[size + 1]; + for(i = z = 0; i < ssl;) { + if(i <= ssl - ksl) { + if(!memcmp(data + i, key, ksl)) { + memcpy(buffer + z, token, tsl); + z += tsl; + i += ksl; + } else buffer[z++] = data[i++]; + } else buffer[z++] = data[i++]; + } + buffer[z] = 0; + + assign(buffer); + delete[] buffer; + } + + return *this; +} + +string& string::qreplace(const char *key, const char *token) { + int i, l, z, ksl = strlen(key), tsl = strlen(token), ssl = length(); + unsigned int replace_count = 0, size = ssl; + uint8_t x; + char *buffer; + + if(ksl <= ssl) { + if(tsl > ksl) { + for(i = 0; i <= ssl - ksl;) { + x = data[i]; + if(x == '\"' || x == '\'') { + l = i; + i++; + while(data[i++] != x) { + if(i == ssl) { + i = l; + break; + } + } + } + if(!memcmp(data + i, key, ksl)) { + replace_count++; + i += ksl; + } else i++; + } + size = ssl + ((tsl - ksl) * replace_count); + reserve(size); + } + + buffer = new char[size + 1]; + for(i = z = 0; i < ssl;) { + x = data[i]; + if(x == '\"' || x == '\'') { + l = i++; + while(data[i] != x && i < ssl)i++; + if(i >= ssl)i = l; + else { + memcpy(buffer + z, data + l, i - l); + z += i - l; + } + } + if(i <= ssl - ksl) { + if(!memcmp(data + i, key, ksl)) { + memcpy(buffer + z, token, tsl); + z += tsl; + i += ksl; + replace_count++; + } else buffer[z++] = data[i++]; + } else buffer[z++] = data[i++]; + } + buffer[z] = 0; + + assign(buffer); + delete[] buffer; + } + + return *this; +} + +}; + +#endif diff --git a/snesfilter/nall/string/split.hpp b/snesfilter/nall/string/split.hpp new file mode 100644 index 00000000..bb77dfcd --- /dev/null +++ b/snesfilter/nall/string/split.hpp @@ -0,0 +1,56 @@ +#ifndef NALL_STRING_SPLIT_HPP +#define NALL_STRING_SPLIT_HPP + +namespace nall { + +void lstring::split(const char *key, const char *src, unsigned limit) { + reset(); + + int ssl = strlen(src), ksl = strlen(key); + int lp = 0, split_count = 0; + + for(int i = 0; i <= ssl - ksl;) { + if(!memcmp(src + i, key, ksl)) { + strlcpy(operator[](split_count++), src + lp, i - lp + 1); + i += ksl; + lp = i; + if(!--limit) break; + } else i++; + } + + operator[](split_count++) = src + lp; +} + +void lstring::qsplit(const char *key, const char *src, unsigned limit) { + reset(); + + int ssl = strlen(src), ksl = strlen(key); + int lp = 0, split_count = 0; + + for(int i = 0; i <= ssl - ksl;) { + uint8_t x = src[i]; + + if(x == '\"' || x == '\'') { + int z = i++; //skip opening quote + while(i < ssl && src[i] != x) i++; + if(i >= ssl) i = z; //failed match, rewind i + else { + i++; //skip closing quote + continue; //restart in case next char is also a quote + } + } + + if(!memcmp(src + i, key, ksl)) { + strlcpy(operator[](split_count++), src + lp, i - lp + 1); + i += ksl; + lp = i; + if(!--limit) break; + } else i++; + } + + operator[](split_count++) = src + lp; +} + +}; + +#endif diff --git a/snesfilter/nall/string/strl.hpp b/snesfilter/nall/string/strl.hpp new file mode 100644 index 00000000..84c841fa --- /dev/null +++ b/snesfilter/nall/string/strl.hpp @@ -0,0 +1,52 @@ +#ifndef NALL_STRING_STRL_HPP +#define NALL_STRING_STRL_HPP + +namespace nall { + +//strlcpy, strlcat based on OpenBSD implementation by Todd C. Miller + +//return = strlen(src) +unsigned strlcpy(char *dest, const char *src, unsigned length) { + char *d = dest; + const char *s = src; + unsigned n = length; + + if(n) { + while(--n && (*d++ = *s++)); //copy as many bytes as possible, or until null terminator reached + } + + if(!n) { + if(length) *d = 0; + while(*s++); //traverse rest of s, so that s - src == strlen(src) + } + + return (s - src - 1); //return length of copied string, sans null terminator +} + +//return = strlen(src) + min(length, strlen(dest)) +unsigned strlcat(char *dest, const char *src, unsigned length) { + char *d = dest; + const char *s = src; + unsigned n = length; + + while(n-- && *d) d++; //find end of dest + unsigned dlength = d - dest; + n = length - dlength; //subtract length of dest from maximum string length + + if(!n) return dlength + strlen(s); + + while(*s) { + if(n != 1) { + *d++ = *s; + n--; + } + s++; + } + *d = 0; + + return dlength + (s - src); //return length of resulting string, sans null terminator +} + +} + +#endif diff --git a/snesfilter/nall/string/strpos.hpp b/snesfilter/nall/string/strpos.hpp new file mode 100644 index 00000000..a1bf85b4 --- /dev/null +++ b/snesfilter/nall/string/strpos.hpp @@ -0,0 +1,60 @@ +#ifndef NALL_STRING_STRPOS_HPP +#define NALL_STRING_STRPOS_HPP + +//usage example: +//if(auto pos = strpos(str, key)) print(pos(), "\n"); +//prints position of key within str, only if it is found + +namespace nall { + +class strpos { + bool found; + unsigned position; +public: + inline operator bool() const { return found; } + inline unsigned operator()() const { return position; } + inline strpos(const char *str, const char *key) : found(false), position(0) { + unsigned ssl = strlen(str), ksl = strlen(key); + if(ksl > ssl) return; + + for(unsigned i = 0; i <= ssl - ksl; i++) { + if(!memcmp(str + i, key, ksl)) { + found = true; + position = i; + return; + } + } + } +}; + +class qstrpos { + bool found; + unsigned position; +public: + inline operator bool() const { return found; } + inline unsigned operator()() const { return position; } + inline qstrpos(const char *str, const char *key) : found(false), position(0) { + unsigned ssl = strlen(str), ksl = strlen(key); + if(ksl > ssl) return; + + for(unsigned i = 0; i <= ssl - ksl;) { + uint8_t x = str[i]; + if(x == '\"' || x == '\'') { + uint8_t z = i++; + while(str[i] != x && i < ssl) i++; + if(i >= ssl) i = z; + } + if(!memcmp(str + i, key, ksl)) { + found = true; + position = i; + return; + } else { + i++; + } + } + } +}; + +} + +#endif diff --git a/snesfilter/nall/string/trim.hpp b/snesfilter/nall/string/trim.hpp new file mode 100644 index 00000000..b13ab9ba --- /dev/null +++ b/snesfilter/nall/string/trim.hpp @@ -0,0 +1,54 @@ +#ifndef NALL_STRING_TRIM_HPP +#define NALL_STRING_TRIM_HPP + +namespace nall { + +char* ltrim(char *str, const char *key) { + if(!key || !*key) return str; + while(strbegin(str, key)) { + char *dest = str, *src = str + strlen(key); + while(true) { + *dest = *src++; + if(!*dest) break; + dest++; + } + } + return str; +} + +char* rtrim(char *str, const char *key) { + if(!key || !*key) return str; + while(strend(str, key)) str[strlen(str) - strlen(key)] = 0; + return str; +} + +char* trim(char *str, const char *key) { + return ltrim(rtrim(str, key), key); +} + +char* ltrim_once(char *str, const char *key) { + if(!key || !*key) return str; + if(strbegin(str, key)) { + char *dest = str, *src = str + strlen(key); + while(true) { + *dest = *src++; + if(!*dest) break; + dest++; + } + } + return str; +} + +char* rtrim_once(char *str, const char *key) { + if(!key || !*key) return str; + if(strend(str, key)) str[strlen(str) - strlen(key)] = 0; + return str; +} + +char* trim_once(char *str, const char *key) { + return ltrim_once(rtrim_once(str, key), key); +} + +} + +#endif diff --git a/snesfilter/nall/string/utility.hpp b/snesfilter/nall/string/utility.hpp new file mode 100644 index 00000000..2da2762b --- /dev/null +++ b/snesfilter/nall/string/utility.hpp @@ -0,0 +1,169 @@ +#ifndef NALL_STRING_UTILITY_HPP +#define NALL_STRING_UTILITY_HPP + +namespace nall { + +unsigned strlcpy(string &dest, const char *src, unsigned length) { + dest.reserve(length); + return strlcpy(dest(), src, length); +} + +unsigned strlcat(string &dest, const char *src, unsigned length) { + dest.reserve(length); + return strlcat(dest(), src, length); +} + +string substr(const char *src, unsigned start, unsigned length) { + string dest; + if(length == 0) { + //copy entire string + dest = src + start; + } else { + //copy partial string + strlcpy(dest, src + start, length + 1); + } + return dest; +} + +/* very simplistic wrappers to return string& instead of char* type */ + +string& strlower(string &str) { strlower(str()); return str; } +string& strupper(string &str) { strupper(str()); return str; } +string& strtr(string &dest, const char *before, const char *after) { strtr(dest(), before, after); return dest; } +string& ltrim(string &str, const char *key) { ltrim(str(), key); return str; } +string& rtrim(string &str, const char *key) { rtrim(str(), key); return str; } +string& trim (string &str, const char *key) { trim (str(), key); return str; } +string& ltrim_once(string &str, const char *key) { ltrim_once(str(), key); return str; } +string& rtrim_once(string &str, const char *key) { rtrim_once(str(), key); return str; } +string& trim_once (string &str, const char *key) { trim_once (str(), key); return str; } + +/* arithmetic <> string */ + +template string strhex(uintmax_t value) { + string output; + unsigned offset = 0; + + //render string backwards, as we do not know its length yet + do { + unsigned n = value & 15; + output[offset++] = n < 10 ? '0' + n : 'a' + n - 10; + value >>= 4; + } while(value); + + while(offset < length) output[offset++] = padding; + output[offset--] = 0; + + //reverse the string in-place + for(unsigned i = 0; i < (offset + 1) >> 1; i++) { + char temp = output[i]; + output[i] = output[offset - i]; + output[offset - i] = temp; + } + + return output; +} + +template string strsigned(intmax_t value) { + string output; + unsigned offset = 0; + + bool negative = value < 0; + if(negative) value = abs(value); + + do { + unsigned n = value % 10; + output[offset++] = '0' + n; + value /= 10; + } while(value); + + while(offset < length) output[offset++] = padding; + if(negative) output[offset++] = '-'; + output[offset--] = 0; + + for(unsigned i = 0; i < (offset + 1) >> 1; i++) { + char temp = output[i]; + output[i] = output[offset - i]; + output[offset - i] = temp; + } + + return output; +} + +template string strunsigned(uintmax_t value) { + string output; + unsigned offset = 0; + + do { + unsigned n = value % 10; + output[offset++] = '0' + n; + value /= 10; + } while(value); + + while(offset < length) output[offset++] = padding; + output[offset--] = 0; + + for(unsigned i = 0; i < (offset + 1) >> 1; i++) { + char temp = output[i]; + output[i] = output[offset - i]; + output[offset - i] = temp; + } + + return output; +} + +template string strbin(uintmax_t value) { + string output; + unsigned offset = 0; + + do { + unsigned n = value & 1; + output[offset++] = '0' + n; + value >>= 1; + } while(value); + + while(offset < length) output[offset++] = padding; + output[offset--] = 0; + + for(unsigned i = 0; i < (offset + 1) >> 1; i++) { + char temp = output[i]; + output[i] = output[offset - i]; + output[offset - i] = temp; + } + + return output; +} + +//using sprintf is certainly not the most ideal method to convert +//a double to a string ... but attempting to parse a double by +//hand, digit-by-digit, results in subtle rounding errors. +unsigned strdouble(char *str, double value) { + char buffer[256]; + sprintf(buffer, "%f", value); + + //remove excess 0's in fraction (2.500000 -> 2.5) + for(char *p = buffer; *p; p++) { + if(*p == '.') { + char *p = buffer + strlen(buffer) - 1; + while(*p == '0') { + if(*(p - 1) != '.') *p = 0; //... but not for eg 1.0 -> 1. + p--; + } + break; + } + } + + unsigned length = strlen(buffer); + if(str) strcpy(str, buffer); + return length + 1; +} + +string strdouble(double value) { + string temp; + temp.reserve(strdouble(0, value)); + strdouble(temp(), value); + return temp; +} + +} + +#endif diff --git a/snesfilter/nall/string/variadic.hpp b/snesfilter/nall/string/variadic.hpp new file mode 100644 index 00000000..13c477a8 --- /dev/null +++ b/snesfilter/nall/string/variadic.hpp @@ -0,0 +1,27 @@ +#ifndef NALL_STRING_VARIADIC_HPP +#define NALL_STRING_VARIADIC_HPP + +namespace nall { + +static void isprint(string &output) { +} + +template +static void isprint(string &output, T value, Args... args) { + output << to_string(value); + isprint(output, args...); +} + +template inline string sprint(Args... args) { + string output; + isprint(output, args...); + return output; +} + +template inline void print(Args... args) { + printf("%s", (const char*)sprint(args...)); +} + +} + +#endif diff --git a/snesfilter/nall/string/xml.hpp b/snesfilter/nall/string/xml.hpp new file mode 100644 index 00000000..218b4cbf --- /dev/null +++ b/snesfilter/nall/string/xml.hpp @@ -0,0 +1,265 @@ +#ifndef NALL_STRING_XML_HPP +#define NALL_STRING_XML_HPP + +//XML subset parser +//version 0.05 + +namespace nall { + +struct xml_attribute { + string name; + string content; + virtual string parse() const; +}; + +struct xml_element : xml_attribute { + string parse() const; + linear_vector attribute; + linear_vector element; + +protected: + void parse_doctype(const char *&data); + bool parse_head(string data); + bool parse_body(const char *&data); + friend xml_element xml_parse(const char *data); +}; + +inline string xml_attribute::parse() const { + string data; + unsigned offset = 0; + + const char *source = content; + while(*source) { + if(*source == '&') { + if(strbegin(source, "<")) { data[offset++] = '<'; source += 4; continue; } + if(strbegin(source, ">")) { data[offset++] = '>'; source += 4; continue; } + if(strbegin(source, "&")) { data[offset++] = '&'; source += 5; continue; } + if(strbegin(source, "'")) { data[offset++] = '\''; source += 6; continue; } + if(strbegin(source, """)) { data[offset++] = '"'; source += 6; continue; } + } + + //reject illegal characters + if(*source == '&') return ""; + if(*source == '<') return ""; + if(*source == '>') return ""; + + data[offset++] = *source++; + } + + data[offset] = 0; + return data; +} + +inline string xml_element::parse() const { + string data; + unsigned offset = 0; + + const char *source = content; + while(*source) { + if(*source == '&') { + if(strbegin(source, "<")) { data[offset++] = '<'; source += 4; continue; } + if(strbegin(source, ">")) { data[offset++] = '>'; source += 4; continue; } + if(strbegin(source, "&")) { data[offset++] = '&'; source += 5; continue; } + if(strbegin(source, "'")) { data[offset++] = '\''; source += 6; continue; } + if(strbegin(source, """)) { data[offset++] = '"'; source += 6; continue; } + } + + if(strbegin(source, "")) { + source += pos() + 3; + continue; + } else { + return ""; + } + } + + if(strbegin(source, "")) { + string cdata = substr(source, 9, pos() - 9); + data << cdata; + offset += strlen(cdata); + + source += offset + 3; + continue; + } else { + return ""; + } + } + + //reject illegal characters + if(*source == '&') return ""; + if(*source == '<') return ""; + if(*source == '>') return ""; + + data[offset++] = *source++; + } + + data[offset] = 0; + return data; +} + +inline void xml_element::parse_doctype(const char *&data) { + name = "!DOCTYPE"; + const char *content_begin = data; + + signed counter = 0; + while(*data) { + char value = *data++; + if(value == '<') counter++; + if(value == '>') counter--; + if(counter < 0) { + content = substr(content_begin, 0, data - content_begin - 1); + return; + } + } + throw "..."; +} + +inline bool xml_element::parse_head(string data) { + data.qreplace("\t", " "); + data.qreplace("\r", " "); + data.qreplace("\n", " "); + while(qstrpos(data, " ")) data.qreplace(" ", " "); + data.qreplace(" =", "="); + data.qreplace("= ", "="); + rtrim(data); + + lstring part; + part.qsplit(" ", data); + + name = part[0]; + if(name == "") throw "..."; + + for(unsigned i = 1; i < part.size(); i++) { + lstring side; + side.qsplit("=", part[i]); + if(side.size() != 2) throw "..."; + + xml_attribute attr; + attr.name = side[0]; + attr.content = side[1]; + if(strbegin(attr.content, "\"") && strend(attr.content, "\"")) trim_once(attr.content, "\""); + else if(strbegin(attr.content, "'") && strend(attr.content, "'")) trim_once(attr.content, "'"); + else throw "..."; + attribute.add(attr); + } +} + +inline bool xml_element::parse_body(const char *&data) { + while(true) { + if(!*data) return false; + if(*data++ != '<') continue; + if(*data == '/') return false; + + if(strbegin(data, "!DOCTYPE") == true) { + parse_doctype(data); + return true; + } + + if(strbegin(data, "!--")) { + if(auto offset = strpos(data, "-->")) { + data += offset() + 3; + continue; + } else { + throw "..."; + } + } + + if(strbegin(data, "![CDATA[")) { + if(auto offset = strpos(data, "]]>")) { + data += offset() + 3; + continue; + } else { + throw "..."; + } + } + + auto offset = strpos(data, ">"); + if(!offset) throw "..."; + + string tag = substr(data, 0, offset()); + data += offset() + 1; + const char *content_begin = data; + + bool self_terminating = false; + + if(strend(tag, "?") == true) { + self_terminating = true; + rtrim_once(tag, "?"); + } else if(strend(tag, "/") == true) { + self_terminating = true; + rtrim_once(tag, "/"); + } + + parse_head(tag); + if(self_terminating) return true; + + while(*data) { + unsigned index = element.size(); + xml_element node; + if(node.parse_body(data) == false) { + if(*data == '/') { + signed length = data - content_begin - 1; + if(length > 0) content = substr(content_begin, 0, length); + + data++; + auto offset = strpos(data, ">"); + if(!offset) throw "..."; + + tag = substr(data, 0, offset()); + data += offset() + 1; + + tag.replace("\t", " "); + tag.replace("\r", " "); + tag.replace("\n", " "); + while(strpos(tag, " ")) tag.replace(" ", " "); + rtrim(tag); + + if(name != tag) throw "..."; + return true; + } + } else { + element.add(node); + } + } + } +} + +//ensure there is only one root element +inline bool xml_validate(xml_element &document) { + unsigned root_counter = 0; + + for(unsigned i = 0; i < document.element.size(); i++) { + string &name = document.element[i].name; + if(strbegin(name, "?")) continue; + if(strbegin(name, "!")) continue; + if(++root_counter > 1) return false; + } + + return true; +} + +inline xml_element xml_parse(const char *data) { + xml_element self; + + try { + while(*data) { + xml_element node; + if(node.parse_body(data) == false) { + break; + } else { + self.element.add(node); + } + } + + if(xml_validate(self) == false) throw "..."; + return self; + } catch(const char*) { + xml_element empty; + return empty; + } +} + +} + +#endif diff --git a/snesfilter/nall/ups.hpp b/snesfilter/nall/ups.hpp new file mode 100644 index 00000000..f255ecb3 --- /dev/null +++ b/snesfilter/nall/ups.hpp @@ -0,0 +1,190 @@ +#ifndef NALL_UPS_HPP +#define NALL_UPS_HPP + +#include + +#include +#include +#include +#include + +namespace nall { + class ups { + public: + enum result { + ok, + patch_unreadable, + patch_unwritable, + patch_invalid, + input_invalid, + output_invalid, + patch_crc32_invalid, + input_crc32_invalid, + output_crc32_invalid, + }; + + ups::result create(const char *patch_fn, const uint8_t *x_data, unsigned x_size, const uint8_t *y_data, unsigned y_size) { + if(!fp.open(patch_fn, file::mode_write)) return patch_unwritable; + + crc32 = ~0; + uint32_t x_crc32 = crc32_calculate(x_data, x_size); + uint32_t y_crc32 = crc32_calculate(y_data, y_size); + + //header + write('U'); + write('P'); + write('S'); + write('1'); + encptr(x_size); + encptr(y_size); + + //body + unsigned max_size = max(x_size, y_size); + unsigned relative = 0; + for(unsigned i = 0; i < max_size;) { + uint8_t x = i < x_size ? x_data[i] : 0x00; + uint8_t y = i < y_size ? y_data[i] : 0x00; + + if(x == y) { + i++; + continue; + } + + encptr(i++ - relative); + write(x ^ y); + + while(true) { + if(i >= max_size) { + write(0x00); + break; + } + + x = i < x_size ? x_data[i] : 0x00; + y = i < y_size ? y_data[i] : 0x00; + i++; + write(x ^ y); + if(x == y) break; + } + + relative = i; + } + + //footer + for(unsigned i = 0; i < 4; i++) write(x_crc32 >> (i << 3)); + for(unsigned i = 0; i < 4; i++) write(y_crc32 >> (i << 3)); + uint32_t p_crc32 = ~crc32; + for(unsigned i = 0; i < 4; i++) write(p_crc32 >> (i << 3)); + + fp.close(); + return ok; + } + + ups::result apply(const uint8_t *p_data, unsigned p_size, const uint8_t *x_data, unsigned x_size, uint8_t *&y_data, unsigned &y_size) { + if(p_size < 18) return patch_invalid; + p_buffer = p_data; + + crc32 = ~0; + + //header + if(read() != 'U') return patch_invalid; + if(read() != 'P') return patch_invalid; + if(read() != 'S') return patch_invalid; + if(read() != '1') return patch_invalid; + + unsigned px_size = decptr(); + unsigned py_size = decptr(); + + //mirror + if(x_size != px_size && x_size != py_size) return input_invalid; + y_size = (x_size == px_size) ? py_size : px_size; + y_data = new uint8_t[y_size](); + + for(unsigned i = 0; i < x_size && i < y_size; i++) y_data[i] = x_data[i]; + for(unsigned i = x_size; i < y_size; i++) y_data[i] = 0x00; + + //body + unsigned relative = 0; + while(p_buffer < p_data + p_size - 12) { + relative += decptr(); + + while(true) { + uint8_t x = read(); + if(x && relative < y_size) { + uint8_t y = relative < x_size ? x_data[relative] : 0x00; + y_data[relative] = x ^ y; + } + relative++; + if(!x) break; + } + } + + //footer + unsigned px_crc32 = 0, py_crc32 = 0, pp_crc32 = 0; + for(unsigned i = 0; i < 4; i++) px_crc32 |= read() << (i << 3); + for(unsigned i = 0; i < 4; i++) py_crc32 |= read() << (i << 3); + uint32_t p_crc32 = ~crc32; + for(unsigned i = 0; i < 4; i++) pp_crc32 |= read() << (i << 3); + + uint32_t x_crc32 = crc32_calculate(x_data, x_size); + uint32_t y_crc32 = crc32_calculate(y_data, y_size); + + if(px_size != py_size) { + if(x_size == px_size && x_crc32 != px_crc32) return input_crc32_invalid; + if(x_size == py_size && x_crc32 != py_crc32) return input_crc32_invalid; + if(y_size == px_size && y_crc32 != px_crc32) return output_crc32_invalid; + if(y_size == py_size && y_crc32 != py_crc32) return output_crc32_invalid; + } else { + if(x_crc32 != px_crc32 && x_crc32 != py_crc32) return input_crc32_invalid; + if(y_crc32 != px_crc32 && y_crc32 != py_crc32) return output_crc32_invalid; + if(x_crc32 == y_crc32 && px_crc32 != py_crc32) return output_crc32_invalid; + if(x_crc32 != y_crc32 && px_crc32 == py_crc32) return output_crc32_invalid; + } + + if(p_crc32 != pp_crc32) return patch_crc32_invalid; + return ok; + } + + private: + file fp; + uint32_t crc32; + const uint8_t *p_buffer; + + uint8_t read() { + uint8_t n = *p_buffer++; + crc32 = crc32_adjust(crc32, n); + return n; + } + + void write(uint8_t n) { + fp.write(n); + crc32 = crc32_adjust(crc32, n); + } + + void encptr(uint64_t offset) { + while(true) { + uint64_t x = offset & 0x7f; + offset >>= 7; + if(offset == 0) { + write(0x80 | x); + break; + } + write(x); + offset--; + } + } + + uint64_t decptr() { + uint64_t offset = 0, shift = 1; + while(true) { + uint8_t x = read(); + offset += (x & 0x7f) * shift; + if(x & 0x80) break; + shift <<= 7; + offset += shift; + } + return offset; + } + }; +} + +#endif diff --git a/snesfilter/nall/utf8.hpp b/snesfilter/nall/utf8.hpp new file mode 100644 index 00000000..c66c341a --- /dev/null +++ b/snesfilter/nall/utf8.hpp @@ -0,0 +1,72 @@ +#ifndef NALL_UTF8_HPP +#define NALL_UTF8_HPP + +//UTF-8 <> UTF-16 conversion +//used only for Win32; Linux, etc use UTF-8 internally + +#if defined(_WIN32) + +#undef _WIN32_WINNT +#define _WIN32_WINNT 0x0501 +#undef NOMINMAX +#define NOMINMAX +#include +#undef interface + +namespace nall { + //UTF-8 to UTF-16 + class utf16_t { + public: + operator wchar_t*() { + return buffer; + } + + operator const wchar_t*() const { + return buffer; + } + + utf16_t(const char *s = "") { + if(!s) s = ""; + unsigned length = MultiByteToWideChar(CP_UTF8, 0, s, -1, 0, 0); + buffer = new wchar_t[length + 1](); + MultiByteToWideChar(CP_UTF8, 0, s, -1, buffer, length); + } + + ~utf16_t() { + delete[] buffer; + } + + private: + wchar_t *buffer; + }; + + //UTF-16 to UTF-8 + class utf8_t { + public: + operator char*() { + return buffer; + } + + operator const char*() const { + return buffer; + } + + utf8_t(const wchar_t *s = L"") { + if(!s) s = L""; + unsigned length = WideCharToMultiByte(CP_UTF8, 0, s, -1, 0, 0, (const char*)0, (BOOL*)0); + buffer = new char[length + 1](); + WideCharToMultiByte(CP_UTF8, 0, s, -1, buffer, length, (const char*)0, (BOOL*)0); + } + + ~utf8_t() { + delete[] buffer; + } + + private: + char *buffer; + }; +} + +#endif //if defined(_WIN32) + +#endif diff --git a/snesfilter/nall/utility.hpp b/snesfilter/nall/utility.hpp new file mode 100644 index 00000000..2a63f515 --- /dev/null +++ b/snesfilter/nall/utility.hpp @@ -0,0 +1,30 @@ +#ifndef NALL_UTILITY_HPP +#define NALL_UTILITY_HPP + +#include +#include + +namespace nall { + template struct enable_if { typedef T type; }; + template struct enable_if {}; + template struct mp_enable_if : enable_if {}; + + template inline void swap(T &x, T &y) { + T temp(std::move(x)); + x = std::move(y); + y = std::move(temp); + } + + template struct base_from_member { + T value; + base_from_member(T value_) : value(value_) {} + }; + + template inline T* allocate(size_t size, const T &value) { + T *array = new T[size]; + for(size_t i = 0; i < size; i++) array[i] = value; + return array; + } +} + +#endif diff --git a/snesfilter/nall/varint.hpp b/snesfilter/nall/varint.hpp new file mode 100644 index 00000000..cc3bb17c --- /dev/null +++ b/snesfilter/nall/varint.hpp @@ -0,0 +1,92 @@ +#ifndef NALL_VARINT_HPP +#define NALL_VARINT_HPP + +#include +#include +#include + +namespace nall { + template class uint_t { + private: + enum { bytes = (bits + 7) >> 3 }; //minimum number of bytes needed to store value + typedef typename static_if< + sizeof(int) >= bytes, + unsigned int, + typename static_if< + sizeof(long) >= bytes, + unsigned long, + typename static_if< + sizeof(long long) >= bytes, + unsigned long long, + void + >::type + >::type + >::type T; + static_assert::value> uint_assert; + T data; + + public: + inline operator T() const { return data; } + inline T operator ++(int) { T r = data; data = uclip(data + 1); return r; } + inline T operator --(int) { T r = data; data = uclip(data - 1); return r; } + inline T operator ++() { return data = uclip(data + 1); } + inline T operator --() { return data = uclip(data - 1); } + inline T operator =(const T i) { return data = uclip(i); } + inline T operator |=(const T i) { return data = uclip(data | i); } + inline T operator ^=(const T i) { return data = uclip(data ^ i); } + inline T operator &=(const T i) { return data = uclip(data & i); } + inline T operator<<=(const T i) { return data = uclip(data << i); } + inline T operator>>=(const T i) { return data = uclip(data >> i); } + inline T operator +=(const T i) { return data = uclip(data + i); } + inline T operator -=(const T i) { return data = uclip(data - i); } + inline T operator *=(const T i) { return data = uclip(data * i); } + inline T operator /=(const T i) { return data = uclip(data / i); } + inline T operator %=(const T i) { return data = uclip(data % i); } + + inline uint_t() : data(0) {} + inline uint_t(const T i) : data(uclip(i)) {} + }; + + template class int_t { + private: + enum { bytes = (bits + 7) >> 3 }; //minimum number of bytes needed to store value + typedef typename static_if< + sizeof(int) >= bytes, + signed int, + typename static_if< + sizeof(long) >= bytes, + signed long, + typename static_if< + sizeof(long long) >= bytes, + signed long long, + void + >::type + >::type + >::type T; + static_assert::value> int_assert; + T data; + + public: + inline operator T() const { return data; } + inline T operator ++(int) { T r = data; data = sclip(data + 1); return r; } + inline T operator --(int) { T r = data; data = sclip(data - 1); return r; } + inline T operator ++() { return data = sclip(data + 1); } + inline T operator --() { return data = sclip(data - 1); } + inline T operator =(const T i) { return data = sclip(i); } + inline T operator |=(const T i) { return data = sclip(data | i); } + inline T operator ^=(const T i) { return data = sclip(data ^ i); } + inline T operator &=(const T i) { return data = sclip(data & i); } + inline T operator<<=(const T i) { return data = sclip(data << i); } + inline T operator>>=(const T i) { return data = sclip(data >> i); } + inline T operator +=(const T i) { return data = sclip(data + i); } + inline T operator -=(const T i) { return data = sclip(data - i); } + inline T operator *=(const T i) { return data = sclip(data * i); } + inline T operator /=(const T i) { return data = sclip(data / i); } + inline T operator %=(const T i) { return data = sclip(data % i); } + + inline int_t() : data(0) {} + inline int_t(const T i) : data(sclip(i)) {} + }; +} + +#endif diff --git a/snesfilter/nall/vector.hpp b/snesfilter/nall/vector.hpp new file mode 100644 index 00000000..3d69d4d5 --- /dev/null +++ b/snesfilter/nall/vector.hpp @@ -0,0 +1,240 @@ +#ifndef NALL_VECTOR_HPP +#define NALL_VECTOR_HPP + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace nall { + //linear_vector + //memory: O(capacity * 2) + // + //linear_vector uses placement new + manual destructor calls to create a + //contiguous block of memory for all objects. accessing individual elements + //is fast, though resizing the array incurs significant overhead. + //reserve() overhead is reduced from quadratic time to amortized constant time + //by resizing twice as much as requested. + // + //if objects hold memory address references to themselves (introspection), a + //valid copy constructor will be needed to keep pointers valid. + + template class linear_vector { + protected: + T *pool; + unsigned poolsize, objectsize; + + public: + unsigned size() const { return objectsize; } + unsigned capacity() const { return poolsize; } + + void reset() { + if(pool) { + for(unsigned i = 0; i < objectsize; i++) pool[i].~T(); + free(pool); + } + pool = 0; + poolsize = 0; + objectsize = 0; + } + + void reserve(unsigned newsize) { + newsize = bit::round(newsize); //round to nearest power of two (for amortized growth) + + T *poolcopy = (T*)malloc(newsize * sizeof(T)); + for(unsigned i = 0; i < min(objectsize, newsize); i++) new(poolcopy + i) T(pool[i]); + for(unsigned i = 0; i < objectsize; i++) pool[i].~T(); + free(pool); + pool = poolcopy; + poolsize = newsize; + objectsize = min(objectsize, newsize); + } + + void resize(unsigned newsize) { + if(newsize > poolsize) reserve(newsize); + + if(newsize < objectsize) { + //vector is shrinking; destroy excess objects + for(unsigned i = newsize; i < objectsize; i++) pool[i].~T(); + } else if(newsize > objectsize) { + //vector is expanding; allocate new objects + for(unsigned i = objectsize; i < newsize; i++) new(pool + i) T; + } + + objectsize = newsize; + } + + void add(const T data) { + if(objectsize + 1 > poolsize) reserve(objectsize + 1); + new(pool + objectsize++) T(data); + } + + inline T& operator[](unsigned index) { + if(index >= objectsize) resize(index + 1); + return pool[index]; + } + + inline const T& operator[](unsigned index) const { + if(index >= objectsize) throw "vector[] out of bounds"; + return pool[index]; + } + + //copy + inline linear_vector& operator=(const linear_vector &source) { + reset(); + reserve(source.capacity()); + resize(source.size()); + for(unsigned i = 0; i < source.size(); i++) operator[](i) = source.operator[](i); + return *this; + } + + linear_vector(const linear_vector &source) : pool(0), poolsize(0), objectsize(0) { + operator=(source); + } + + //move + inline linear_vector& operator=(linear_vector &&source) { + reset(); + pool = source.pool; + poolsize = source.poolsize; + objectsize = source.objectsize; + source.pool = 0; + source.reset(); + return *this; + } + + linear_vector(linear_vector &&source) : pool(0), poolsize(0), objectsize(0) { + operator=(std::move(source)); + } + + //construction + linear_vector() : pool(0), poolsize(0), objectsize(0) { + } + + linear_vector(std::initializer_list list) : pool(0), poolsize(0), objectsize(0) { + for(const T *p = list.begin(); p != list.end(); ++p) add(*p); + } + + ~linear_vector() { + reset(); + } + }; + + //pointer_vector + //memory: O(1) + // + //pointer_vector keeps an array of pointers to each vector object. this adds + //significant overhead to individual accesses, but allows for optimal memory + //utilization. + // + //by guaranteeing that the base memory address of each objects never changes, + //this avoids the need for an object to have a valid copy constructor. + + template class pointer_vector { + protected: + T **pool; + unsigned poolsize, objectsize; + + public: + unsigned size() const { return objectsize; } + unsigned capacity() const { return poolsize; } + + void reset() { + if(pool) { + for(unsigned i = 0; i < objectsize; i++) { if(pool[i]) delete pool[i]; } + free(pool); + } + pool = 0; + poolsize = 0; + objectsize = 0; + } + + void reserve(unsigned newsize) { + newsize = bit::round(newsize); //round to nearest power of two (for amortized growth) + + for(unsigned i = newsize; i < objectsize; i++) { + if(pool[i]) { delete pool[i]; pool[i] = 0; } + } + + pool = (T**)realloc(pool, newsize * sizeof(T*)); + for(unsigned i = poolsize; i < newsize; i++) pool[i] = 0; + poolsize = newsize; + objectsize = min(objectsize, newsize); + } + + void resize(unsigned newsize) { + if(newsize > poolsize) reserve(newsize); + + for(unsigned i = newsize; i < objectsize; i++) { + if(pool[i]) { delete pool[i]; pool[i] = 0; } + } + + objectsize = newsize; + } + + void add(const T data) { + if(objectsize + 1 > poolsize) reserve(objectsize + 1); + pool[objectsize++] = new T(data); + } + + inline T& operator[](unsigned index) { + if(index >= objectsize) resize(index + 1); + if(!pool[index]) pool[index] = new T; + return *pool[index]; + } + + inline const T& operator[](unsigned index) const { + if(index >= objectsize || !pool[index]) throw "vector[] out of bounds"; + return *pool[index]; + } + + //copy + inline pointer_vector& operator=(const pointer_vector &source) { + reset(); + reserve(source.capacity()); + resize(source.size()); + for(unsigned i = 0; i < source.size(); i++) operator[](i) = source.operator[](i); + return *this; + } + + pointer_vector(const pointer_vector &source) : pool(0), poolsize(0), objectsize(0) { + operator=(source); + } + + //move + inline pointer_vector& operator=(pointer_vector &&source) { + reset(); + pool = source.pool; + poolsize = source.poolsize; + objectsize = source.objectsize; + source.pool = 0; + source.reset(); + return *this; + } + + pointer_vector(pointer_vector &&source) : pool(0), poolsize(0), objectsize(0) { + operator=(std::move(source)); + } + + //construction + pointer_vector() : pool(0), poolsize(0), objectsize(0) { + } + + pointer_vector(std::initializer_list list) : pool(0), poolsize(0), objectsize(0) { + for(const T *p = list.begin(); p != list.end(); ++p) add(*p); + } + + ~pointer_vector() { + reset(); + } + }; + + template struct has_size> { enum { value = true }; }; + template struct has_size> { enum { value = true }; }; +} + +#endif diff --git a/snesfilter/ntsc/ntsc.cpp b/snesfilter/ntsc/ntsc.cpp new file mode 100644 index 00000000..3db8a34a --- /dev/null +++ b/snesfilter/ntsc/ntsc.cpp @@ -0,0 +1,380 @@ +#include "snes_ntsc/snes_ntsc.h" +#include "snes_ntsc/snes_ntsc.c" + +#include "ntsc.moc.hpp" +#include "ntsc.moc" + +void NTSCFilter::bind(configuration &config) { + config.attach(hue = 0.0, "snesfilter.ntsc.hue"); + config.attach(saturation = 0.0, "snesfilter.ntsc.saturation"); + config.attach(contrast = 0.0, "snesfilter.ntsc.contrast"); + config.attach(brightness = 0.0, "snesfilter.ntsc.brightness"); + config.attach(sharpness = 0.0, "snesfilter.ntsc.sharpness"); + config.attach(gamma = 0.0, "snesfilter.ntsc.gamma"); + config.attach(resolution = 0.0, "snesfilter.ntsc.resolution"); + config.attach(artifacts = 0.0, "snesfilter.ntsc.artifacts"); + config.attach(fringing = 0.0, "snesfilter.ntsc.fringing"); + config.attach(bleed = 0.0, "snesfilter.ntsc.bleed"); + config.attach(mergeFields = true, "snesfilter.ntsc.mergeFields"); +} + +void NTSCFilter::size(unsigned &outwidth, unsigned &outheight, unsigned width, unsigned height) { + outwidth = SNES_NTSC_OUT_WIDTH(256); + outheight = height; +} + +void NTSCFilter::render( + uint32_t *output, unsigned outpitch, + const uint16_t *input, unsigned pitch, unsigned width, unsigned height +) { + if(!ntsc) return; + + pitch >>= 1; + outpitch >>= 2; + + if(width <= 256) { + snes_ntsc_blit (ntsc, input, pitch, burst, width, height, output, outpitch << 2); + } else { + snes_ntsc_blit_hires(ntsc, input, pitch, burst, width, height, output, outpitch << 2); + } + + burst ^= burst_toggle; +} + +QWidget* NTSCFilter::settings() { + if(!widget) { + widget = new QWidget; + widget->setWindowTitle("NTSC Filter Configuration"); + + layout = new QVBoxLayout; + layout->setAlignment(Qt::AlignTop); + widget->setLayout(layout); + + gridLayout = new QGridLayout; + layout->addLayout(gridLayout); + + basicSettings = new QLabel("Basic settings:"); + gridLayout->addWidget(basicSettings, 0, 0, 1, 3); + + hueLabel = new QLabel("Hue:"); + gridLayout->addWidget(hueLabel, 1, 0); + + hueValue = new QLabel; + hueValue->setMinimumWidth(hueValue->fontMetrics().width("-100.0")); + hueValue->setAlignment(Qt::AlignHCenter); + gridLayout->addWidget(hueValue, 1, 1); + + hueSlider = new QSlider(Qt::Horizontal); + hueSlider->setMinimum(-100); + hueSlider->setMaximum(+100); + gridLayout->addWidget(hueSlider, 1, 2); + + saturationLabel = new QLabel("Saturation:"); + gridLayout->addWidget(saturationLabel, 2, 0); + + saturationValue = new QLabel; + saturationValue->setAlignment(Qt::AlignHCenter); + gridLayout->addWidget(saturationValue, 2, 1); + + saturationSlider = new QSlider(Qt::Horizontal); + saturationSlider->setMinimum(-100); + saturationSlider->setMaximum(+100); + gridLayout->addWidget(saturationSlider, 2, 2); + + contrastLabel = new QLabel("Contrast:"); + gridLayout->addWidget(contrastLabel, 3, 0); + + contrastValue = new QLabel; + contrastValue->setAlignment(Qt::AlignHCenter); + gridLayout->addWidget(contrastValue, 3, 1); + + contrastSlider = new QSlider(Qt::Horizontal); + contrastSlider->setMinimum(-100); + contrastSlider->setMaximum(+100); + gridLayout->addWidget(contrastSlider, 3, 2); + + brightnessLabel = new QLabel("Brightness:"); + gridLayout->addWidget(brightnessLabel, 4, 0); + + brightnessValue = new QLabel; + brightnessValue->setAlignment(Qt::AlignHCenter); + gridLayout->addWidget(brightnessValue, 4, 1); + + brightnessSlider = new QSlider(Qt::Horizontal); + brightnessSlider->setMinimum(-100); + brightnessSlider->setMaximum(+100); + gridLayout->addWidget(brightnessSlider, 4, 2); + + sharpnessLabel = new QLabel("Sharpness:"); + gridLayout->addWidget(sharpnessLabel, 5, 0); + + sharpnessValue = new QLabel; + sharpnessValue->setAlignment(Qt::AlignHCenter); + gridLayout->addWidget(sharpnessValue, 5, 1); + + sharpnessSlider = new QSlider(Qt::Horizontal); + sharpnessSlider->setMinimum(-100); + sharpnessSlider->setMaximum(+100); + gridLayout->addWidget(sharpnessSlider, 5, 2); + + advancedSettings = new QLabel("Advanced settings:"); + gridLayout->addWidget(advancedSettings, 6, 0, 1, 3); + + gammaLabel = new QLabel("Gamma:"); + gridLayout->addWidget(gammaLabel, 7, 0); + + gammaValue = new QLabel; + gammaValue->setAlignment(Qt::AlignHCenter); + gridLayout->addWidget(gammaValue, 7, 1); + + gammaSlider = new QSlider(Qt::Horizontal); + gammaSlider->setMinimum(-100); + gammaSlider->setMaximum(+100); + gridLayout->addWidget(gammaSlider, 7, 2); + + resolutionLabel = new QLabel("Resolution:"); + gridLayout->addWidget(resolutionLabel, 8, 0); + + resolutionValue = new QLabel; + resolutionValue->setAlignment(Qt::AlignHCenter); + gridLayout->addWidget(resolutionValue, 8, 1); + + resolutionSlider = new QSlider(Qt::Horizontal); + resolutionSlider->setMinimum(-100); + resolutionSlider->setMaximum(+100); + gridLayout->addWidget(resolutionSlider, 8, 2); + + artifactsLabel = new QLabel("Artifacts:"); + gridLayout->addWidget(artifactsLabel, 9, 0); + + artifactsValue = new QLabel; + artifactsValue->setAlignment(Qt::AlignHCenter); + gridLayout->addWidget(artifactsValue, 9, 1); + + artifactsSlider = new QSlider(Qt::Horizontal); + artifactsSlider->setMinimum(-100); + artifactsSlider->setMaximum(+100); + gridLayout->addWidget(artifactsSlider, 9, 2); + + fringingLabel = new QLabel("Fringing:"); + gridLayout->addWidget(fringingLabel, 10, 0); + + fringingValue = new QLabel; + fringingValue->setAlignment(Qt::AlignHCenter); + gridLayout->addWidget(fringingValue, 10, 1); + + fringingSlider = new QSlider(Qt::Horizontal); + fringingSlider->setMinimum(-100); + fringingSlider->setMaximum(+100); + gridLayout->addWidget(fringingSlider, 10, 2); + + bleedLabel = new QLabel("Color bleed:"); + gridLayout->addWidget(bleedLabel, 11, 0); + + bleedValue = new QLabel; + bleedValue->setAlignment(Qt::AlignHCenter); + gridLayout->addWidget(bleedValue, 11, 1); + + bleedSlider = new QSlider(Qt::Horizontal); + bleedSlider->setMinimum(-100); + bleedSlider->setMaximum(+100); + gridLayout->addWidget(bleedSlider, 11, 2); + + mergeFieldsBox = new QCheckBox("Merge even and odd fields to reduce flicker"); + gridLayout->addWidget(mergeFieldsBox, 12, 0, 1, 3); + + presets = new QLabel("Presets:"); + gridLayout->addWidget(presets, 13, 0, 1, 3); + + controlLayout = new QHBoxLayout; + layout->addLayout(controlLayout); + + rfPreset = new QPushButton("RF"); + controlLayout->addWidget(rfPreset); + + compositePreset = new QPushButton("Composite"); + controlLayout->addWidget(compositePreset); + + svideoPreset = new QPushButton("S-Video"); + controlLayout->addWidget(svideoPreset); + + rgbPreset = new QPushButton("RGB"); + controlLayout->addWidget(rgbPreset); + + monoPreset = new QPushButton("Monochrome"); + controlLayout->addWidget(monoPreset); + + spacer = new QWidget; + spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + spacer->setMinimumWidth(50); + controlLayout->addWidget(spacer); + + ok = new QPushButton("Ok"); + controlLayout->addWidget(ok); + + blockSignals = true; + loadSettingsFromConfig(); + syncUiToSettings(); + initialize(); + + connect(hueSlider, SIGNAL(valueChanged(int)), this, SLOT(syncSettingsToUi())); + connect(saturationSlider, SIGNAL(valueChanged(int)), this, SLOT(syncSettingsToUi())); + connect(contrastSlider, SIGNAL(valueChanged(int)), this, SLOT(syncSettingsToUi())); + connect(brightnessSlider, SIGNAL(valueChanged(int)), this, SLOT(syncSettingsToUi())); + connect(sharpnessSlider, SIGNAL(valueChanged(int)), this, SLOT(syncSettingsToUi())); + connect(gammaSlider, SIGNAL(valueChanged(int)), this, SLOT(syncSettingsToUi())); + connect(resolutionSlider, SIGNAL(valueChanged(int)), this, SLOT(syncSettingsToUi())); + connect(artifactsSlider, SIGNAL(valueChanged(int)), this, SLOT(syncSettingsToUi())); + connect(fringingSlider, SIGNAL(valueChanged(int)), this, SLOT(syncSettingsToUi())); + connect(bleedSlider, SIGNAL(valueChanged(int)), this, SLOT(syncSettingsToUi())); + connect(mergeFieldsBox, SIGNAL(stateChanged(int)), this, SLOT(syncSettingsToUi())); + connect(rfPreset, SIGNAL(released()), this, SLOT(setRfPreset())); + connect(compositePreset, SIGNAL(released()), this, SLOT(setCompositePreset())); + connect(svideoPreset, SIGNAL(released()), this, SLOT(setSvideoPreset())); + connect(rgbPreset, SIGNAL(released()), this, SLOT(setRgbPreset())); + connect(monoPreset, SIGNAL(released()), this, SLOT(setMonoPreset())); + connect(ok, SIGNAL(released()), widget, SLOT(hide())); + + blockSignals = false; + } + + return widget; +} + +void NTSCFilter::initialize() { + burst = 0; + burst_toggle = (setup.merge_fields ? 0 : 1); //don't toggle burst when fields are merged + snes_ntsc_init(ntsc, &setup); +} + +void NTSCFilter::loadSettingsFromConfig() { + setup.hue = hue; + setup.saturation = saturation; + setup.contrast = contrast; + setup.brightness = brightness; + setup.sharpness = sharpness; + + setup.gamma = gamma; + setup.resolution = resolution; + setup.artifacts = artifacts; + setup.fringing = fringing; + setup.bleed = bleed; + + setup.merge_fields = mergeFields; +} + +void NTSCFilter::syncUiToSettings() { + blockSignals = true; + + hue = setup.hue; + saturation = setup.saturation; + contrast = setup.contrast; + brightness = setup.brightness; + sharpness = setup.sharpness; + + gamma = setup.gamma; + resolution = setup.resolution; + artifacts = setup.artifacts; + fringing = setup.fringing; + bleed = setup.bleed; + + mergeFields = setup.merge_fields; + + hueValue->setText(string() << hue); + hueSlider->setSliderPosition(hue * 100); + + saturationValue->setText(string() << saturation); + saturationSlider->setSliderPosition(saturation * 100); + + contrastValue->setText(string() << contrast); + contrastSlider->setSliderPosition(contrast * 100); + + brightnessValue->setText(string() << brightness); + brightnessSlider->setSliderPosition(brightness * 100); + + sharpnessValue->setText(string() << sharpness); + sharpnessSlider->setSliderPosition(sharpness * 100); + + gammaValue->setText(string() << gamma); + gammaSlider->setSliderPosition(gamma * 100); + + resolutionValue->setText(string() << resolution); + resolutionSlider->setSliderPosition(resolution * 100); + + artifactsValue->setText(string() << artifacts); + artifactsSlider->setSliderPosition(artifacts * 100); + + fringingValue->setText(string() << fringing); + fringingSlider->setSliderPosition(fringing * 100); + + bleedValue->setText(string() << bleed); + bleedSlider->setSliderPosition(bleed * 100); + + mergeFieldsBox->setChecked(mergeFields); + + blockSignals = false; +} + +void NTSCFilter::syncSettingsToUi() { + if(blockSignals) return; + + hue = hueSlider->sliderPosition() / 100.0; + saturation = saturationSlider->sliderPosition() / 100.0; + contrast = contrastSlider->sliderPosition() / 100.0; + brightness = brightnessSlider->sliderPosition() / 100.0; + sharpness = sharpnessSlider->sliderPosition() / 100.0; + + gamma = gammaSlider->sliderPosition() / 100.0; + resolution = resolutionSlider->sliderPosition() / 100.0; + artifacts = artifactsSlider->sliderPosition() / 100.0; + fringing = fringingSlider->sliderPosition() / 100.0; + bleed = bleedSlider->sliderPosition() / 100.0; + + mergeFields = mergeFieldsBox->isChecked(); + + loadSettingsFromConfig(); + syncUiToSettings(); + initialize(); +} + +void NTSCFilter::setRfPreset() { + static snes_ntsc_setup_t defaults; + setup = defaults; + syncUiToSettings(); + initialize(); +} + +void NTSCFilter::setCompositePreset() { + setup = snes_ntsc_composite; + syncUiToSettings(); + initialize(); +} + +void NTSCFilter::setSvideoPreset() { + setup = snes_ntsc_svideo; + syncUiToSettings(); + initialize(); +} + +void NTSCFilter::setRgbPreset() { + setup = snes_ntsc_rgb; + syncUiToSettings(); + initialize(); +} + +void NTSCFilter::setMonoPreset() { + setup = snes_ntsc_monochrome; + syncUiToSettings(); + initialize(); +} + +NTSCFilter::NTSCFilter() : widget(0) { + ntsc = (snes_ntsc_t*)malloc(sizeof *ntsc); + static snes_ntsc_setup_t defaults; + setup = defaults; + initialize(); +} + +NTSCFilter::~NTSCFilter() { + if(ntsc) free(ntsc); +} diff --git a/snesfilter/ntsc/ntsc.moc.hpp b/snesfilter/ntsc/ntsc.moc.hpp new file mode 100644 index 00000000..e8aba423 --- /dev/null +++ b/snesfilter/ntsc/ntsc.moc.hpp @@ -0,0 +1,91 @@ +class NTSCFilter : public QObject { + Q_OBJECT + +public: + void bind(configuration&); + void size(unsigned&, unsigned&, unsigned, unsigned); + void render(uint32_t*, unsigned, const uint16_t*, unsigned, unsigned, unsigned); + QWidget* settings(); + + NTSCFilter(); + ~NTSCFilter(); + +private: + void initialize(); + void loadSettingsFromConfig(); + void syncUiToSettings(); + +private slots: + void syncSettingsToUi(); + void setRfPreset(); + void setCompositePreset(); + void setSvideoPreset(); + void setRgbPreset(); + void setMonoPreset(); + +private: + QWidget *widget; + QVBoxLayout *layout; + QGridLayout *gridLayout; + QLabel *basicSettings; + QLabel *hueLabel; + QLabel *hueValue; + QSlider *hueSlider; + QLabel *saturationLabel; + QLabel *saturationValue; + QSlider *saturationSlider; + QLabel *contrastLabel; + QLabel *contrastValue; + QSlider *contrastSlider; + QLabel *brightnessLabel; + QLabel *brightnessValue; + QSlider *brightnessSlider; + QLabel *sharpnessLabel; + QLabel *sharpnessValue; + QSlider *sharpnessSlider; + QLabel *advancedSettings; + QLabel *gammaLabel; + QLabel *gammaValue; + QSlider *gammaSlider; + QLabel *resolutionLabel; + QLabel *resolutionValue; + QSlider *resolutionSlider; + QLabel *artifactsLabel; + QLabel *artifactsValue; + QSlider *artifactsSlider; + QLabel *fringingLabel; + QLabel *fringingValue; + QSlider *fringingSlider; + QLabel *bleedLabel; + QLabel *bleedValue; + QSlider *bleedSlider; + QCheckBox *mergeFieldsBox; + QLabel *presets; + QHBoxLayout *controlLayout; + QPushButton *rfPreset; + QPushButton *compositePreset; + QPushButton *svideoPreset; + QPushButton *rgbPreset; + QPushButton *monoPreset; + QWidget *spacer; + QPushButton *ok; + + bool blockSignals; + + struct snes_ntsc_t *ntsc; + snes_ntsc_setup_t setup; + int burst, burst_toggle; + + //settings + double hue; + double saturation; + double contrast; + double brightness; + double sharpness; + double gamma; + double resolution; + double artifacts; + double fringing; + double bleed; + bool mergeFields; +} filter_ntsc; diff --git a/snesfilter/ntsc/snes_ntsc/snes_ntsc.c b/snesfilter/ntsc/snes_ntsc/snes_ntsc.c new file mode 100644 index 00000000..f622baf8 --- /dev/null +++ b/snesfilter/ntsc/snes_ntsc/snes_ntsc.c @@ -0,0 +1,251 @@ +/* snes_ntsc 0.2.2. http://www.slack.net/~ant/ */ + +#include "snes_ntsc.h" + +/* Copyright (C) 2006-2007 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +snes_ntsc_setup_t const snes_ntsc_monochrome = { 0,-1, 0, 0,.2, 0,.2,-.2,-.2,-1, 1, 0, 0 }; +snes_ntsc_setup_t const snes_ntsc_composite = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0 }; +snes_ntsc_setup_t const snes_ntsc_svideo = { 0, 0, 0, 0,.2, 0,.2, -1, -1, 0, 1, 0, 0 }; +snes_ntsc_setup_t const snes_ntsc_rgb = { 0, 0, 0, 0,.2, 0,.7, -1, -1,-1, 1, 0, 0 }; + +#define alignment_count 3 +#define burst_count 3 +#define rescale_in 8 +#define rescale_out 7 + +#define artifacts_mid 1.0f +#define fringing_mid 1.0f +#define std_decoder_hue 0 + +#define rgb_bits 7 /* half normal range to allow for doubled hires pixels */ +#define gamma_size 32 + +#include "snes_ntsc_impl.h" + +/* 3 input pixels -> 8 composite samples */ +pixel_info_t const snes_ntsc_pixels [alignment_count] = { + { PIXEL_OFFSET( -4, -9 ), { 1, 1, .6667f, 0 } }, + { PIXEL_OFFSET( -2, -7 ), { .3333f, 1, 1, .3333f } }, + { PIXEL_OFFSET( 0, -5 ), { 0, .6667f, 1, 1 } }, +}; + +static void merge_kernel_fields( snes_ntsc_rgb_t* io ) +{ + int n; + for ( n = burst_size; n; --n ) + { + snes_ntsc_rgb_t p0 = io [burst_size * 0] + rgb_bias; + snes_ntsc_rgb_t p1 = io [burst_size * 1] + rgb_bias; + snes_ntsc_rgb_t p2 = io [burst_size * 2] + rgb_bias; + /* merge colors without losing precision */ + io [burst_size * 0] = + ((p0 + p1 - ((p0 ^ p1) & snes_ntsc_rgb_builder)) >> 1) - rgb_bias; + io [burst_size * 1] = + ((p1 + p2 - ((p1 ^ p2) & snes_ntsc_rgb_builder)) >> 1) - rgb_bias; + io [burst_size * 2] = + ((p2 + p0 - ((p2 ^ p0) & snes_ntsc_rgb_builder)) >> 1) - rgb_bias; + ++io; + } +} + +static void correct_errors( snes_ntsc_rgb_t color, snes_ntsc_rgb_t* out ) +{ + int n; + for ( n = burst_count; n; --n ) + { + unsigned i; + for ( i = 0; i < rgb_kernel_size / 2; i++ ) + { + snes_ntsc_rgb_t error = color - + out [i ] - out [(i+12)%14+14] - out [(i+10)%14+28] - + out [i + 7] - out [i + 5 +14] - out [i + 3 +28]; + DISTRIBUTE_ERROR( i+3+28, i+5+14, i+7 ); + } + out += alignment_count * rgb_kernel_size; + } +} + +void snes_ntsc_init( snes_ntsc_t* ntsc, snes_ntsc_setup_t const* setup ) +{ + int merge_fields; + int entry; + init_t impl; + if ( !setup ) + setup = &snes_ntsc_composite; + init( &impl, setup ); + + merge_fields = setup->merge_fields; + if ( setup->artifacts <= -1 && setup->fringing <= -1 ) + merge_fields = 1; + + for ( entry = 0; entry < snes_ntsc_palette_size; entry++ ) + { + /* Reduce number of significant bits of source color. Clearing the + low bits of R and B were least notictable. Modifying green was too + noticeable. */ + int ir = entry >> 8 & 0x1E; + int ig = entry >> 4 & 0x1F; + int ib = entry << 1 & 0x1E; + + #if SNES_NTSC_BSNES_COLORTBL + if ( setup->bsnes_colortbl ) + { + int bgr15 = (ib << 10) | (ig << 5) | ir; + unsigned long rgb16 = setup->bsnes_colortbl [bgr15]; + ir = rgb16 >> 11 & 0x1E; + ig = rgb16 >> 6 & 0x1F; + ib = rgb16 & 0x1E; + } + #endif + + { + float rr = impl.to_float [ir]; + float gg = impl.to_float [ig]; + float bb = impl.to_float [ib]; + + float y, i, q = RGB_TO_YIQ( rr, gg, bb, y, i ); + + int r, g, b = YIQ_TO_RGB( y, i, q, impl.to_rgb, int, r, g ); + snes_ntsc_rgb_t rgb = PACK_RGB( r, g, b ); + + snes_ntsc_rgb_t* out = ntsc->table [entry]; + gen_kernel( &impl, y, i, q, out ); + if ( merge_fields ) + merge_kernel_fields( out ); + correct_errors( rgb, out ); + } + } +} + +#ifndef SNES_NTSC_NO_BLITTERS + +void snes_ntsc_blit( snes_ntsc_t const* ntsc, SNES_NTSC_IN_T const* input, long in_row_width, + int burst_phase, int in_width, int in_height, void* rgb_out, long out_pitch ) +{ + int chunk_count = (in_width - 1) / snes_ntsc_in_chunk; + for ( ; in_height; --in_height ) + { + SNES_NTSC_IN_T const* line_in = input; + SNES_NTSC_BEGIN_ROW( ntsc, burst_phase, + snes_ntsc_black, snes_ntsc_black, SNES_NTSC_ADJ_IN( *line_in ) ); + snes_ntsc_out_t* restrict line_out = (snes_ntsc_out_t*) rgb_out; + int n; + ++line_in; + + for ( n = chunk_count; n; --n ) + { + /* order of input and output pixels must not be altered */ + SNES_NTSC_COLOR_IN( 0, SNES_NTSC_ADJ_IN( line_in [0] ) ); + SNES_NTSC_RGB_OUT( 0, line_out [0], SNES_NTSC_OUT_DEPTH ); + SNES_NTSC_RGB_OUT( 1, line_out [1], SNES_NTSC_OUT_DEPTH ); + + SNES_NTSC_COLOR_IN( 1, SNES_NTSC_ADJ_IN( line_in [1] ) ); + SNES_NTSC_RGB_OUT( 2, line_out [2], SNES_NTSC_OUT_DEPTH ); + SNES_NTSC_RGB_OUT( 3, line_out [3], SNES_NTSC_OUT_DEPTH ); + + SNES_NTSC_COLOR_IN( 2, SNES_NTSC_ADJ_IN( line_in [2] ) ); + SNES_NTSC_RGB_OUT( 4, line_out [4], SNES_NTSC_OUT_DEPTH ); + SNES_NTSC_RGB_OUT( 5, line_out [5], SNES_NTSC_OUT_DEPTH ); + SNES_NTSC_RGB_OUT( 6, line_out [6], SNES_NTSC_OUT_DEPTH ); + + line_in += 3; + line_out += 7; + } + + /* finish final pixels */ + SNES_NTSC_COLOR_IN( 0, snes_ntsc_black ); + SNES_NTSC_RGB_OUT( 0, line_out [0], SNES_NTSC_OUT_DEPTH ); + SNES_NTSC_RGB_OUT( 1, line_out [1], SNES_NTSC_OUT_DEPTH ); + + SNES_NTSC_COLOR_IN( 1, snes_ntsc_black ); + SNES_NTSC_RGB_OUT( 2, line_out [2], SNES_NTSC_OUT_DEPTH ); + SNES_NTSC_RGB_OUT( 3, line_out [3], SNES_NTSC_OUT_DEPTH ); + + SNES_NTSC_COLOR_IN( 2, snes_ntsc_black ); + SNES_NTSC_RGB_OUT( 4, line_out [4], SNES_NTSC_OUT_DEPTH ); + SNES_NTSC_RGB_OUT( 5, line_out [5], SNES_NTSC_OUT_DEPTH ); + SNES_NTSC_RGB_OUT( 6, line_out [6], SNES_NTSC_OUT_DEPTH ); + + burst_phase = (burst_phase + 1) % snes_ntsc_burst_count; + input += in_row_width; + rgb_out = (char*) rgb_out + out_pitch; + } +} + +void snes_ntsc_blit_hires( snes_ntsc_t const* ntsc, SNES_NTSC_IN_T const* input, long in_row_width, + int burst_phase, int in_width, int in_height, void* rgb_out, long out_pitch ) +{ + int chunk_count = (in_width - 2) / (snes_ntsc_in_chunk * 2); + for ( ; in_height; --in_height ) + { + SNES_NTSC_IN_T const* line_in = input; + SNES_NTSC_HIRES_ROW( ntsc, burst_phase, + snes_ntsc_black, snes_ntsc_black, snes_ntsc_black, + SNES_NTSC_ADJ_IN( line_in [0] ), + SNES_NTSC_ADJ_IN( line_in [1] ) ); + snes_ntsc_out_t* restrict line_out = (snes_ntsc_out_t*) rgb_out; + int n; + line_in += 2; + + for ( n = chunk_count; n; --n ) + { + /* twice as many input pixels per chunk */ + SNES_NTSC_COLOR_IN( 0, SNES_NTSC_ADJ_IN( line_in [0] ) ); + SNES_NTSC_HIRES_OUT( 0, line_out [0], SNES_NTSC_OUT_DEPTH ); + + SNES_NTSC_COLOR_IN( 1, SNES_NTSC_ADJ_IN( line_in [1] ) ); + SNES_NTSC_HIRES_OUT( 1, line_out [1], SNES_NTSC_OUT_DEPTH ); + + SNES_NTSC_COLOR_IN( 2, SNES_NTSC_ADJ_IN( line_in [2] ) ); + SNES_NTSC_HIRES_OUT( 2, line_out [2], SNES_NTSC_OUT_DEPTH ); + + SNES_NTSC_COLOR_IN( 3, SNES_NTSC_ADJ_IN( line_in [3] ) ); + SNES_NTSC_HIRES_OUT( 3, line_out [3], SNES_NTSC_OUT_DEPTH ); + + SNES_NTSC_COLOR_IN( 4, SNES_NTSC_ADJ_IN( line_in [4] ) ); + SNES_NTSC_HIRES_OUT( 4, line_out [4], SNES_NTSC_OUT_DEPTH ); + + SNES_NTSC_COLOR_IN( 5, SNES_NTSC_ADJ_IN( line_in [5] ) ); + SNES_NTSC_HIRES_OUT( 5, line_out [5], SNES_NTSC_OUT_DEPTH ); + SNES_NTSC_HIRES_OUT( 6, line_out [6], SNES_NTSC_OUT_DEPTH ); + + line_in += 6; + line_out += 7; + } + + SNES_NTSC_COLOR_IN( 0, snes_ntsc_black ); + SNES_NTSC_HIRES_OUT( 0, line_out [0], SNES_NTSC_OUT_DEPTH ); + + SNES_NTSC_COLOR_IN( 1, snes_ntsc_black ); + SNES_NTSC_HIRES_OUT( 1, line_out [1], SNES_NTSC_OUT_DEPTH ); + + SNES_NTSC_COLOR_IN( 2, snes_ntsc_black ); + SNES_NTSC_HIRES_OUT( 2, line_out [2], SNES_NTSC_OUT_DEPTH ); + + SNES_NTSC_COLOR_IN( 3, snes_ntsc_black ); + SNES_NTSC_HIRES_OUT( 3, line_out [3], SNES_NTSC_OUT_DEPTH ); + + SNES_NTSC_COLOR_IN( 4, snes_ntsc_black ); + SNES_NTSC_HIRES_OUT( 4, line_out [4], SNES_NTSC_OUT_DEPTH ); + + SNES_NTSC_COLOR_IN( 5, snes_ntsc_black ); + SNES_NTSC_HIRES_OUT( 5, line_out [5], SNES_NTSC_OUT_DEPTH ); + SNES_NTSC_HIRES_OUT( 6, line_out [6], SNES_NTSC_OUT_DEPTH ); + + burst_phase = (burst_phase + 1) % snes_ntsc_burst_count; + input += in_row_width; + rgb_out = (char*) rgb_out + out_pitch; + } +} + +#endif diff --git a/snesfilter/ntsc/snes_ntsc/snes_ntsc.h b/snesfilter/ntsc/snes_ntsc/snes_ntsc.h new file mode 100644 index 00000000..fff97ecd --- /dev/null +++ b/snesfilter/ntsc/snes_ntsc/snes_ntsc.h @@ -0,0 +1,228 @@ +/* SNES NTSC video filter */ + +/* snes_ntsc 0.2.2 */ +#ifndef SNES_NTSC_H +#define SNES_NTSC_H + +#include "snes_ntsc_config.h" + +#ifdef __cplusplus + extern "C" { +#endif + +/* Image parameters, ranging from -1.0 to 1.0. Actual internal values shown +in parenthesis and should remain fairly stable in future versions. */ +typedef struct snes_ntsc_setup_t +{ + /* Basic parameters */ + double hue; /* -1 = -180 degrees +1 = +180 degrees */ + double saturation; /* -1 = grayscale (0.0) +1 = oversaturated colors (2.0) */ + double contrast; /* -1 = dark (0.5) +1 = light (1.5) */ + double brightness; /* -1 = dark (0.5) +1 = light (1.5) */ + double sharpness; /* edge contrast enhancement/blurring */ + + /* Advanced parameters */ + double gamma; /* -1 = dark (1.5) +1 = light (0.5) */ + double resolution; /* image resolution */ + double artifacts; /* artifacts caused by color changes */ + double fringing; /* color artifacts caused by brightness changes */ + double bleed; /* color bleed (color resolution reduction) */ + int merge_fields; /* if 1, merges even and odd fields together to reduce flicker */ + float const* decoder_matrix; /* optional RGB decoder matrix, 6 elements */ + + unsigned long const* bsnes_colortbl; /* undocumented; set to 0 */ +} snes_ntsc_setup_t; + +/* Video format presets */ +extern snes_ntsc_setup_t const snes_ntsc_composite; /* color bleeding + artifacts */ +extern snes_ntsc_setup_t const snes_ntsc_svideo; /* color bleeding only */ +extern snes_ntsc_setup_t const snes_ntsc_rgb; /* crisp image */ +extern snes_ntsc_setup_t const snes_ntsc_monochrome;/* desaturated + artifacts */ + +/* Initializes and adjusts parameters. Can be called multiple times on the same +snes_ntsc_t object. Can pass NULL for either parameter. */ +typedef struct snes_ntsc_t snes_ntsc_t; +void snes_ntsc_init( snes_ntsc_t* ntsc, snes_ntsc_setup_t const* setup ); + +/* Filters one or more rows of pixels. Input pixel format is set by SNES_NTSC_IN_FORMAT +and output RGB depth is set by SNES_NTSC_OUT_DEPTH. Both default to 16-bit RGB. +In_row_width is the number of pixels to get to the next input row. Out_pitch +is the number of *bytes* to get to the next output row. */ +void snes_ntsc_blit( snes_ntsc_t const* ntsc, SNES_NTSC_IN_T const* input, + long in_row_width, int burst_phase, int in_width, int in_height, + void* rgb_out, long out_pitch ); + +void snes_ntsc_blit_hires( snes_ntsc_t const* ntsc, SNES_NTSC_IN_T const* input, + long in_row_width, int burst_phase, int in_width, int in_height, + void* rgb_out, long out_pitch ); + +/* Number of output pixels written by low-res blitter for given input width. Width +might be rounded down slightly; use SNES_NTSC_IN_WIDTH() on result to find rounded +value. Guaranteed not to round 256 down at all. */ +#define SNES_NTSC_OUT_WIDTH( in_width ) \ + ((((in_width) - 1) / snes_ntsc_in_chunk + 1) * snes_ntsc_out_chunk) + +/* Number of low-res input pixels that will fit within given output width. Might be +rounded down slightly; use SNES_NTSC_OUT_WIDTH() on result to find rounded +value. */ +#define SNES_NTSC_IN_WIDTH( out_width ) \ + (((out_width) / snes_ntsc_out_chunk - 1) * snes_ntsc_in_chunk + 1) + + +/* Interface for user-defined custom blitters */ + +enum { snes_ntsc_in_chunk = 3 }; /* number of input pixels read per chunk */ +enum { snes_ntsc_out_chunk = 7 }; /* number of output pixels generated per chunk */ +enum { snes_ntsc_black = 0 }; /* palette index for black */ +enum { snes_ntsc_burst_count = 3 }; /* burst phase cycles through 0, 1, and 2 */ + +/* Begins outputting row and starts three pixels. First pixel will be cut off a bit. +Use snes_ntsc_black for unused pixels. Declares variables, so must be before first +statement in a block (unless you're using C++). */ +#define SNES_NTSC_BEGIN_ROW( ntsc, burst, pixel0, pixel1, pixel2 ) \ + char const* ktable = \ + (char const*) (ntsc)->table + burst * (snes_ntsc_burst_size * sizeof (snes_ntsc_rgb_t));\ + SNES_NTSC_BEGIN_ROW_6_( pixel0, pixel1, pixel2, SNES_NTSC_IN_FORMAT, ktable ) + +/* Begins input pixel */ +#define SNES_NTSC_COLOR_IN( index, color ) \ + SNES_NTSC_COLOR_IN_( index, color, SNES_NTSC_IN_FORMAT, ktable ) + +/* Generates output pixel. Bits can be 24, 16, 15, 14, 32 (treated as 24), or 0: +24: RRRRRRRR GGGGGGGG BBBBBBBB (8-8-8 RGB) +16: RRRRRGGG GGGBBBBB (5-6-5 RGB) +15: RRRRRGG GGGBBBBB (5-5-5 RGB) +14: BBBBBGG GGGRRRRR (5-5-5 BGR, native SNES format) + 0: xxxRRRRR RRRxxGGG GGGGGxxB BBBBBBBx (native internal format; x = junk bits) */ +#define SNES_NTSC_RGB_OUT( index, rgb_out, bits ) \ + SNES_NTSC_RGB_OUT_14_( index, rgb_out, bits, 1 ) + +/* Hires equivalents */ +#define SNES_NTSC_HIRES_ROW( ntsc, burst, pixel1, pixel2, pixel3, pixel4, pixel5 ) \ + char const* ktable = \ + (char const*) (ntsc)->table + burst * (snes_ntsc_burst_size * sizeof (snes_ntsc_rgb_t));\ + unsigned const snes_ntsc_pixel1_ = (pixel1);\ + snes_ntsc_rgb_t const* kernel1 = SNES_NTSC_IN_FORMAT( ktable, snes_ntsc_pixel1_ );\ + unsigned const snes_ntsc_pixel2_ = (pixel2);\ + snes_ntsc_rgb_t const* kernel2 = SNES_NTSC_IN_FORMAT( ktable, snes_ntsc_pixel2_ );\ + unsigned const snes_ntsc_pixel3_ = (pixel3);\ + snes_ntsc_rgb_t const* kernel3 = SNES_NTSC_IN_FORMAT( ktable, snes_ntsc_pixel3_ );\ + unsigned const snes_ntsc_pixel4_ = (pixel4);\ + snes_ntsc_rgb_t const* kernel4 = SNES_NTSC_IN_FORMAT( ktable, snes_ntsc_pixel4_ );\ + unsigned const snes_ntsc_pixel5_ = (pixel5);\ + snes_ntsc_rgb_t const* kernel5 = SNES_NTSC_IN_FORMAT( ktable, snes_ntsc_pixel5_ );\ + snes_ntsc_rgb_t const* kernel0 = kernel1;\ + snes_ntsc_rgb_t const* kernelx0;\ + snes_ntsc_rgb_t const* kernelx1 = kernel1;\ + snes_ntsc_rgb_t const* kernelx2 = kernel1;\ + snes_ntsc_rgb_t const* kernelx3 = kernel1;\ + snes_ntsc_rgb_t const* kernelx4 = kernel1;\ + snes_ntsc_rgb_t const* kernelx5 = kernel1 + +#define SNES_NTSC_HIRES_OUT( x, rgb_out, bits ) {\ + snes_ntsc_rgb_t raw_ =\ + kernel0 [ x ] + kernel2 [(x+5)%7+14] + kernel4 [(x+3)%7+28] +\ + kernelx0 [(x+7)%7+7] + kernelx2 [(x+5)%7+21] + kernelx4 [(x+3)%7+35] +\ + kernel1 [(x+6)%7 ] + kernel3 [(x+4)%7+14] + kernel5 [(x+2)%7+28] +\ + kernelx1 [(x+6)%7+7] + kernelx3 [(x+4)%7+21] + kernelx5 [(x+2)%7+35];\ + SNES_NTSC_CLAMP_( raw_, 0 );\ + SNES_NTSC_RGB_OUT_( rgb_out, (bits), 0 );\ +} + + +/* private */ +enum { snes_ntsc_entry_size = 128 }; +enum { snes_ntsc_palette_size = 0x2000 }; +typedef unsigned long snes_ntsc_rgb_t; +struct snes_ntsc_t { + snes_ntsc_rgb_t table [snes_ntsc_palette_size] [snes_ntsc_entry_size]; +}; +enum { snes_ntsc_burst_size = snes_ntsc_entry_size / snes_ntsc_burst_count }; + +#define SNES_NTSC_RGB16( ktable, n ) \ + (snes_ntsc_rgb_t const*) (ktable + ((n & 0x001E) | (n >> 1 & 0x03E0) | (n >> 2 & 0x3C00)) * \ + (snes_ntsc_entry_size / 2 * sizeof (snes_ntsc_rgb_t))) + +#define SNES_NTSC_BGR15( ktable, n ) \ + (snes_ntsc_rgb_t const*) (ktable + ((n << 9 & 0x3C00) | (n & 0x03E0) | (n >> 10 & 0x001E)) * \ + (snes_ntsc_entry_size / 2 * sizeof (snes_ntsc_rgb_t))) + +/* common 3->7 ntsc macros */ +#define SNES_NTSC_BEGIN_ROW_6_( pixel0, pixel1, pixel2, ENTRY, table ) \ + unsigned const snes_ntsc_pixel0_ = (pixel0);\ + snes_ntsc_rgb_t const* kernel0 = ENTRY( table, snes_ntsc_pixel0_ );\ + unsigned const snes_ntsc_pixel1_ = (pixel1);\ + snes_ntsc_rgb_t const* kernel1 = ENTRY( table, snes_ntsc_pixel1_ );\ + unsigned const snes_ntsc_pixel2_ = (pixel2);\ + snes_ntsc_rgb_t const* kernel2 = ENTRY( table, snes_ntsc_pixel2_ );\ + snes_ntsc_rgb_t const* kernelx0;\ + snes_ntsc_rgb_t const* kernelx1 = kernel0;\ + snes_ntsc_rgb_t const* kernelx2 = kernel0 + +#define SNES_NTSC_RGB_OUT_14_( x, rgb_out, bits, shift ) {\ + snes_ntsc_rgb_t raw_ =\ + kernel0 [x ] + kernel1 [(x+12)%7+14] + kernel2 [(x+10)%7+28] +\ + kernelx0 [(x+7)%14] + kernelx1 [(x+ 5)%7+21] + kernelx2 [(x+ 3)%7+35];\ + SNES_NTSC_CLAMP_( raw_, shift );\ + SNES_NTSC_RGB_OUT_( rgb_out, bits, shift );\ +} + +/* common ntsc macros */ +#define snes_ntsc_rgb_builder ((1L << 21) | (1 << 11) | (1 << 1)) +#define snes_ntsc_clamp_mask (snes_ntsc_rgb_builder * 3 / 2) +#define snes_ntsc_clamp_add (snes_ntsc_rgb_builder * 0x101) +#define SNES_NTSC_CLAMP_( io, shift ) {\ + snes_ntsc_rgb_t sub = (io) >> (9-(shift)) & snes_ntsc_clamp_mask;\ + snes_ntsc_rgb_t clamp = snes_ntsc_clamp_add - sub;\ + io |= clamp;\ + clamp -= sub;\ + io &= clamp;\ +} + +#define SNES_NTSC_COLOR_IN_( index, color, ENTRY, table ) {\ + unsigned color_;\ + kernelx##index = kernel##index;\ + kernel##index = (color_ = (color), ENTRY( table, color_ ));\ +} + +/* x is always zero except in snes_ntsc library */ +/* original routine */ +/* +#define SNES_NTSC_RGB_OUT_( rgb_out, bits, x ) {\ + if ( bits == 16 )\ + rgb_out = (raw_>>(13-x)& 0xF800)|(raw_>>(8-x)&0x07E0)|(raw_>>(4-x)&0x001F);\ + if ( bits == 24 || bits == 32 )\ + rgb_out = (raw_>>(5-x)&0xFF0000)|(raw_>>(3-x)&0xFF00)|(raw_>>(1-x)&0xFF);\ + if ( bits == 15 )\ + rgb_out = (raw_>>(14-x)& 0x7C00)|(raw_>>(9-x)&0x03E0)|(raw_>>(4-x)&0x001F);\ + if ( bits == 14 )\ + rgb_out = (raw_>>(24-x)& 0x001F)|(raw_>>(9-x)&0x03E0)|(raw_<<(6+x)&0x7C00);\ + if ( bits == 0 )\ + rgb_out = raw_ << x;\ +} +*/ + +/* custom bsnes routine -- hooks into bsnes colortable */ +#define SNES_NTSC_RGB_OUT_( rgb_out, bits, x ) {\ + if ( bits == 16 ) {\ + rgb_out = (raw_>>(13-x)& 0xF800)|(raw_>>(8-x)&0x07E0)|(raw_>>(4-x)&0x001F);\ + rgb_out = ((rgb_out&0xf800)>>11)|((rgb_out&0x07c0)>>1)|((rgb_out&0x001f)<<10);\ + rgb_out = colortable[rgb_out];\ + } else if ( bits == 24 || bits == 32 ) {\ + rgb_out = (raw_>>(5-x)&0xFF0000)|(raw_>>(3-x)&0xFF00)|(raw_>>(1-x)&0xFF);\ + rgb_out = ((rgb_out&0xf80000)>>19)|((rgb_out&0x00f800)>>6)|((rgb_out&0x0000f8)<<7);\ + rgb_out = colortable[rgb_out];\ + } else if ( bits == 15 ) {\ + rgb_out = (raw_>>(14-x)& 0x7C00)|(raw_>>(9-x)&0x03E0)|(raw_>>(4-x)&0x001F);\ + rgb_out = ((rgb_out&0x7c00)>>10)|((rgb_out&0x03e0))|((rgb_out&0x001f)<<10);\ + rgb_out = colortable[rgb_out];\ + } else {\ + rgb_out = raw_ << x;\ + }\ +} + +#ifdef __cplusplus + } +#endif + +#endif diff --git a/snesfilter/ntsc/snes_ntsc/snes_ntsc_config.h b/snesfilter/ntsc/snes_ntsc/snes_ntsc_config.h new file mode 100644 index 00000000..7ab94c2c --- /dev/null +++ b/snesfilter/ntsc/snes_ntsc/snes_ntsc_config.h @@ -0,0 +1,26 @@ +/* Configure library by modifying this file */ + +#ifndef SNES_NTSC_CONFIG_H +#define SNES_NTSC_CONFIG_H + +/* Format of source pixels */ +/* #define SNES_NTSC_IN_FORMAT SNES_NTSC_RGB16 */ +#define SNES_NTSC_IN_FORMAT SNES_NTSC_BGR15 + +/* The following affect the built-in blitter only; a custom blitter can +handle things however it wants. */ + +/* Bits per pixel of output. Can be 15, 16, 32, or 24 (same as 32). */ +#define SNES_NTSC_OUT_DEPTH 32 + +/* Type of input pixel values */ +#define SNES_NTSC_IN_T unsigned short + +/* Each raw pixel input value is passed through this. You might want to mask +the pixel index if you use the high bits as flags, etc. */ +#define SNES_NTSC_ADJ_IN( in ) in + +/* For each pixel, this is the basic operation: +output_color = SNES_NTSC_ADJ_IN( SNES_NTSC_IN_T ) */ + +#endif diff --git a/snesfilter/ntsc/snes_ntsc/snes_ntsc_impl.h b/snesfilter/ntsc/snes_ntsc/snes_ntsc_impl.h new file mode 100644 index 00000000..1d7adc78 --- /dev/null +++ b/snesfilter/ntsc/snes_ntsc/snes_ntsc_impl.h @@ -0,0 +1,439 @@ +/* snes_ntsc 0.2.2. http://www.slack.net/~ant/ */ + +/* Common implementation of NTSC filters */ + +#include +#include + +/* Copyright (C) 2006 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#define DISABLE_CORRECTION 0 + +#undef PI +#define PI 3.14159265358979323846f + +#ifndef LUMA_CUTOFF + #define LUMA_CUTOFF 0.20 +#endif +#ifndef gamma_size + #define gamma_size 1 +#endif +#ifndef rgb_bits + #define rgb_bits 8 +#endif +#ifndef artifacts_max + #define artifacts_max (artifacts_mid * 1.5f) +#endif +#ifndef fringing_max + #define fringing_max (fringing_mid * 2) +#endif +#ifndef STD_HUE_CONDITION + #define STD_HUE_CONDITION( setup ) 1 +#endif + +#define ext_decoder_hue (std_decoder_hue + 15) +#define rgb_unit (1 << rgb_bits) +#define rgb_offset (rgb_unit * 2 + 0.5f) + +enum { burst_size = snes_ntsc_entry_size / burst_count }; +enum { kernel_half = 16 }; +enum { kernel_size = kernel_half * 2 + 1 }; + +typedef struct init_t +{ + float to_rgb [burst_count * 6]; + float to_float [gamma_size]; + float contrast; + float brightness; + float artifacts; + float fringing; + float kernel [rescale_out * kernel_size * 2]; +} init_t; + +#define ROTATE_IQ( i, q, sin_b, cos_b ) {\ + float t;\ + t = i * cos_b - q * sin_b;\ + q = i * sin_b + q * cos_b;\ + i = t;\ +} + +static void init_filters( init_t* impl, snes_ntsc_setup_t const* setup ) +{ +#if rescale_out > 1 + float kernels [kernel_size * 2]; +#else + float* const kernels = impl->kernel; +#endif + + /* generate luma (y) filter using sinc kernel */ + { + /* sinc with rolloff (dsf) */ + float const rolloff = 1 + (float) setup->sharpness * (float) 0.032; + float const maxh = 32; + float const pow_a_n = (float) pow( rolloff, maxh ); + float sum; + int i; + /* quadratic mapping to reduce negative (blurring) range */ + float to_angle = (float) setup->resolution + 1; + to_angle = PI / maxh * (float) LUMA_CUTOFF * (to_angle * to_angle + 1); + + kernels [kernel_size * 3 / 2] = maxh; /* default center value */ + for ( i = 0; i < kernel_half * 2 + 1; i++ ) + { + int x = i - kernel_half; + float angle = x * to_angle; + /* instability occurs at center point with rolloff very close to 1.0 */ + if ( x || pow_a_n > (float) 1.056 || pow_a_n < (float) 0.981 ) + { + float rolloff_cos_a = rolloff * (float) cos( angle ); + float num = 1 - rolloff_cos_a - + pow_a_n * (float) cos( maxh * angle ) + + pow_a_n * rolloff * (float) cos( (maxh - 1) * angle ); + float den = 1 - rolloff_cos_a - rolloff_cos_a + rolloff * rolloff; + float dsf = num / den; + kernels [kernel_size * 3 / 2 - kernel_half + i] = dsf - (float) 0.5; + } + } + + /* apply blackman window and find sum */ + sum = 0; + for ( i = 0; i < kernel_half * 2 + 1; i++ ) + { + float x = PI * 2 / (kernel_half * 2) * i; + float blackman = 0.42f - 0.5f * (float) cos( x ) + 0.08f * (float) cos( x * 2 ); + sum += (kernels [kernel_size * 3 / 2 - kernel_half + i] *= blackman); + } + + /* normalize kernel */ + sum = 1.0f / sum; + for ( i = 0; i < kernel_half * 2 + 1; i++ ) + { + int x = kernel_size * 3 / 2 - kernel_half + i; + kernels [x] *= sum; + assert( kernels [x] == kernels [x] ); /* catch numerical instability */ + } + } + + /* generate chroma (iq) filter using gaussian kernel */ + { + float const cutoff_factor = -0.03125f; + float cutoff = (float) setup->bleed; + int i; + + if ( cutoff < 0 ) + { + /* keep extreme value accessible only near upper end of scale (1.0) */ + cutoff *= cutoff; + cutoff *= cutoff; + cutoff *= cutoff; + cutoff *= -30.0f / 0.65f; + } + cutoff = cutoff_factor - 0.65f * cutoff_factor * cutoff; + + for ( i = -kernel_half; i <= kernel_half; i++ ) + kernels [kernel_size / 2 + i] = (float) exp( i * i * cutoff ); + + /* normalize even and odd phases separately */ + for ( i = 0; i < 2; i++ ) + { + float sum = 0; + int x; + for ( x = i; x < kernel_size; x += 2 ) + sum += kernels [x]; + + sum = 1.0f / sum; + for ( x = i; x < kernel_size; x += 2 ) + { + kernels [x] *= sum; + assert( kernels [x] == kernels [x] ); /* catch numerical instability */ + } + } + } + + /* + printf( "luma:\n" ); + for ( i = kernel_size; i < kernel_size * 2; i++ ) + printf( "%f\n", kernels [i] ); + printf( "chroma:\n" ); + for ( i = 0; i < kernel_size; i++ ) + printf( "%f\n", kernels [i] ); + */ + + /* generate linear rescale kernels */ + #if rescale_out > 1 + { + float weight = 1.0f; + float* out = impl->kernel; + int n = rescale_out; + do + { + float remain = 0; + int i; + weight -= 1.0f / rescale_in; + for ( i = 0; i < kernel_size * 2; i++ ) + { + float cur = kernels [i]; + float m = cur * weight; + *out++ = m + remain; + remain = cur - m; + } + } + while ( --n ); + } + #endif +} + +static float const default_decoder [6] = + { 0.956f, 0.621f, -0.272f, -0.647f, -1.105f, 1.702f }; + +static void init( init_t* impl, snes_ntsc_setup_t const* setup ) +{ + impl->brightness = (float) setup->brightness * (0.5f * rgb_unit) + rgb_offset; + impl->contrast = (float) setup->contrast * (0.5f * rgb_unit) + rgb_unit; + #ifdef default_palette_contrast + if ( !setup->palette ) + impl->contrast *= default_palette_contrast; + #endif + + impl->artifacts = (float) setup->artifacts; + if ( impl->artifacts > 0 ) + impl->artifacts *= artifacts_max - artifacts_mid; + impl->artifacts = impl->artifacts * artifacts_mid + artifacts_mid; + + impl->fringing = (float) setup->fringing; + if ( impl->fringing > 0 ) + impl->fringing *= fringing_max - fringing_mid; + impl->fringing = impl->fringing * fringing_mid + fringing_mid; + + init_filters( impl, setup ); + + /* generate gamma table */ + if ( gamma_size > 1 ) + { + float const to_float = 1.0f / (gamma_size - (gamma_size > 1)); + float const gamma = 1.1333f - (float) setup->gamma * 0.5f; + /* match common PC's 2.2 gamma to TV's 2.65 gamma */ + int i; + for ( i = 0; i < gamma_size; i++ ) + impl->to_float [i] = + (float) pow( i * to_float, gamma ) * impl->contrast + impl->brightness; + } + + /* setup decoder matricies */ + { + float hue = (float) setup->hue * PI + PI / 180 * ext_decoder_hue; + float sat = (float) setup->saturation + 1; + float const* decoder = setup->decoder_matrix; + if ( !decoder ) + { + decoder = default_decoder; + if ( STD_HUE_CONDITION( setup ) ) + hue += PI / 180 * (std_decoder_hue - ext_decoder_hue); + } + + { + float s = (float) sin( hue ) * sat; + float c = (float) cos( hue ) * sat; + float* out = impl->to_rgb; + int n; + + n = burst_count; + do + { + float const* in = decoder; + int n = 3; + do + { + float i = *in++; + float q = *in++; + *out++ = i * c - q * s; + *out++ = i * s + q * c; + } + while ( --n ); + if ( burst_count <= 1 ) + break; + ROTATE_IQ( s, c, 0.866025f, -0.5f ); /* +120 degrees */ + } + while ( --n ); + } + } +} + +/* kernel generation */ + +#define RGB_TO_YIQ( r, g, b, y, i ) (\ + (y = (r) * 0.299f + (g) * 0.587f + (b) * 0.114f),\ + (i = (r) * 0.596f - (g) * 0.275f - (b) * 0.321f),\ + ((r) * 0.212f - (g) * 0.523f + (b) * 0.311f)\ +) + +#define YIQ_TO_RGB( y, i, q, to_rgb, type, r, g ) (\ + r = (type) (y + to_rgb [0] * i + to_rgb [1] * q),\ + g = (type) (y + to_rgb [2] * i + to_rgb [3] * q),\ + (type) (y + to_rgb [4] * i + to_rgb [5] * q)\ +) + +#define PACK_RGB( r, g, b ) ((r) << 21 | (g) << 11 | (b) << 1) + +enum { rgb_kernel_size = burst_size / alignment_count }; +enum { rgb_bias = rgb_unit * 2 * snes_ntsc_rgb_builder }; + +typedef struct pixel_info_t +{ + int offset; + float negate; + float kernel [4]; +} pixel_info_t; + +#if rescale_in > 1 + #define PIXEL_OFFSET_( ntsc, scaled ) \ + (kernel_size / 2 + ntsc + (scaled != 0) + (rescale_out - scaled) % rescale_out + \ + (kernel_size * 2 * scaled)) + + #define PIXEL_OFFSET( ntsc, scaled ) \ + PIXEL_OFFSET_( ((ntsc) - (scaled) / rescale_out * rescale_in),\ + (((scaled) + rescale_out * 10) % rescale_out) ),\ + (1.0f - (((ntsc) + 100) & 2)) +#else + #define PIXEL_OFFSET( ntsc, scaled ) \ + (kernel_size / 2 + (ntsc) - (scaled)),\ + (1.0f - (((ntsc) + 100) & 2)) +#endif + +extern pixel_info_t const snes_ntsc_pixels [alignment_count]; + +/* Generate pixel at all burst phases and column alignments */ +static void gen_kernel( init_t* impl, float y, float i, float q, snes_ntsc_rgb_t* out ) +{ + /* generate for each scanline burst phase */ + float const* to_rgb = impl->to_rgb; + int burst_remain = burst_count; + y -= rgb_offset; + do + { + /* Encode yiq into *two* composite signals (to allow control over artifacting). + Convolve these with kernels which: filter respective components, apply + sharpening, and rescale horizontally. Convert resulting yiq to rgb and pack + into integer. Based on algorithm by NewRisingSun. */ + pixel_info_t const* pixel = snes_ntsc_pixels; + int alignment_remain = alignment_count; + do + { + /* negate is -1 when composite starts at odd multiple of 2 */ + float const yy = y * impl->fringing * pixel->negate; + float const ic0 = (i + yy) * pixel->kernel [0]; + float const qc1 = (q + yy) * pixel->kernel [1]; + float const ic2 = (i - yy) * pixel->kernel [2]; + float const qc3 = (q - yy) * pixel->kernel [3]; + + float const factor = impl->artifacts * pixel->negate; + float const ii = i * factor; + float const yc0 = (y + ii) * pixel->kernel [0]; + float const yc2 = (y - ii) * pixel->kernel [2]; + + float const qq = q * factor; + float const yc1 = (y + qq) * pixel->kernel [1]; + float const yc3 = (y - qq) * pixel->kernel [3]; + + float const* k = &impl->kernel [pixel->offset]; + int n; + ++pixel; + for ( n = rgb_kernel_size; n; --n ) + { + float i = k[0]*ic0 + k[2]*ic2; + float q = k[1]*qc1 + k[3]*qc3; + float y = k[kernel_size+0]*yc0 + k[kernel_size+1]*yc1 + + k[kernel_size+2]*yc2 + k[kernel_size+3]*yc3 + rgb_offset; + if ( rescale_out <= 1 ) + k--; + else if ( k < &impl->kernel [kernel_size * 2 * (rescale_out - 1)] ) + k += kernel_size * 2 - 1; + else + k -= kernel_size * 2 * (rescale_out - 1) + 2; + { + int r, g, b = YIQ_TO_RGB( y, i, q, to_rgb, int, r, g ); + *out++ = PACK_RGB( r, g, b ) - rgb_bias; + } + } + } + while ( alignment_count > 1 && --alignment_remain ); + + if ( burst_count <= 1 ) + break; + + to_rgb += 6; + + ROTATE_IQ( i, q, -0.866025f, -0.5f ); /* -120 degrees */ + } + while ( --burst_remain ); +} + +static void correct_errors( snes_ntsc_rgb_t color, snes_ntsc_rgb_t* out ); + +#if DISABLE_CORRECTION + #define CORRECT_ERROR( a ) { out [i] += rgb_bias; } + #define DISTRIBUTE_ERROR( a, b, c ) { out [i] += rgb_bias; } +#else + #define CORRECT_ERROR( a ) { out [a] += error; } + #define DISTRIBUTE_ERROR( a, b, c ) {\ + snes_ntsc_rgb_t fourth = (error + 2 * snes_ntsc_rgb_builder) >> 2;\ + fourth &= (rgb_bias >> 1) - snes_ntsc_rgb_builder;\ + fourth -= rgb_bias >> 2;\ + out [a] += fourth;\ + out [b] += fourth;\ + out [c] += fourth;\ + out [i] += error - (fourth * 3);\ + } +#endif + +#define RGB_PALETTE_OUT( rgb, out_ )\ +{\ + unsigned char* out = (out_);\ + snes_ntsc_rgb_t clamped = (rgb);\ + SNES_NTSC_CLAMP_( clamped, (8 - rgb_bits) );\ + out [0] = (unsigned char) (clamped >> 21);\ + out [1] = (unsigned char) (clamped >> 11);\ + out [2] = (unsigned char) (clamped >> 1);\ +} + +/* blitter related */ + +#ifndef restrict + #if defined (__GNUC__) + #define restrict __restrict__ + #elif defined (_MSC_VER) && _MSC_VER > 1300 + #define restrict __restrict + #else + /* no support for restricted pointers */ + #define restrict + #endif +#endif + +#include + +#if SNES_NTSC_OUT_DEPTH <= 16 + #if USHRT_MAX == 0xFFFF + typedef unsigned short snes_ntsc_out_t; + #else + #error "Need 16-bit int type" + #endif + +#else + #if UINT_MAX == 0xFFFFFFFF + typedef unsigned int snes_ntsc_out_t; + #elif ULONG_MAX == 0xFFFFFFFF + typedef unsigned long snes_ntsc_out_t; + #else + #error "Need 32-bit int type" + #endif + +#endif diff --git a/snesfilter/pixellate2x/pixellate2x.cpp b/snesfilter/pixellate2x/pixellate2x.cpp new file mode 100644 index 00000000..ca8f79f3 --- /dev/null +++ b/snesfilter/pixellate2x/pixellate2x.cpp @@ -0,0 +1,38 @@ +#include "pixellate2x.hpp" + +void Pixellate2xFilter::size(unsigned &outwidth, unsigned &outheight, unsigned width, unsigned height) { + outwidth = (width <= 256) ? width * 2 : width; + outheight = (height <= 240) ? height * 2 : height; +} + +void Pixellate2xFilter::render( + uint32_t *output, unsigned outpitch, + const uint16_t *input, unsigned pitch, unsigned width, unsigned height +) { + pitch >>= 1; + outpitch >>= 2; + + uint32_t *out0 = output; + uint32_t *out1 = output + outpitch; + + for(unsigned y = 0; y < height; y++) { + for(unsigned x = 0; x < width; x++) { + uint32_t p = colortable[*input++]; + + *out0++ = p; + if(height <= 240) *out1++ = p; + if(width > 256) continue; + + *out0++ = p; + if(height <= 240) *out1++ = p; + } + + input += pitch - width; + if(height <= 240) { + out0 += outpitch + outpitch - 512; + out1 += outpitch + outpitch - 512; + } else { + out0 += outpitch - 512; + } + } +} diff --git a/snesfilter/pixellate2x/pixellate2x.hpp b/snesfilter/pixellate2x/pixellate2x.hpp new file mode 100644 index 00000000..5ba2a1da --- /dev/null +++ b/snesfilter/pixellate2x/pixellate2x.hpp @@ -0,0 +1,5 @@ +class Pixellate2xFilter { +public: + void size(unsigned&, unsigned&, unsigned, unsigned); + void render(uint32_t*, unsigned, const uint16_t*, unsigned, unsigned, unsigned); +} filter_pixellate2x; diff --git a/snesfilter/scale2x/scale2x.cpp b/snesfilter/scale2x/scale2x.cpp new file mode 100644 index 00000000..c9bf0520 --- /dev/null +++ b/snesfilter/scale2x/scale2x.cpp @@ -0,0 +1,53 @@ +#include "scale2x.hpp" + +void Scale2xFilter::size(unsigned &outwidth, unsigned &outheight, unsigned width, unsigned height) { + if(width > 256 || height > 240) return filter_direct.size(outwidth, outheight, width, height); + outwidth = width * 2; + outheight = height * 2; +} + +void Scale2xFilter::render( + uint32_t *output, unsigned outpitch, + const uint16_t *input, unsigned pitch, unsigned width, unsigned height +) { + if(width > 256 || height > 240) { + filter_direct.render(output, outpitch, input, pitch, width, height); + return; + } + + pitch >>= 1; + outpitch >>= 2; + + uint32_t *out0 = output; + uint32_t *out1 = output + outpitch; + + for(unsigned y = 0; y < height; y++) { + int prevline = (y == 0 ? 0 : pitch); + int nextline = (y == height - 1 ? 0 : pitch); + + for(unsigned x = 0; x < width; x++) { + uint16_t A = *(input - prevline); + uint16_t B = (x > 0) ? *(input - 1) : *input; + uint16_t C = *input; + uint16_t D = (x < 255) ? *(input + 1) : *input; + uint16_t E = *(input++ + nextline); + uint32_t c = colortable[C]; + + if(A != E && B != D) { + *out0++ = (A == B ? colortable[A] : c); + *out0++ = (A == D ? colortable[A] : c); + *out1++ = (E == B ? colortable[E] : c); + *out1++ = (E == D ? colortable[E] : c); + } else { + *out0++ = c; + *out0++ = c; + *out1++ = c; + *out1++ = c; + } + } + + input += pitch - width; + out0 += outpitch + outpitch - 512; + out1 += outpitch + outpitch - 512; + } +} diff --git a/snesfilter/scale2x/scale2x.hpp b/snesfilter/scale2x/scale2x.hpp new file mode 100644 index 00000000..58f7b1bb --- /dev/null +++ b/snesfilter/scale2x/scale2x.hpp @@ -0,0 +1,5 @@ +class Scale2xFilter { +public: + void size(unsigned&, unsigned&, unsigned, unsigned); + void render(uint32_t*, unsigned, const uint16_t*, unsigned, unsigned, unsigned); +} filter_scale2x; diff --git a/snesfilter/snesfilter.cpp b/snesfilter/snesfilter.cpp new file mode 100644 index 00000000..30045474 --- /dev/null +++ b/snesfilter/snesfilter.cpp @@ -0,0 +1,83 @@ +#include "snesfilter.hpp" + +#if defined(_WIN32) + #define dllexport __declspec(dllexport) +#else + #define dllexport +#endif + +#include +#include +#include + +#define QT_CORE_LIB +#include + +#include +#include +#include +using namespace nall; + +const uint32_t *colortable; +configuration *config; + +#include "direct/direct.cpp" +#include "pixellate2x/pixellate2x.cpp" +#include "scale2x/scale2x.cpp" +#include "2xsai/2xsai.cpp" +#include "lq2x/lq2x.cpp" +#include "hq2x/hq2x.cpp" +#include "ntsc/ntsc.cpp" + +dllexport const char* snesfilter_supported() { + return "Pixellate2x;Scale2x;2xSaI;Super 2xSaI;Super Eagle;LQ2x;HQ2x;NTSC"; +} + +dllexport void snesfilter_configuration(configuration &config_) { + config = &config_; + if(config) { + filter_ntsc.bind(*config); + } +} + +dllexport void snesfilter_colortable(const uint32_t *colortable_) { + colortable = colortable_; +} + +dllexport void snesfilter_size(unsigned filter, unsigned &outwidth, unsigned &outheight, unsigned width, unsigned height) { + switch(filter) { + default: return filter_direct.size(outwidth, outheight, width, height); + case 1: return filter_pixellate2x.size(outwidth, outheight, width, height); + case 2: return filter_scale2x.size(outwidth, outheight, width, height); + case 3: return filter_2xsai.size(outwidth, outheight, width, height); + case 4: return filter_super2xsai.size(outwidth, outheight, width, height); + case 5: return filter_supereagle.size(outwidth, outheight, width, height); + case 6: return filter_lq2x.size(outwidth, outheight, width, height); + case 7: return filter_hq2x.size(outwidth, outheight, width, height); + case 8: return filter_ntsc.size(outwidth, outheight, width, height); + } +} + +dllexport void snesfilter_render( + unsigned filter, uint32_t *output, unsigned outpitch, + const uint16_t *input, unsigned pitch, unsigned width, unsigned height +) { + switch(filter) { + default: return filter_direct.render(output, outpitch, input, pitch, width, height); + case 1: return filter_pixellate2x.render(output, outpitch, input, pitch, width, height); + case 2: return filter_scale2x.render(output, outpitch, input, pitch, width, height); + case 3: return filter_2xsai.render(output, outpitch, input, pitch, width, height); + case 4: return filter_super2xsai.render(output, outpitch, input, pitch, width, height); + case 5: return filter_supereagle.render(output, outpitch, input, pitch, width, height); + case 6: return filter_lq2x.render(output, outpitch, input, pitch, width, height); + case 7: return filter_hq2x.render(output, outpitch, input, pitch, width, height); + case 8: return filter_ntsc.render(output, outpitch, input, pitch, width, height); + } +} + +dllexport QWidget* snesfilter_settings(unsigned filter) { + switch(filter) { + default: return 0; + case 8: return filter_ntsc.settings(); + } +} diff --git a/snesfilter/snesfilter.hpp b/snesfilter/snesfilter.hpp new file mode 100644 index 00000000..8c390dff --- /dev/null +++ b/snesfilter/snesfilter.hpp @@ -0,0 +1,12 @@ +#include +class QWidget; +namespace nall { class configuration; } + +extern "C" { + const char* snesfilter_supported(); + void snesfilter_configuration(nall::configuration&); + void snesfilter_colortable(const uint32_t*); + void snesfilter_size(unsigned, unsigned&, unsigned&, unsigned, unsigned); + void snesfilter_render(unsigned, uint32_t*, unsigned, const uint16_t*, unsigned, unsigned, unsigned); + QWidget* snesfilter_settings(unsigned); +} diff --git a/snesfilter/sync.sh b/snesfilter/sync.sh new file mode 100644 index 00000000..4bbaf34f --- /dev/null +++ b/snesfilter/sync.sh @@ -0,0 +1,2 @@ +rm -r nall +cp -r ../nall ./nall diff --git a/snesreader.dll b/snesreader.dll deleted file mode 100644 index 9ed8542c6eff899169a33e34084cb3d3a22f3fc4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 761582 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 z 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 GevS+mMyREV}5T>+$bbo~}Onjq;?3_8pq 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 diff --git a/snesreader/7z_C/7zAlloc.c b/snesreader/7z_C/7zAlloc.c new file mode 100644 index 00000000..4bfaf42a --- /dev/null +++ b/snesreader/7z_C/7zAlloc.c @@ -0,0 +1,77 @@ +/* 7zAlloc.c -- Allocation functions +2008-10-04 : Igor Pavlov : Public domain */ + +#include +#include "7zAlloc.h" + +/* #define _SZ_ALLOC_DEBUG */ +/* use _SZ_ALLOC_DEBUG to debug alloc/free operations */ + +#ifdef _SZ_ALLOC_DEBUG + +#ifdef _WIN32 +#include +#endif + +#include +int g_allocCount = 0; +int g_allocCountTemp = 0; + +#endif + +void *SzAlloc(void *p, size_t size) +{ + p = p; + if (size == 0) + return 0; + #ifdef _SZ_ALLOC_DEBUG + fprintf(stderr, "\nAlloc %10d bytes; count = %10d", size, g_allocCount); + g_allocCount++; + #endif + return malloc(size); +} + +void SzFree(void *p, void *address) +{ + p = p; + #ifdef _SZ_ALLOC_DEBUG + if (address != 0) + { + g_allocCount--; + fprintf(stderr, "\nFree; count = %10d", g_allocCount); + } + #endif + free(address); +} + +void *SzAllocTemp(void *p, size_t size) +{ + p = p; + if (size == 0) + return 0; + #ifdef _SZ_ALLOC_DEBUG + fprintf(stderr, "\nAlloc_temp %10d bytes; count = %10d", size, g_allocCountTemp); + g_allocCountTemp++; + #ifdef _WIN32 + return HeapAlloc(GetProcessHeap(), 0, size); + #endif + #endif + return malloc(size); +} + +void SzFreeTemp(void *p, void *address) +{ + p = p; + #ifdef _SZ_ALLOC_DEBUG + if (address != 0) + { + g_allocCountTemp--; + fprintf(stderr, "\nFree_temp; count = %10d", g_allocCountTemp); + } + #ifdef _WIN32 + HeapFree(GetProcessHeap(), 0, address); + return; + #endif + #endif + free(address); +} diff --git a/snesreader/7z_C/7zAlloc.h b/snesreader/7z_C/7zAlloc.h new file mode 100644 index 00000000..f84ca5ae --- /dev/null +++ b/snesreader/7z_C/7zAlloc.h @@ -0,0 +1,23 @@ +/* 7zAlloc.h -- Allocation functions +2008-10-04 : Igor Pavlov : Public domain */ + +#ifndef __7Z_ALLOC_H +#define __7Z_ALLOC_H + +#include + +#ifdef __cplusplus + extern "C" { +#endif + +void *SzAlloc(void *p, size_t size); +void SzFree(void *p, void *address); + +void *SzAllocTemp(void *p, size_t size); +void SzFreeTemp(void *p, void *address); + +#ifdef __cplusplus + } +#endif + +#endif diff --git a/snesreader/7z_C/7zBuf.c b/snesreader/7z_C/7zBuf.c new file mode 100644 index 00000000..14e7f4e2 --- /dev/null +++ b/snesreader/7z_C/7zBuf.c @@ -0,0 +1,36 @@ +/* 7zBuf.c -- Byte Buffer +2008-03-28 +Igor Pavlov +Public domain */ + +#include "7zBuf.h" + +void Buf_Init(CBuf *p) +{ + p->data = 0; + p->size = 0; +} + +int Buf_Create(CBuf *p, size_t size, ISzAlloc *alloc) +{ + p->size = 0; + if (size == 0) + { + p->data = 0; + return 1; + } + p->data = (Byte *)alloc->Alloc(alloc, size); + if (p->data != 0) + { + p->size = size; + return 1; + } + return 0; +} + +void Buf_Free(CBuf *p, ISzAlloc *alloc) +{ + alloc->Free(alloc, p->data); + p->data = 0; + p->size = 0; +} diff --git a/snesreader/7z_C/7zBuf.h b/snesreader/7z_C/7zBuf.h new file mode 100644 index 00000000..c5bd7187 --- /dev/null +++ b/snesreader/7z_C/7zBuf.h @@ -0,0 +1,31 @@ +/* 7zBuf.h -- Byte Buffer +2008-10-04 : Igor Pavlov : Public domain */ + +#ifndef __7Z_BUF_H +#define __7Z_BUF_H + +#include "Types.h" + +typedef struct +{ + Byte *data; + size_t size; +} CBuf; + +void Buf_Init(CBuf *p); +int Buf_Create(CBuf *p, size_t size, ISzAlloc *alloc); +void Buf_Free(CBuf *p, ISzAlloc *alloc); + +typedef struct +{ + Byte *data; + size_t size; + size_t pos; +} CDynBuf; + +void DynBuf_Construct(CDynBuf *p); +void DynBuf_SeekToBeg(CDynBuf *p); +int DynBuf_Write(CDynBuf *p, const Byte *buf, size_t size, ISzAlloc *alloc); +void DynBuf_Free(CDynBuf *p, ISzAlloc *alloc); + +#endif diff --git a/snesreader/7z_C/7zC.txt b/snesreader/7z_C/7zC.txt new file mode 100644 index 00000000..4ff63250 --- /dev/null +++ b/snesreader/7z_C/7zC.txt @@ -0,0 +1,194 @@ +7z ANSI-C Decoder 4.62 +---------------------- + +7z ANSI-C provides 7z/LZMA decoding. +7z ANSI-C version is simplified version ported from C++ code. + +LZMA is default and general compression method of 7z format +in 7-Zip compression program (www.7-zip.org). LZMA provides high +compression ratio and very fast decompression. + + +LICENSE +------- + +7z ANSI-C Decoder is part of the LZMA SDK. +LZMA SDK is written and placed in the public domain by Igor Pavlov. + +Files +--------------------- + +7zDecode.* - Low level 7z decoding +7zExtract.* - High level 7z decoding +7zHeader.* - .7z format constants +7zIn.* - .7z archive opening +7zItem.* - .7z structures +7zMain.c - Test application + + +How To Use +---------- + +You must download 7-Zip program from www.7-zip.org. + +You can create .7z archive with 7z.exe or 7za.exe: + + 7za.exe a archive.7z *.htm -r -mx -m0fb=255 + +If you have big number of files in archive, and you need fast extracting, +you can use partly-solid archives: + + 7za.exe a archive.7z *.htm -ms=512K -r -mx -m0fb=255 -m0d=512K + +In that example 7-Zip will use 512KB solid blocks. So it needs to decompress only +512KB for extracting one file from such archive. + + +Limitations of current version of 7z ANSI-C Decoder +--------------------------------------------------- + + - It reads only "FileName", "Size", "LastWriteTime" and "CRC" information for each file in archive. + - It supports only LZMA and Copy (no compression) methods with BCJ or BCJ2 filters. + - It converts original UTF-16 Unicode file names to UTF-8 Unicode file names. + +These limitations will be fixed in future versions. + + +Using 7z ANSI-C Decoder Test application: +----------------------------------------- + +Usage: 7zDec + +: + e: Extract files from archive + l: List contents of archive + t: Test integrity of archive + +Example: + + 7zDec l archive.7z + +lists contents of archive.7z + + 7zDec e archive.7z + +extracts files from archive.7z to current folder. + + +How to use .7z Decoder +---------------------- + +Memory allocation +~~~~~~~~~~~~~~~~~ + +7z Decoder uses two memory pools: +1) Temporary pool +2) Main pool +Such scheme can allow you to avoid fragmentation of allocated blocks. + + +Steps for using 7z decoder +-------------------------- + +Use code at 7zMain.c as example. + +1) Declare variables: + inStream /* implements ILookInStream interface */ + CSzArEx db; /* 7z archive database structure */ + ISzAlloc allocImp; /* memory functions for main pool */ + ISzAlloc allocTempImp; /* memory functions for temporary pool */ + +2) call CrcGenerateTable(); function to initialize CRC structures. + +3) call SzArEx_Init(&db); function to initialize db structures. + +4) call SzArEx_Open(&db, inStream, &allocMain, &allocTemp) to open archive + +This function opens archive "inStream" and reads headers to "db". +All items in "db" will be allocated with "allocMain" functions. +SzArEx_Open function allocates and frees temporary structures by "allocTemp" functions. + +5) List items or Extract items + + Listing code: + ~~~~~~~~~~~~~ + { + UInt32 i; + for (i = 0; i < db.db.NumFiles; i++) + { + CFileItem *f = db.db.Files + i; + printf("%10d %s\n", (int)f->Size, f->Name); + } + } + + Extracting code: + ~~~~~~~~~~~~~~~~ + + SZ_RESULT SzAr_Extract( + CArchiveDatabaseEx *db, + ILookInStream *inStream, + UInt32 fileIndex, /* index of file */ + UInt32 *blockIndex, /* index of solid block */ + Byte **outBuffer, /* pointer to pointer to output buffer (allocated with allocMain) */ + size_t *outBufferSize, /* buffer size for output buffer */ + size_t *offset, /* offset of stream for required file in *outBuffer */ + size_t *outSizeProcessed, /* size of file in *outBuffer */ + ISzAlloc *allocMain, + ISzAlloc *allocTemp); + + If you need to decompress more than one file, you can send these values from previous call: + blockIndex, + outBuffer, + outBufferSize, + You can consider "outBuffer" as cache of solid block. If your archive is solid, + it will increase decompression speed. + + After decompressing you must free "outBuffer": + allocImp.Free(outBuffer); + +6) call SzArEx_Free(&db, allocImp.Free) to free allocated items in "db". + + + + +Memory requirements for .7z decoding +------------------------------------ + +Memory usage for Archive opening: + - Temporary pool: + - Memory for uncompressed .7z headers + - some other temporary blocks + - Main pool: + - Memory for database: + Estimated size of one file structures in solid archive: + - Size (4 or 8 Bytes) + - CRC32 (4 bytes) + - LastWriteTime (8 bytes) + - Some file information (4 bytes) + - File Name (variable length) + pointer + allocation structures + +Memory usage for archive Decompressing: + - Temporary pool: + - Memory for LZMA decompressing structures + - Main pool: + - Memory for decompressed solid block + - Memory for temprorary buffers, if BCJ2 fileter is used. Usually these + temprorary buffers can be about 15% of solid block size. + + +7z Decoder doesn't allocate memory for compressed blocks. +Instead of this, you must allocate buffer with desired +size before calling 7z Decoder. Use 7zMain.c as example. + + +Defines +------- + +_SZ_ALLOC_DEBUG - define it if you want to debug alloc/free operations to stderr. + + +--- + +http://www.7-zip.org +http://www.7-zip.org/sdk.html +http://www.7-zip.org/support.html diff --git a/snesreader/7z_C/7zCrc.c b/snesreader/7z_C/7zCrc.c new file mode 100644 index 00000000..71962b2c --- /dev/null +++ b/snesreader/7z_C/7zCrc.c @@ -0,0 +1,35 @@ +/* 7zCrc.c -- CRC32 calculation +2008-08-05 +Igor Pavlov +Public domain */ + +#include "7zCrc.h" + +#define kCrcPoly 0xEDB88320 +UInt32 g_CrcTable[256]; + +void MY_FAST_CALL CrcGenerateTable(void) +{ + UInt32 i; + for (i = 0; i < 256; i++) + { + UInt32 r = i; + int j; + for (j = 0; j < 8; j++) + r = (r >> 1) ^ (kCrcPoly & ~((r & 1) - 1)); + g_CrcTable[i] = r; + } +} + +UInt32 MY_FAST_CALL CrcUpdate(UInt32 v, const void *data, size_t size) +{ + const Byte *p = (const Byte *)data; + for (; size > 0 ; size--, p++) + v = CRC_UPDATE_BYTE(v, *p); + return v; +} + +UInt32 MY_FAST_CALL CrcCalc(const void *data, size_t size) +{ + return CrcUpdate(CRC_INIT_VAL, data, size) ^ 0xFFFFFFFF; +} diff --git a/snesreader/7z_C/7zCrc.h b/snesreader/7z_C/7zCrc.h new file mode 100644 index 00000000..ab8cf8c4 --- /dev/null +++ b/snesreader/7z_C/7zCrc.h @@ -0,0 +1,32 @@ +/* 7zCrc.h -- CRC32 calculation +2008-03-13 +Igor Pavlov +Public domain */ + +#ifndef __7Z_CRC_H +#define __7Z_CRC_H + +#include + +#include "Types.h" + +#ifdef __cplusplus + extern "C" { +#endif + +extern UInt32 g_CrcTable[]; + +void MY_FAST_CALL CrcGenerateTable(void); + +#define CRC_INIT_VAL 0xFFFFFFFF +#define CRC_GET_DIGEST(crc) ((crc) ^ 0xFFFFFFFF) +#define CRC_UPDATE_BYTE(crc, b) (g_CrcTable[((crc) ^ (b)) & 0xFF] ^ ((crc) >> 8)) + +UInt32 MY_FAST_CALL CrcUpdate(UInt32 crc, const void *data, size_t size); +UInt32 MY_FAST_CALL CrcCalc(const void *data, size_t size); + +#ifdef __cplusplus + } +#endif + +#endif diff --git a/snesreader/7z_C/7zDecode.c b/snesreader/7z_C/7zDecode.c new file mode 100644 index 00000000..c643da5f --- /dev/null +++ b/snesreader/7z_C/7zDecode.c @@ -0,0 +1,257 @@ +/* 7zDecode.c -- Decoding from 7z folder +2008-11-23 : Igor Pavlov : Public domain */ + +#include + +#include "Bcj2.h" +#include "Bra.h" +#include "LzmaDec.h" +#include "7zDecode.h" + +#define k_Copy 0 +#define k_LZMA 0x30101 +#define k_BCJ 0x03030103 +#define k_BCJ2 0x0303011B + +static SRes SzDecodeLzma(CSzCoderInfo *coder, UInt64 inSize, ILookInStream *inStream, + Byte *outBuffer, SizeT outSize, ISzAlloc *allocMain) +{ + CLzmaDec state; + SRes res = SZ_OK; + + LzmaDec_Construct(&state); + RINOK(LzmaDec_AllocateProbs(&state, coder->Props.data, (unsigned)coder->Props.size, allocMain)); + state.dic = outBuffer; + state.dicBufSize = outSize; + LzmaDec_Init(&state); + + for (;;) + { + Byte *inBuf = NULL; + size_t lookahead = (1 << 18); + if (lookahead > inSize) + lookahead = (size_t)inSize; + res = inStream->Look((void *)inStream, (void **)&inBuf, &lookahead); + if (res != SZ_OK) + break; + + { + SizeT inProcessed = (SizeT)lookahead, dicPos = state.dicPos; + ELzmaStatus status; + res = LzmaDec_DecodeToDic(&state, outSize, inBuf, &inProcessed, LZMA_FINISH_END, &status); + lookahead -= inProcessed; + inSize -= inProcessed; + if (res != SZ_OK) + break; + if (state.dicPos == state.dicBufSize || (inProcessed == 0 && dicPos == state.dicPos)) + { + if (state.dicBufSize != outSize || lookahead != 0 || + (status != LZMA_STATUS_FINISHED_WITH_MARK && + status != LZMA_STATUS_MAYBE_FINISHED_WITHOUT_MARK)) + res = SZ_ERROR_DATA; + break; + } + res = inStream->Skip((void *)inStream, inProcessed); + if (res != SZ_OK) + break; + } + } + + LzmaDec_FreeProbs(&state, allocMain); + return res; +} + +static SRes SzDecodeCopy(UInt64 inSize, ILookInStream *inStream, Byte *outBuffer) +{ + while (inSize > 0) + { + void *inBuf; + size_t curSize = (1 << 18); + if (curSize > inSize) + curSize = (size_t)inSize; + RINOK(inStream->Look((void *)inStream, (void **)&inBuf, &curSize)); + if (curSize == 0) + return SZ_ERROR_INPUT_EOF; + memcpy(outBuffer, inBuf, curSize); + outBuffer += curSize; + inSize -= curSize; + RINOK(inStream->Skip((void *)inStream, curSize)); + } + return SZ_OK; +} + +#define IS_UNSUPPORTED_METHOD(m) ((m) != k_Copy && (m) != k_LZMA) +#define IS_UNSUPPORTED_CODER(c) (IS_UNSUPPORTED_METHOD(c.MethodID) || c.NumInStreams != 1 || c.NumOutStreams != 1) +#define IS_NO_BCJ(c) (c.MethodID != k_BCJ || c.NumInStreams != 1 || c.NumOutStreams != 1) +#define IS_NO_BCJ2(c) (c.MethodID != k_BCJ2 || c.NumInStreams != 4 || c.NumOutStreams != 1) + +static +SRes CheckSupportedFolder(const CSzFolder *f) +{ + if (f->NumCoders < 1 || f->NumCoders > 4) + return SZ_ERROR_UNSUPPORTED; + if (IS_UNSUPPORTED_CODER(f->Coders[0])) + return SZ_ERROR_UNSUPPORTED; + if (f->NumCoders == 1) + { + if (f->NumPackStreams != 1 || f->PackStreams[0] != 0 || f->NumBindPairs != 0) + return SZ_ERROR_UNSUPPORTED; + return SZ_OK; + } + if (f->NumCoders == 2) + { + if (IS_NO_BCJ(f->Coders[1]) || + f->NumPackStreams != 1 || f->PackStreams[0] != 0 || + f->NumBindPairs != 1 || + f->BindPairs[0].InIndex != 1 || f->BindPairs[0].OutIndex != 0) + return SZ_ERROR_UNSUPPORTED; + return SZ_OK; + } + if (f->NumCoders == 4) + { + if (IS_UNSUPPORTED_CODER(f->Coders[1]) || + IS_UNSUPPORTED_CODER(f->Coders[2]) || + IS_NO_BCJ2(f->Coders[3])) + return SZ_ERROR_UNSUPPORTED; + if (f->NumPackStreams != 4 || + f->PackStreams[0] != 2 || + f->PackStreams[1] != 6 || + f->PackStreams[2] != 1 || + f->PackStreams[3] != 0 || + f->NumBindPairs != 3 || + f->BindPairs[0].InIndex != 5 || f->BindPairs[0].OutIndex != 0 || + f->BindPairs[1].InIndex != 4 || f->BindPairs[1].OutIndex != 1 || + f->BindPairs[2].InIndex != 3 || f->BindPairs[2].OutIndex != 2) + return SZ_ERROR_UNSUPPORTED; + return SZ_OK; + } + return SZ_ERROR_UNSUPPORTED; +} + +static +UInt64 GetSum(const UInt64 *values, UInt32 index) +{ + UInt64 sum = 0; + UInt32 i; + for (i = 0; i < index; i++) + sum += values[i]; + return sum; +} + +static +SRes SzDecode2(const UInt64 *packSizes, const CSzFolder *folder, + ILookInStream *inStream, UInt64 startPos, + Byte *outBuffer, SizeT outSize, ISzAlloc *allocMain, + Byte *tempBuf[]) +{ + UInt32 ci; + SizeT tempSizes[3] = { 0, 0, 0}; + SizeT tempSize3 = 0; + Byte *tempBuf3 = 0; + + RINOK(CheckSupportedFolder(folder)); + + for (ci = 0; ci < folder->NumCoders; ci++) + { + CSzCoderInfo *coder = &folder->Coders[ci]; + + if (coder->MethodID == k_Copy || coder->MethodID == k_LZMA) + { + UInt32 si = 0; + UInt64 offset; + UInt64 inSize; + Byte *outBufCur = outBuffer; + SizeT outSizeCur = outSize; + if (folder->NumCoders == 4) + { + UInt32 indices[] = { 3, 2, 0 }; + UInt64 unpackSize = folder->UnpackSizes[ci]; + si = indices[ci]; + if (ci < 2) + { + Byte *temp; + outSizeCur = (SizeT)unpackSize; + if (outSizeCur != unpackSize) + return SZ_ERROR_MEM; + temp = (Byte *)IAlloc_Alloc(allocMain, outSizeCur); + if (temp == 0 && outSizeCur != 0) + return SZ_ERROR_MEM; + outBufCur = tempBuf[1 - ci] = temp; + tempSizes[1 - ci] = outSizeCur; + } + else if (ci == 2) + { + if (unpackSize > outSize) /* check it */ + return SZ_ERROR_PARAM; + tempBuf3 = outBufCur = outBuffer + (outSize - (size_t)unpackSize); + tempSize3 = outSizeCur = (SizeT)unpackSize; + } + else + return SZ_ERROR_UNSUPPORTED; + } + offset = GetSum(packSizes, si); + inSize = packSizes[si]; + RINOK(LookInStream_SeekTo(inStream, startPos + offset)); + + if (coder->MethodID == k_Copy) + { + if (inSize != outSizeCur) /* check it */ + return SZ_ERROR_DATA; + RINOK(SzDecodeCopy(inSize, inStream, outBufCur)); + } + else + { + RINOK(SzDecodeLzma(coder, inSize, inStream, outBufCur, outSizeCur, allocMain)); + } + } + else if (coder->MethodID == k_BCJ) + { + UInt32 state; + if (ci != 1) + return SZ_ERROR_UNSUPPORTED; + x86_Convert_Init(state); + x86_Convert(outBuffer, outSize, 0, &state, 0); + } + else if (coder->MethodID == k_BCJ2) + { + UInt64 offset = GetSum(packSizes, 1); + UInt64 s3Size = packSizes[1]; + SRes res; + if (ci != 3) + return SZ_ERROR_UNSUPPORTED; + RINOK(LookInStream_SeekTo(inStream, startPos + offset)); + tempSizes[2] = (SizeT)s3Size; + if (tempSizes[2] != s3Size) + return SZ_ERROR_MEM; + tempBuf[2] = (Byte *)IAlloc_Alloc(allocMain, tempSizes[2]); + if (tempBuf[2] == 0 && tempSizes[2] != 0) + return SZ_ERROR_MEM; + res = SzDecodeCopy(s3Size, inStream, tempBuf[2]); + RINOK(res) + + res = Bcj2_Decode( + tempBuf3, tempSize3, + tempBuf[0], tempSizes[0], + tempBuf[1], tempSizes[1], + tempBuf[2], tempSizes[2], + outBuffer, outSize); + RINOK(res) + } + else + return SZ_ERROR_UNSUPPORTED; + } + return SZ_OK; +} + +SRes SzDecode(const UInt64 *packSizes, const CSzFolder *folder, + ILookInStream *inStream, UInt64 startPos, + Byte *outBuffer, size_t outSize, ISzAlloc *allocMain) +{ + Byte *tempBuf[3] = { 0, 0, 0}; + int i; + SRes res = SzDecode2(packSizes, folder, inStream, startPos, + outBuffer, (SizeT)outSize, allocMain, tempBuf); + for (i = 0; i < 3; i++) + IAlloc_Free(allocMain, tempBuf[i]); + return res; +} diff --git a/snesreader/7z_C/7zDecode.h b/snesreader/7z_C/7zDecode.h new file mode 100644 index 00000000..e19fe387 --- /dev/null +++ b/snesreader/7z_C/7zDecode.h @@ -0,0 +1,13 @@ +/* 7zDecode.h -- Decoding from 7z folder +2008-11-23 : Igor Pavlov : Public domain */ + +#ifndef __7Z_DECODE_H +#define __7Z_DECODE_H + +#include "7zItem.h" + +SRes SzDecode(const UInt64 *packSizes, const CSzFolder *folder, + ILookInStream *stream, UInt64 startPos, + Byte *outBuffer, size_t outSize, ISzAlloc *allocMain); + +#endif diff --git a/snesreader/7z_C/7zExtract.c b/snesreader/7z_C/7zExtract.c new file mode 100644 index 00000000..99ef3654 --- /dev/null +++ b/snesreader/7z_C/7zExtract.c @@ -0,0 +1,93 @@ +/* 7zExtract.c -- Extracting from 7z archive +2008-11-23 : Igor Pavlov : Public domain */ + +#include "7zCrc.h" +#include "7zDecode.h" +#include "7zExtract.h" + +SRes SzAr_Extract( + const CSzArEx *p, + ILookInStream *inStream, + UInt32 fileIndex, + UInt32 *blockIndex, + Byte **outBuffer, + size_t *outBufferSize, + size_t *offset, + size_t *outSizeProcessed, + ISzAlloc *allocMain, + ISzAlloc *allocTemp) +{ + UInt32 folderIndex = p->FileIndexToFolderIndexMap[fileIndex]; + SRes res = SZ_OK; + *offset = 0; + *outSizeProcessed = 0; + if (folderIndex == (UInt32)-1) + { + IAlloc_Free(allocMain, *outBuffer); + *blockIndex = folderIndex; + *outBuffer = 0; + *outBufferSize = 0; + return SZ_OK; + } + + if (*outBuffer == 0 || *blockIndex != folderIndex) + { + CSzFolder *folder = p->db.Folders + folderIndex; + UInt64 unpackSizeSpec = SzFolder_GetUnpackSize(folder); + size_t unpackSize = (size_t)unpackSizeSpec; + UInt64 startOffset = SzArEx_GetFolderStreamPos(p, folderIndex, 0); + + if (unpackSize != unpackSizeSpec) + return SZ_ERROR_MEM; + *blockIndex = folderIndex; + IAlloc_Free(allocMain, *outBuffer); + *outBuffer = 0; + + RINOK(LookInStream_SeekTo(inStream, startOffset)); + + if (res == SZ_OK) + { + *outBufferSize = unpackSize; + if (unpackSize != 0) + { + *outBuffer = (Byte *)IAlloc_Alloc(allocMain, unpackSize); + if (*outBuffer == 0) + res = SZ_ERROR_MEM; + } + if (res == SZ_OK) + { + res = SzDecode(p->db.PackSizes + + p->FolderStartPackStreamIndex[folderIndex], folder, + inStream, startOffset, + *outBuffer, unpackSize, allocTemp); + if (res == SZ_OK) + { + if (folder->UnpackCRCDefined) + { + if (CrcCalc(*outBuffer, unpackSize) != folder->UnpackCRC) + res = SZ_ERROR_CRC; + } + } + } + } + } + if (res == SZ_OK) + { + UInt32 i; + CSzFileItem *fileItem = p->db.Files + fileIndex; + *offset = 0; + for (i = p->FolderStartFileIndex[folderIndex]; i < fileIndex; i++) + *offset += (UInt32)p->db.Files[i].Size; + *outSizeProcessed = (size_t)fileItem->Size; + if (*offset + *outSizeProcessed > *outBufferSize) + return SZ_ERROR_FAIL; + { + if (fileItem->FileCRCDefined) + { + if (CrcCalc(*outBuffer + *offset, *outSizeProcessed) != fileItem->FileCRC) + res = SZ_ERROR_CRC; + } + } + } + return res; +} diff --git a/snesreader/7z_C/7zExtract.h b/snesreader/7z_C/7zExtract.h new file mode 100644 index 00000000..1ca110c6 --- /dev/null +++ b/snesreader/7z_C/7zExtract.h @@ -0,0 +1,49 @@ +/* 7zExtract.h -- Extracting from 7z archive +2008-11-23 : Igor Pavlov : Public domain */ + +#ifndef __7Z_EXTRACT_H +#define __7Z_EXTRACT_H + +#include "7zIn.h" + +#ifdef __cplusplus + extern "C" { +#endif + +/* + SzExtract extracts file from archive + + *outBuffer must be 0 before first call for each new archive. + + Extracting cache: + If you need to decompress more than one file, you can send + these values from previous call: + *blockIndex, + *outBuffer, + *outBufferSize + You can consider "*outBuffer" as cache of solid block. If your archive is solid, + it will increase decompression speed. + + If you use external function, you can declare these 3 cache variables + (blockIndex, outBuffer, outBufferSize) as static in that external function. + + Free *outBuffer and set *outBuffer to 0, if you want to flush cache. +*/ + +SRes SzAr_Extract( + const CSzArEx *db, + ILookInStream *inStream, + UInt32 fileIndex, /* index of file */ + UInt32 *blockIndex, /* index of solid block */ + Byte **outBuffer, /* pointer to pointer to output buffer (allocated with allocMain) */ + size_t *outBufferSize, /* buffer size for output buffer */ + size_t *offset, /* offset of stream for required file in *outBuffer */ + size_t *outSizeProcessed, /* size of file in *outBuffer */ + ISzAlloc *allocMain, + ISzAlloc *allocTemp); + +#ifdef __cplusplus + } +#endif + +#endif diff --git a/snesreader/7z_C/7zHeader.c b/snesreader/7z_C/7zHeader.c new file mode 100644 index 00000000..e48faa48 --- /dev/null +++ b/snesreader/7z_C/7zHeader.c @@ -0,0 +1,6 @@ +/* 7zHeader.c -- 7z Headers +2008-10-04 : Igor Pavlov : Public domain */ + +#include "7zHeader.h" + +Byte k7zSignature[k7zSignatureSize] = {'7', 'z', 0xBC, 0xAF, 0x27, 0x1C}; diff --git a/snesreader/7z_C/7zHeader.h b/snesreader/7z_C/7zHeader.h new file mode 100644 index 00000000..ad095df4 --- /dev/null +++ b/snesreader/7z_C/7zHeader.h @@ -0,0 +1,57 @@ +/* 7zHeader.h -- 7z Headers +2008-10-04 : Igor Pavlov : Public domain */ + +#ifndef __7Z_HEADER_H +#define __7Z_HEADER_H + +#include "Types.h" + +#define k7zSignatureSize 6 +extern Byte k7zSignature[k7zSignatureSize]; + +#define k7zMajorVersion 0 + +#define k7zStartHeaderSize 0x20 + +enum EIdEnum +{ + k7zIdEnd, + + k7zIdHeader, + + k7zIdArchiveProperties, + + k7zIdAdditionalStreamsInfo, + k7zIdMainStreamsInfo, + k7zIdFilesInfo, + + k7zIdPackInfo, + k7zIdUnpackInfo, + k7zIdSubStreamsInfo, + + k7zIdSize, + k7zIdCRC, + + k7zIdFolder, + + k7zIdCodersUnpackSize, + k7zIdNumUnpackStream, + + k7zIdEmptyStream, + k7zIdEmptyFile, + k7zIdAnti, + + k7zIdName, + k7zIdCTime, + k7zIdATime, + k7zIdMTime, + k7zIdWinAttributes, + k7zIdComment, + + k7zIdEncodedHeader, + + k7zIdStartPos, + k7zIdDummy +}; + +#endif diff --git a/snesreader/7z_C/7zIn.c b/snesreader/7z_C/7zIn.c new file mode 100644 index 00000000..e594b7de --- /dev/null +++ b/snesreader/7z_C/7zIn.c @@ -0,0 +1,1204 @@ +/* 7zIn.c -- 7z Input functions +2008-12-31 : Igor Pavlov : Public domain */ + +#include "7zCrc.h" +#include "CpuArch.h" + +#include "7zDecode.h" +#include "7zIn.h" + +#define RINOM(x) { if ((x) == 0) return SZ_ERROR_MEM; } + +#define NUM_FOLDER_CODERS_MAX 32 +#define NUM_CODER_STREAMS_MAX 32 + +void SzArEx_Init(CSzArEx *p) +{ + SzAr_Init(&p->db); + p->FolderStartPackStreamIndex = 0; + p->PackStreamStartPositions = 0; + p->FolderStartFileIndex = 0; + p->FileIndexToFolderIndexMap = 0; +} + +void SzArEx_Free(CSzArEx *p, ISzAlloc *alloc) +{ + IAlloc_Free(alloc, p->FolderStartPackStreamIndex); + IAlloc_Free(alloc, p->PackStreamStartPositions); + IAlloc_Free(alloc, p->FolderStartFileIndex); + IAlloc_Free(alloc, p->FileIndexToFolderIndexMap); + SzAr_Free(&p->db, alloc); + SzArEx_Init(p); +} + +/* +UInt64 GetFolderPackStreamSize(int folderIndex, int streamIndex) const +{ + return PackSizes[FolderStartPackStreamIndex[folderIndex] + streamIndex]; +} + +UInt64 GetFilePackSize(int fileIndex) const +{ + int folderIndex = FileIndexToFolderIndexMap[fileIndex]; + if (folderIndex >= 0) + { + const CSzFolder &folderInfo = Folders[folderIndex]; + if (FolderStartFileIndex[folderIndex] == fileIndex) + return GetFolderFullPackSize(folderIndex); + } + return 0; +} +*/ + +#define MY_ALLOC(T, p, size, alloc) { if ((size) == 0) p = 0; else \ + if ((p = (T *)IAlloc_Alloc(alloc, (size) * sizeof(T))) == 0) return SZ_ERROR_MEM; } + +static SRes SzArEx_Fill(CSzArEx *p, ISzAlloc *alloc) +{ + UInt32 startPos = 0; + UInt64 startPosSize = 0; + UInt32 i; + UInt32 folderIndex = 0; + UInt32 indexInFolder = 0; + MY_ALLOC(UInt32, p->FolderStartPackStreamIndex, p->db.NumFolders, alloc); + for (i = 0; i < p->db.NumFolders; i++) + { + p->FolderStartPackStreamIndex[i] = startPos; + startPos += p->db.Folders[i].NumPackStreams; + } + + MY_ALLOC(UInt64, p->PackStreamStartPositions, p->db.NumPackStreams, alloc); + + for (i = 0; i < p->db.NumPackStreams; i++) + { + p->PackStreamStartPositions[i] = startPosSize; + startPosSize += p->db.PackSizes[i]; + } + + MY_ALLOC(UInt32, p->FolderStartFileIndex, p->db.NumFolders, alloc); + MY_ALLOC(UInt32, p->FileIndexToFolderIndexMap, p->db.NumFiles, alloc); + + for (i = 0; i < p->db.NumFiles; i++) + { + CSzFileItem *file = p->db.Files + i; + int emptyStream = !file->HasStream; + if (emptyStream && indexInFolder == 0) + { + p->FileIndexToFolderIndexMap[i] = (UInt32)-1; + continue; + } + if (indexInFolder == 0) + { + /* + v3.13 incorrectly worked with empty folders + v4.07: Loop for skipping empty folders + */ + for (;;) + { + if (folderIndex >= p->db.NumFolders) + return SZ_ERROR_ARCHIVE; + p->FolderStartFileIndex[folderIndex] = i; + if (p->db.Folders[folderIndex].NumUnpackStreams != 0) + break; + folderIndex++; + } + } + p->FileIndexToFolderIndexMap[i] = folderIndex; + if (emptyStream) + continue; + indexInFolder++; + if (indexInFolder >= p->db.Folders[folderIndex].NumUnpackStreams) + { + folderIndex++; + indexInFolder = 0; + } + } + return SZ_OK; +} + + +UInt64 SzArEx_GetFolderStreamPos(const CSzArEx *p, UInt32 folderIndex, UInt32 indexInFolder) +{ + return p->dataPos + + p->PackStreamStartPositions[p->FolderStartPackStreamIndex[folderIndex] + indexInFolder]; +} + +int SzArEx_GetFolderFullPackSize(const CSzArEx *p, UInt32 folderIndex, UInt64 *resSize) +{ + UInt32 packStreamIndex = p->FolderStartPackStreamIndex[folderIndex]; + CSzFolder *folder = p->db.Folders + folderIndex; + UInt64 size = 0; + UInt32 i; + for (i = 0; i < folder->NumPackStreams; i++) + { + UInt64 t = size + p->db.PackSizes[packStreamIndex + i]; + if (t < size) /* check it */ + return SZ_ERROR_FAIL; + size = t; + } + *resSize = size; + return SZ_OK; +} + + +/* +SRes SzReadTime(const CObjectVector &dataVector, + CObjectVector &files, UInt64 type) +{ + CBoolVector boolVector; + RINOK(ReadBoolVector2(files.Size(), boolVector)) + + CStreamSwitch streamSwitch; + RINOK(streamSwitch.Set(this, &dataVector)); + + for (int i = 0; i < files.Size(); i++) + { + CSzFileItem &file = files[i]; + CArchiveFileTime fileTime; + bool defined = boolVector[i]; + if (defined) + { + UInt32 low, high; + RINOK(SzReadUInt32(low)); + RINOK(SzReadUInt32(high)); + fileTime.dwLowDateTime = low; + fileTime.dwHighDateTime = high; + } + switch(type) + { + case k7zIdCTime: file.IsCTimeDefined = defined; if (defined) file.CTime = fileTime; break; + case k7zIdATime: file.IsATimeDefined = defined; if (defined) file.ATime = fileTime; break; + case k7zIdMTime: file.IsMTimeDefined = defined; if (defined) file.MTime = fileTime; break; + } + } + return SZ_OK; +} +*/ + +static int TestSignatureCandidate(Byte *testBytes) +{ + size_t i; + for (i = 0; i < k7zSignatureSize; i++) + if (testBytes[i] != k7zSignature[i]) + return 0; + return 1; +} + +typedef struct _CSzState +{ + Byte *Data; + size_t Size; +}CSzData; + +static SRes SzReadByte(CSzData *sd, Byte *b) +{ + if (sd->Size == 0) + return SZ_ERROR_ARCHIVE; + sd->Size--; + *b = *sd->Data++; + return SZ_OK; +} + +static SRes SzReadBytes(CSzData *sd, Byte *data, size_t size) +{ + size_t i; + for (i = 0; i < size; i++) + { + RINOK(SzReadByte(sd, data + i)); + } + return SZ_OK; +} + +static SRes SzReadUInt32(CSzData *sd, UInt32 *value) +{ + int i; + *value = 0; + for (i = 0; i < 4; i++) + { + Byte b; + RINOK(SzReadByte(sd, &b)); + *value |= ((UInt32)(b) << (8 * i)); + } + return SZ_OK; +} + +static SRes SzReadNumber(CSzData *sd, UInt64 *value) +{ + Byte firstByte; + Byte mask = 0x80; + int i; + RINOK(SzReadByte(sd, &firstByte)); + *value = 0; + for (i = 0; i < 8; i++) + { + Byte b; + if ((firstByte & mask) == 0) + { + UInt64 highPart = firstByte & (mask - 1); + *value += (highPart << (8 * i)); + return SZ_OK; + } + RINOK(SzReadByte(sd, &b)); + *value |= ((UInt64)b << (8 * i)); + mask >>= 1; + } + return SZ_OK; +} + +static SRes SzReadNumber32(CSzData *sd, UInt32 *value) +{ + UInt64 value64; + RINOK(SzReadNumber(sd, &value64)); + if (value64 >= 0x80000000) + return SZ_ERROR_UNSUPPORTED; + if (value64 >= ((UInt64)(1) << ((sizeof(size_t) - 1) * 8 + 2))) + return SZ_ERROR_UNSUPPORTED; + *value = (UInt32)value64; + return SZ_OK; +} + +static SRes SzReadID(CSzData *sd, UInt64 *value) +{ + return SzReadNumber(sd, value); +} + +static SRes SzSkeepDataSize(CSzData *sd, UInt64 size) +{ + if (size > sd->Size) + return SZ_ERROR_ARCHIVE; + sd->Size -= (size_t)size; + sd->Data += (size_t)size; + return SZ_OK; +} + +static SRes SzSkeepData(CSzData *sd) +{ + UInt64 size; + RINOK(SzReadNumber(sd, &size)); + return SzSkeepDataSize(sd, size); +} + +static SRes SzReadArchiveProperties(CSzData *sd) +{ + for (;;) + { + UInt64 type; + RINOK(SzReadID(sd, &type)); + if (type == k7zIdEnd) + break; + SzSkeepData(sd); + } + return SZ_OK; +} + +static SRes SzWaitAttribute(CSzData *sd, UInt64 attribute) +{ + for (;;) + { + UInt64 type; + RINOK(SzReadID(sd, &type)); + if (type == attribute) + return SZ_OK; + if (type == k7zIdEnd) + return SZ_ERROR_ARCHIVE; + RINOK(SzSkeepData(sd)); + } +} + +static SRes SzReadBoolVector(CSzData *sd, size_t numItems, Byte **v, ISzAlloc *alloc) +{ + Byte b = 0; + Byte mask = 0; + size_t i; + MY_ALLOC(Byte, *v, numItems, alloc); + for (i = 0; i < numItems; i++) + { + if (mask == 0) + { + RINOK(SzReadByte(sd, &b)); + mask = 0x80; + } + (*v)[i] = (Byte)(((b & mask) != 0) ? 1 : 0); + mask >>= 1; + } + return SZ_OK; +} + +static SRes SzReadBoolVector2(CSzData *sd, size_t numItems, Byte **v, ISzAlloc *alloc) +{ + Byte allAreDefined; + size_t i; + RINOK(SzReadByte(sd, &allAreDefined)); + if (allAreDefined == 0) + return SzReadBoolVector(sd, numItems, v, alloc); + MY_ALLOC(Byte, *v, numItems, alloc); + for (i = 0; i < numItems; i++) + (*v)[i] = 1; + return SZ_OK; +} + +static SRes SzReadHashDigests( + CSzData *sd, + size_t numItems, + Byte **digestsDefined, + UInt32 **digests, + ISzAlloc *alloc) +{ + size_t i; + RINOK(SzReadBoolVector2(sd, numItems, digestsDefined, alloc)); + MY_ALLOC(UInt32, *digests, numItems, alloc); + for (i = 0; i < numItems; i++) + if ((*digestsDefined)[i]) + { + RINOK(SzReadUInt32(sd, (*digests) + i)); + } + return SZ_OK; +} + +static SRes SzReadPackInfo( + CSzData *sd, + UInt64 *dataOffset, + UInt32 *numPackStreams, + UInt64 **packSizes, + Byte **packCRCsDefined, + UInt32 **packCRCs, + ISzAlloc *alloc) +{ + UInt32 i; + RINOK(SzReadNumber(sd, dataOffset)); + RINOK(SzReadNumber32(sd, numPackStreams)); + + RINOK(SzWaitAttribute(sd, k7zIdSize)); + + MY_ALLOC(UInt64, *packSizes, (size_t)*numPackStreams, alloc); + + for (i = 0; i < *numPackStreams; i++) + { + RINOK(SzReadNumber(sd, (*packSizes) + i)); + } + + for (;;) + { + UInt64 type; + RINOK(SzReadID(sd, &type)); + if (type == k7zIdEnd) + break; + if (type == k7zIdCRC) + { + RINOK(SzReadHashDigests(sd, (size_t)*numPackStreams, packCRCsDefined, packCRCs, alloc)); + continue; + } + RINOK(SzSkeepData(sd)); + } + if (*packCRCsDefined == 0) + { + MY_ALLOC(Byte, *packCRCsDefined, (size_t)*numPackStreams, alloc); + MY_ALLOC(UInt32, *packCRCs, (size_t)*numPackStreams, alloc); + for (i = 0; i < *numPackStreams; i++) + { + (*packCRCsDefined)[i] = 0; + (*packCRCs)[i] = 0; + } + } + return SZ_OK; +} + +static SRes SzReadSwitch(CSzData *sd) +{ + Byte external; + RINOK(SzReadByte(sd, &external)); + return (external == 0) ? SZ_OK: SZ_ERROR_UNSUPPORTED; +} + +static SRes SzGetNextFolderItem(CSzData *sd, CSzFolder *folder, ISzAlloc *alloc) +{ + UInt32 numCoders, numBindPairs, numPackStreams, i; + UInt32 numInStreams = 0, numOutStreams = 0; + + RINOK(SzReadNumber32(sd, &numCoders)); + if (numCoders > NUM_FOLDER_CODERS_MAX) + return SZ_ERROR_UNSUPPORTED; + folder->NumCoders = numCoders; + + MY_ALLOC(CSzCoderInfo, folder->Coders, (size_t)numCoders, alloc); + + for (i = 0; i < numCoders; i++) + SzCoderInfo_Init(folder->Coders + i); + + for (i = 0; i < numCoders; i++) + { + Byte mainByte; + CSzCoderInfo *coder = folder->Coders + i; + { + unsigned idSize, j; + Byte longID[15]; + RINOK(SzReadByte(sd, &mainByte)); + idSize = (unsigned)(mainByte & 0xF); + RINOK(SzReadBytes(sd, longID, idSize)); + if (idSize > sizeof(coder->MethodID)) + return SZ_ERROR_UNSUPPORTED; + coder->MethodID = 0; + for (j = 0; j < idSize; j++) + coder->MethodID |= (UInt64)longID[idSize - 1 - j] << (8 * j); + + if ((mainByte & 0x10) != 0) + { + RINOK(SzReadNumber32(sd, &coder->NumInStreams)); + RINOK(SzReadNumber32(sd, &coder->NumOutStreams)); + if (coder->NumInStreams > NUM_CODER_STREAMS_MAX || + coder->NumOutStreams > NUM_CODER_STREAMS_MAX) + return SZ_ERROR_UNSUPPORTED; + } + else + { + coder->NumInStreams = 1; + coder->NumOutStreams = 1; + } + if ((mainByte & 0x20) != 0) + { + UInt64 propertiesSize = 0; + RINOK(SzReadNumber(sd, &propertiesSize)); + if (!Buf_Create(&coder->Props, (size_t)propertiesSize, alloc)) + return SZ_ERROR_MEM; + RINOK(SzReadBytes(sd, coder->Props.data, (size_t)propertiesSize)); + } + } + while ((mainByte & 0x80) != 0) + { + RINOK(SzReadByte(sd, &mainByte)); + RINOK(SzSkeepDataSize(sd, (mainByte & 0xF))); + if ((mainByte & 0x10) != 0) + { + UInt32 n; + RINOK(SzReadNumber32(sd, &n)); + RINOK(SzReadNumber32(sd, &n)); + } + if ((mainByte & 0x20) != 0) + { + UInt64 propertiesSize = 0; + RINOK(SzReadNumber(sd, &propertiesSize)); + RINOK(SzSkeepDataSize(sd, propertiesSize)); + } + } + numInStreams += coder->NumInStreams; + numOutStreams += coder->NumOutStreams; + } + + if (numOutStreams == 0) + return SZ_ERROR_UNSUPPORTED; + + folder->NumBindPairs = numBindPairs = numOutStreams - 1; + MY_ALLOC(CBindPair, folder->BindPairs, (size_t)numBindPairs, alloc); + + for (i = 0; i < numBindPairs; i++) + { + CBindPair *bp = folder->BindPairs + i; + RINOK(SzReadNumber32(sd, &bp->InIndex)); + RINOK(SzReadNumber32(sd, &bp->OutIndex)); + } + + if (numInStreams < numBindPairs) + return SZ_ERROR_UNSUPPORTED; + + folder->NumPackStreams = numPackStreams = numInStreams - numBindPairs; + MY_ALLOC(UInt32, folder->PackStreams, (size_t)numPackStreams, alloc); + + if (numPackStreams == 1) + { + for (i = 0; i < numInStreams ; i++) + if (SzFolder_FindBindPairForInStream(folder, i) < 0) + break; + if (i == numInStreams) + return SZ_ERROR_UNSUPPORTED; + folder->PackStreams[0] = i; + } + else + for (i = 0; i < numPackStreams; i++) + { + RINOK(SzReadNumber32(sd, folder->PackStreams + i)); + } + return SZ_OK; +} + +static SRes SzReadUnpackInfo( + CSzData *sd, + UInt32 *numFolders, + CSzFolder **folders, /* for alloc */ + ISzAlloc *alloc, + ISzAlloc *allocTemp) +{ + UInt32 i; + RINOK(SzWaitAttribute(sd, k7zIdFolder)); + RINOK(SzReadNumber32(sd, numFolders)); + { + RINOK(SzReadSwitch(sd)); + + MY_ALLOC(CSzFolder, *folders, (size_t)*numFolders, alloc); + + for (i = 0; i < *numFolders; i++) + SzFolder_Init((*folders) + i); + + for (i = 0; i < *numFolders; i++) + { + RINOK(SzGetNextFolderItem(sd, (*folders) + i, alloc)); + } + } + + RINOK(SzWaitAttribute(sd, k7zIdCodersUnpackSize)); + + for (i = 0; i < *numFolders; i++) + { + UInt32 j; + CSzFolder *folder = (*folders) + i; + UInt32 numOutStreams = SzFolder_GetNumOutStreams(folder); + + MY_ALLOC(UInt64, folder->UnpackSizes, (size_t)numOutStreams, alloc); + + for (j = 0; j < numOutStreams; j++) + { + RINOK(SzReadNumber(sd, folder->UnpackSizes + j)); + } + } + + for (;;) + { + UInt64 type; + RINOK(SzReadID(sd, &type)); + if (type == k7zIdEnd) + return SZ_OK; + if (type == k7zIdCRC) + { + SRes res; + Byte *crcsDefined = 0; + UInt32 *crcs = 0; + res = SzReadHashDigests(sd, *numFolders, &crcsDefined, &crcs, allocTemp); + if (res == SZ_OK) + { + for (i = 0; i < *numFolders; i++) + { + CSzFolder *folder = (*folders) + i; + folder->UnpackCRCDefined = crcsDefined[i]; + folder->UnpackCRC = crcs[i]; + } + } + IAlloc_Free(allocTemp, crcs); + IAlloc_Free(allocTemp, crcsDefined); + RINOK(res); + continue; + } + RINOK(SzSkeepData(sd)); + } +} + +static SRes SzReadSubStreamsInfo( + CSzData *sd, + UInt32 numFolders, + CSzFolder *folders, + UInt32 *numUnpackStreams, + UInt64 **unpackSizes, + Byte **digestsDefined, + UInt32 **digests, + ISzAlloc *allocTemp) +{ + UInt64 type = 0; + UInt32 i; + UInt32 si = 0; + UInt32 numDigests = 0; + + for (i = 0; i < numFolders; i++) + folders[i].NumUnpackStreams = 1; + *numUnpackStreams = numFolders; + + for (;;) + { + RINOK(SzReadID(sd, &type)); + if (type == k7zIdNumUnpackStream) + { + *numUnpackStreams = 0; + for (i = 0; i < numFolders; i++) + { + UInt32 numStreams; + RINOK(SzReadNumber32(sd, &numStreams)); + folders[i].NumUnpackStreams = numStreams; + *numUnpackStreams += numStreams; + } + continue; + } + if (type == k7zIdCRC || type == k7zIdSize) + break; + if (type == k7zIdEnd) + break; + RINOK(SzSkeepData(sd)); + } + + if (*numUnpackStreams == 0) + { + *unpackSizes = 0; + *digestsDefined = 0; + *digests = 0; + } + else + { + *unpackSizes = (UInt64 *)IAlloc_Alloc(allocTemp, (size_t)*numUnpackStreams * sizeof(UInt64)); + RINOM(*unpackSizes); + *digestsDefined = (Byte *)IAlloc_Alloc(allocTemp, (size_t)*numUnpackStreams * sizeof(Byte)); + RINOM(*digestsDefined); + *digests = (UInt32 *)IAlloc_Alloc(allocTemp, (size_t)*numUnpackStreams * sizeof(UInt32)); + RINOM(*digests); + } + + for (i = 0; i < numFolders; i++) + { + /* + v3.13 incorrectly worked with empty folders + v4.07: we check that folder is empty + */ + UInt64 sum = 0; + UInt32 j; + UInt32 numSubstreams = folders[i].NumUnpackStreams; + if (numSubstreams == 0) + continue; + if (type == k7zIdSize) + for (j = 1; j < numSubstreams; j++) + { + UInt64 size; + RINOK(SzReadNumber(sd, &size)); + (*unpackSizes)[si++] = size; + sum += size; + } + (*unpackSizes)[si++] = SzFolder_GetUnpackSize(folders + i) - sum; + } + if (type == k7zIdSize) + { + RINOK(SzReadID(sd, &type)); + } + + for (i = 0; i < *numUnpackStreams; i++) + { + (*digestsDefined)[i] = 0; + (*digests)[i] = 0; + } + + + for (i = 0; i < numFolders; i++) + { + UInt32 numSubstreams = folders[i].NumUnpackStreams; + if (numSubstreams != 1 || !folders[i].UnpackCRCDefined) + numDigests += numSubstreams; + } + + + si = 0; + for (;;) + { + if (type == k7zIdCRC) + { + int digestIndex = 0; + Byte *digestsDefined2 = 0; + UInt32 *digests2 = 0; + SRes res = SzReadHashDigests(sd, numDigests, &digestsDefined2, &digests2, allocTemp); + if (res == SZ_OK) + { + for (i = 0; i < numFolders; i++) + { + CSzFolder *folder = folders + i; + UInt32 numSubstreams = folder->NumUnpackStreams; + if (numSubstreams == 1 && folder->UnpackCRCDefined) + { + (*digestsDefined)[si] = 1; + (*digests)[si] = folder->UnpackCRC; + si++; + } + else + { + UInt32 j; + for (j = 0; j < numSubstreams; j++, digestIndex++) + { + (*digestsDefined)[si] = digestsDefined2[digestIndex]; + (*digests)[si] = digests2[digestIndex]; + si++; + } + } + } + } + IAlloc_Free(allocTemp, digestsDefined2); + IAlloc_Free(allocTemp, digests2); + RINOK(res); + } + else if (type == k7zIdEnd) + return SZ_OK; + else + { + RINOK(SzSkeepData(sd)); + } + RINOK(SzReadID(sd, &type)); + } +} + + +static SRes SzReadStreamsInfo( + CSzData *sd, + UInt64 *dataOffset, + CSzAr *p, + UInt32 *numUnpackStreams, + UInt64 **unpackSizes, /* allocTemp */ + Byte **digestsDefined, /* allocTemp */ + UInt32 **digests, /* allocTemp */ + ISzAlloc *alloc, + ISzAlloc *allocTemp) +{ + for (;;) + { + UInt64 type; + RINOK(SzReadID(sd, &type)); + if ((UInt64)(int)type != type) + return SZ_ERROR_UNSUPPORTED; + switch((int)type) + { + case k7zIdEnd: + return SZ_OK; + case k7zIdPackInfo: + { + RINOK(SzReadPackInfo(sd, dataOffset, &p->NumPackStreams, + &p->PackSizes, &p->PackCRCsDefined, &p->PackCRCs, alloc)); + break; + } + case k7zIdUnpackInfo: + { + RINOK(SzReadUnpackInfo(sd, &p->NumFolders, &p->Folders, alloc, allocTemp)); + break; + } + case k7zIdSubStreamsInfo: + { + RINOK(SzReadSubStreamsInfo(sd, p->NumFolders, p->Folders, + numUnpackStreams, unpackSizes, digestsDefined, digests, allocTemp)); + break; + } + default: + return SZ_ERROR_UNSUPPORTED; + } + } +} + +Byte kUtf8Limits[5] = { 0xC0, 0xE0, 0xF0, 0xF8, 0xFC }; + +static SRes SzReadFileNames(CSzData *sd, UInt32 numFiles, CSzFileItem *files, ISzAlloc *alloc) +{ + UInt32 i; + for (i = 0; i < numFiles; i++) + { + UInt32 len = 0; + UInt32 pos = 0; + CSzFileItem *file = files + i; + while (pos + 2 <= sd->Size) + { + int numAdds; + UInt32 value = (UInt32)(sd->Data[pos] | (((UInt32)sd->Data[pos + 1]) << 8)); + pos += 2; + len++; + if (value == 0) + break; + if (value < 0x80) + continue; + if (value >= 0xD800 && value < 0xE000) + { + UInt32 c2; + if (value >= 0xDC00) + return SZ_ERROR_ARCHIVE; + if (pos + 2 > sd->Size) + return SZ_ERROR_ARCHIVE; + c2 = (UInt32)(sd->Data[pos] | (((UInt32)sd->Data[pos + 1]) << 8)); + pos += 2; + if (c2 < 0xDC00 || c2 >= 0xE000) + return SZ_ERROR_ARCHIVE; + value = ((value - 0xD800) << 10) | (c2 - 0xDC00); + } + for (numAdds = 1; numAdds < 5; numAdds++) + if (value < (((UInt32)1) << (numAdds * 5 + 6))) + break; + len += numAdds; + } + + MY_ALLOC(char, file->Name, (size_t)len, alloc); + + len = 0; + while (2 <= sd->Size) + { + int numAdds; + UInt32 value = (UInt32)(sd->Data[0] | (((UInt32)sd->Data[1]) << 8)); + SzSkeepDataSize(sd, 2); + if (value < 0x80) + { + file->Name[len++] = (char)value; + if (value == 0) + break; + continue; + } + if (value >= 0xD800 && value < 0xE000) + { + UInt32 c2 = (UInt32)(sd->Data[0] | (((UInt32)sd->Data[1]) << 8)); + SzSkeepDataSize(sd, 2); + value = ((value - 0xD800) << 10) | (c2 - 0xDC00); + } + for (numAdds = 1; numAdds < 5; numAdds++) + if (value < (((UInt32)1) << (numAdds * 5 + 6))) + break; + file->Name[len++] = (char)(kUtf8Limits[numAdds - 1] + (value >> (6 * numAdds))); + do + { + numAdds--; + file->Name[len++] = (char)(0x80 + ((value >> (6 * numAdds)) & 0x3F)); + } + while (numAdds > 0); + + len += numAdds; + } + } + return SZ_OK; +} + +static SRes SzReadHeader2( + CSzArEx *p, /* allocMain */ + CSzData *sd, + UInt64 **unpackSizes, /* allocTemp */ + Byte **digestsDefined, /* allocTemp */ + UInt32 **digests, /* allocTemp */ + Byte **emptyStreamVector, /* allocTemp */ + Byte **emptyFileVector, /* allocTemp */ + Byte **lwtVector, /* allocTemp */ + ISzAlloc *allocMain, + ISzAlloc *allocTemp) +{ + UInt64 htype; + UInt32 numUnpackStreams = 0; + UInt32 numFiles = 0; + CSzFileItem *files = 0; + UInt32 numEmptyStreams = 0; + UInt32 i; + + RINOK(SzReadID(sd, &htype)); + + if (htype == k7zIdArchiveProperties) + { + RINOK(SzReadArchiveProperties(sd)); + RINOK(SzReadID(sd, &htype)); + } + + + if (htype == k7zIdMainStreamsInfo) + { + RINOK(SzReadStreamsInfo(sd, + &p->dataPos, + &p->db, + &numUnpackStreams, + unpackSizes, + digestsDefined, + digests, allocMain, allocTemp)); + p->dataPos += p->startPosAfterHeader; + RINOK(SzReadID(sd, &htype)); + } + + if (htype == k7zIdEnd) + return SZ_OK; + if (htype != k7zIdFilesInfo) + return SZ_ERROR_ARCHIVE; + + RINOK(SzReadNumber32(sd, &numFiles)); + p->db.NumFiles = numFiles; + + MY_ALLOC(CSzFileItem, files, (size_t)numFiles, allocMain); + + p->db.Files = files; + for (i = 0; i < numFiles; i++) + SzFile_Init(files + i); + + for (;;) + { + UInt64 type; + UInt64 size; + RINOK(SzReadID(sd, &type)); + if (type == k7zIdEnd) + break; + RINOK(SzReadNumber(sd, &size)); + + if ((UInt64)(int)type != type) + { + RINOK(SzSkeepDataSize(sd, size)); + } + else + switch((int)type) + { + case k7zIdName: + { + RINOK(SzReadSwitch(sd)); + RINOK(SzReadFileNames(sd, numFiles, files, allocMain)) + break; + } + case k7zIdEmptyStream: + { + RINOK(SzReadBoolVector(sd, numFiles, emptyStreamVector, allocTemp)); + numEmptyStreams = 0; + for (i = 0; i < numFiles; i++) + if ((*emptyStreamVector)[i]) + numEmptyStreams++; + break; + } + case k7zIdEmptyFile: + { + RINOK(SzReadBoolVector(sd, numEmptyStreams, emptyFileVector, allocTemp)); + break; + } + case k7zIdMTime: + { + RINOK(SzReadBoolVector2(sd, numFiles, lwtVector, allocTemp)); + RINOK(SzReadSwitch(sd)); + for (i = 0; i < numFiles; i++) + { + CSzFileItem *f = &files[i]; + Byte defined = (*lwtVector)[i]; + f->MTimeDefined = defined; + f->MTime.Low = f->MTime.High = 0; + if (defined) + { + RINOK(SzReadUInt32(sd, &f->MTime.Low)); + RINOK(SzReadUInt32(sd, &f->MTime.High)); + } + } + break; + } + default: + { + RINOK(SzSkeepDataSize(sd, size)); + } + } + } + + { + UInt32 emptyFileIndex = 0; + UInt32 sizeIndex = 0; + for (i = 0; i < numFiles; i++) + { + CSzFileItem *file = files + i; + file->IsAnti = 0; + if (*emptyStreamVector == 0) + file->HasStream = 1; + else + file->HasStream = (Byte)((*emptyStreamVector)[i] ? 0 : 1); + if (file->HasStream) + { + file->IsDir = 0; + file->Size = (*unpackSizes)[sizeIndex]; + file->FileCRC = (*digests)[sizeIndex]; + file->FileCRCDefined = (Byte)(*digestsDefined)[sizeIndex]; + sizeIndex++; + } + else + { + if (*emptyFileVector == 0) + file->IsDir = 1; + else + file->IsDir = (Byte)((*emptyFileVector)[emptyFileIndex] ? 0 : 1); + emptyFileIndex++; + file->Size = 0; + file->FileCRCDefined = 0; + } + } + } + return SzArEx_Fill(p, allocMain); +} + +static SRes SzReadHeader( + CSzArEx *p, + CSzData *sd, + ISzAlloc *allocMain, + ISzAlloc *allocTemp) +{ + UInt64 *unpackSizes = 0; + Byte *digestsDefined = 0; + UInt32 *digests = 0; + Byte *emptyStreamVector = 0; + Byte *emptyFileVector = 0; + Byte *lwtVector = 0; + SRes res = SzReadHeader2(p, sd, + &unpackSizes, &digestsDefined, &digests, + &emptyStreamVector, &emptyFileVector, &lwtVector, + allocMain, allocTemp); + IAlloc_Free(allocTemp, unpackSizes); + IAlloc_Free(allocTemp, digestsDefined); + IAlloc_Free(allocTemp, digests); + IAlloc_Free(allocTemp, emptyStreamVector); + IAlloc_Free(allocTemp, emptyFileVector); + IAlloc_Free(allocTemp, lwtVector); + return res; +} + +static SRes SzReadAndDecodePackedStreams2( + ILookInStream *inStream, + CSzData *sd, + CBuf *outBuffer, + UInt64 baseOffset, + CSzAr *p, + UInt64 **unpackSizes, + Byte **digestsDefined, + UInt32 **digests, + ISzAlloc *allocTemp) +{ + + UInt32 numUnpackStreams = 0; + UInt64 dataStartPos; + CSzFolder *folder; + UInt64 unpackSize; + SRes res; + + RINOK(SzReadStreamsInfo(sd, &dataStartPos, p, + &numUnpackStreams, unpackSizes, digestsDefined, digests, + allocTemp, allocTemp)); + + dataStartPos += baseOffset; + if (p->NumFolders != 1) + return SZ_ERROR_ARCHIVE; + + folder = p->Folders; + unpackSize = SzFolder_GetUnpackSize(folder); + + RINOK(LookInStream_SeekTo(inStream, dataStartPos)); + + if (!Buf_Create(outBuffer, (size_t)unpackSize, allocTemp)) + return SZ_ERROR_MEM; + + res = SzDecode(p->PackSizes, folder, + inStream, dataStartPos, + outBuffer->data, (size_t)unpackSize, allocTemp); + RINOK(res); + if (folder->UnpackCRCDefined) + if (CrcCalc(outBuffer->data, (size_t)unpackSize) != folder->UnpackCRC) + return SZ_ERROR_CRC; + return SZ_OK; +} + +static SRes SzReadAndDecodePackedStreams( + ILookInStream *inStream, + CSzData *sd, + CBuf *outBuffer, + UInt64 baseOffset, + ISzAlloc *allocTemp) +{ + CSzAr p; + UInt64 *unpackSizes = 0; + Byte *digestsDefined = 0; + UInt32 *digests = 0; + SRes res; + SzAr_Init(&p); + res = SzReadAndDecodePackedStreams2(inStream, sd, outBuffer, baseOffset, + &p, &unpackSizes, &digestsDefined, &digests, + allocTemp); + SzAr_Free(&p, allocTemp); + IAlloc_Free(allocTemp, unpackSizes); + IAlloc_Free(allocTemp, digestsDefined); + IAlloc_Free(allocTemp, digests); + return res; +} + +static SRes SzArEx_Open2( + CSzArEx *p, + ILookInStream *inStream, + ISzAlloc *allocMain, + ISzAlloc *allocTemp) +{ + Byte header[k7zStartHeaderSize]; + UInt64 nextHeaderOffset, nextHeaderSize; + size_t nextHeaderSizeT; + UInt32 nextHeaderCRC; + CBuf buffer; + SRes res; + + RINOK(LookInStream_Read2(inStream, header, k7zStartHeaderSize, SZ_ERROR_NO_ARCHIVE)); + + if (!TestSignatureCandidate(header)) + return SZ_ERROR_NO_ARCHIVE; + if (header[6] != k7zMajorVersion) + return SZ_ERROR_UNSUPPORTED; + + nextHeaderOffset = GetUi64(header + 12); + nextHeaderSize = GetUi64(header + 20); + nextHeaderCRC = GetUi32(header + 28); + + p->startPosAfterHeader = k7zStartHeaderSize; + + if (CrcCalc(header + 12, 20) != GetUi32(header + 8)) + return SZ_ERROR_CRC; + + nextHeaderSizeT = (size_t)nextHeaderSize; + if (nextHeaderSizeT != nextHeaderSize) + return SZ_ERROR_MEM; + if (nextHeaderSizeT == 0) + return SZ_OK; + if (nextHeaderOffset > nextHeaderOffset + nextHeaderSize || + nextHeaderOffset > nextHeaderOffset + nextHeaderSize + k7zStartHeaderSize) + return SZ_ERROR_NO_ARCHIVE; + + { + Int64 pos = 0; + RINOK(inStream->Seek(inStream, &pos, SZ_SEEK_END)); + if ((UInt64)pos < nextHeaderOffset || + (UInt64)pos < k7zStartHeaderSize + nextHeaderOffset || + (UInt64)pos < k7zStartHeaderSize + nextHeaderOffset + nextHeaderSize) + return SZ_ERROR_INPUT_EOF; + } + + RINOK(LookInStream_SeekTo(inStream, k7zStartHeaderSize + nextHeaderOffset)); + + if (!Buf_Create(&buffer, nextHeaderSizeT, allocTemp)) + return SZ_ERROR_MEM; + + res = LookInStream_Read(inStream, buffer.data, nextHeaderSizeT); + if (res == SZ_OK) + { + res = SZ_ERROR_ARCHIVE; + if (CrcCalc(buffer.data, nextHeaderSizeT) == nextHeaderCRC) + { + CSzData sd; + UInt64 type; + sd.Data = buffer.data; + sd.Size = buffer.size; + res = SzReadID(&sd, &type); + if (res == SZ_OK) + { + if (type == k7zIdEncodedHeader) + { + CBuf outBuffer; + Buf_Init(&outBuffer); + res = SzReadAndDecodePackedStreams(inStream, &sd, &outBuffer, p->startPosAfterHeader, allocTemp); + if (res != SZ_OK) + Buf_Free(&outBuffer, allocTemp); + else + { + Buf_Free(&buffer, allocTemp); + buffer.data = outBuffer.data; + buffer.size = outBuffer.size; + sd.Data = buffer.data; + sd.Size = buffer.size; + res = SzReadID(&sd, &type); + } + } + } + if (res == SZ_OK) + { + if (type == k7zIdHeader) + res = SzReadHeader(p, &sd, allocMain, allocTemp); + else + res = SZ_ERROR_UNSUPPORTED; + } + } + } + Buf_Free(&buffer, allocTemp); + return res; +} + +SRes SzArEx_Open(CSzArEx *p, ILookInStream *inStream, ISzAlloc *allocMain, ISzAlloc *allocTemp) +{ + SRes res = SzArEx_Open2(p, inStream, allocMain, allocTemp); + if (res != SZ_OK) + SzArEx_Free(p, allocMain); + return res; +} diff --git a/snesreader/7z_C/7zIn.h b/snesreader/7z_C/7zIn.h new file mode 100644 index 00000000..89e0fb85 --- /dev/null +++ b/snesreader/7z_C/7zIn.h @@ -0,0 +1,49 @@ +/* 7zIn.h -- 7z Input functions +2008-11-23 : Igor Pavlov : Public domain */ + +#ifndef __7Z_IN_H +#define __7Z_IN_H + +#include "7zHeader.h" +#include "7zItem.h" + +#ifdef __cplusplus + extern "C" { +#endif + +typedef struct +{ + CSzAr db; + + UInt64 startPosAfterHeader; + UInt64 dataPos; + + UInt32 *FolderStartPackStreamIndex; + UInt64 *PackStreamStartPositions; + UInt32 *FolderStartFileIndex; + UInt32 *FileIndexToFolderIndexMap; +} CSzArEx; + +void SzArEx_Init(CSzArEx *p); +void SzArEx_Free(CSzArEx *p, ISzAlloc *alloc); +UInt64 SzArEx_GetFolderStreamPos(const CSzArEx *p, UInt32 folderIndex, UInt32 indexInFolder); +int SzArEx_GetFolderFullPackSize(const CSzArEx *p, UInt32 folderIndex, UInt64 *resSize); + +/* +Errors: +SZ_ERROR_NO_ARCHIVE +SZ_ERROR_ARCHIVE +SZ_ERROR_UNSUPPORTED +SZ_ERROR_MEM +SZ_ERROR_CRC +SZ_ERROR_INPUT_EOF +SZ_ERROR_FAIL +*/ + +SRes SzArEx_Open(CSzArEx *p, ILookInStream *inStream, ISzAlloc *allocMain, ISzAlloc *allocTemp); + +#ifdef __cplusplus + } +#endif + +#endif diff --git a/snesreader/7z_C/7zItem.c b/snesreader/7z_C/7zItem.c new file mode 100644 index 00000000..4a092614 --- /dev/null +++ b/snesreader/7z_C/7zItem.c @@ -0,0 +1,129 @@ +/* 7zItem.c -- 7z Items +2008-10-04 : Igor Pavlov : Public domain */ + +#include "7zItem.h" + +void SzCoderInfo_Init(CSzCoderInfo *p) +{ + Buf_Init(&p->Props); +} + +void SzCoderInfo_Free(CSzCoderInfo *p, ISzAlloc *alloc) +{ + Buf_Free(&p->Props, alloc); + SzCoderInfo_Init(p); +} + +void SzFolder_Init(CSzFolder *p) +{ + p->Coders = 0; + p->BindPairs = 0; + p->PackStreams = 0; + p->UnpackSizes = 0; + p->NumCoders = 0; + p->NumBindPairs = 0; + p->NumPackStreams = 0; + p->UnpackCRCDefined = 0; + p->UnpackCRC = 0; + p->NumUnpackStreams = 0; +} + +static +void SzFolder_Free(CSzFolder *p, ISzAlloc *alloc) +{ + UInt32 i; + if (p->Coders) + for (i = 0; i < p->NumCoders; i++) + SzCoderInfo_Free(&p->Coders[i], alloc); + IAlloc_Free(alloc, p->Coders); + IAlloc_Free(alloc, p->BindPairs); + IAlloc_Free(alloc, p->PackStreams); + IAlloc_Free(alloc, p->UnpackSizes); + SzFolder_Init(p); +} + +UInt32 SzFolder_GetNumOutStreams(CSzFolder *p) +{ + UInt32 result = 0; + UInt32 i; + for (i = 0; i < p->NumCoders; i++) + result += p->Coders[i].NumOutStreams; + return result; +} + +int SzFolder_FindBindPairForInStream(CSzFolder *p, UInt32 inStreamIndex) +{ + UInt32 i; + for (i = 0; i < p->NumBindPairs; i++) + if (p->BindPairs[i].InIndex == inStreamIndex) + return i; + return -1; +} + + +static +int SzFolder_FindBindPairForOutStream(CSzFolder *p, UInt32 outStreamIndex) +{ + UInt32 i; + for (i = 0; i < p->NumBindPairs; i++) + if (p->BindPairs[i].OutIndex == outStreamIndex) + return i; + return -1; +} + +UInt64 SzFolder_GetUnpackSize(CSzFolder *p) +{ + int i = (int)SzFolder_GetNumOutStreams(p); + if (i == 0) + return 0; + for (i--; i >= 0; i--) + if (SzFolder_FindBindPairForOutStream(p, i) < 0) + return p->UnpackSizes[i]; + /* throw 1; */ + return 0; +} + +void SzFile_Init(CSzFileItem *p) +{ + p->HasStream = 1; + p->IsDir = 0; + p->IsAnti = 0; + p->FileCRCDefined = 0; + p->MTimeDefined = 0; + p->Name = 0; +} + +static void SzFile_Free(CSzFileItem *p, ISzAlloc *alloc) +{ + IAlloc_Free(alloc, p->Name); + SzFile_Init(p); +} + +void SzAr_Init(CSzAr *p) +{ + p->PackSizes = 0; + p->PackCRCsDefined = 0; + p->PackCRCs = 0; + p->Folders = 0; + p->Files = 0; + p->NumPackStreams = 0; + p->NumFolders = 0; + p->NumFiles = 0; +} + +void SzAr_Free(CSzAr *p, ISzAlloc *alloc) +{ + UInt32 i; + if (p->Folders) + for (i = 0; i < p->NumFolders; i++) + SzFolder_Free(&p->Folders[i], alloc); + if (p->Files) + for (i = 0; i < p->NumFiles; i++) + SzFile_Free(&p->Files[i], alloc); + IAlloc_Free(alloc, p->PackSizes); + IAlloc_Free(alloc, p->PackCRCsDefined); + IAlloc_Free(alloc, p->PackCRCs); + IAlloc_Free(alloc, p->Folders); + IAlloc_Free(alloc, p->Files); + SzAr_Init(p); +} diff --git a/snesreader/7z_C/7zItem.h b/snesreader/7z_C/7zItem.h new file mode 100644 index 00000000..7ef24731 --- /dev/null +++ b/snesreader/7z_C/7zItem.h @@ -0,0 +1,83 @@ +/* 7zItem.h -- 7z Items +2008-10-04 : Igor Pavlov : Public domain */ + +#ifndef __7Z_ITEM_H +#define __7Z_ITEM_H + +#include "7zBuf.h" + +typedef struct +{ + UInt32 NumInStreams; + UInt32 NumOutStreams; + UInt64 MethodID; + CBuf Props; +} CSzCoderInfo; + +void SzCoderInfo_Init(CSzCoderInfo *p); +void SzCoderInfo_Free(CSzCoderInfo *p, ISzAlloc *alloc); + +typedef struct +{ + UInt32 InIndex; + UInt32 OutIndex; +} CBindPair; + +typedef struct +{ + CSzCoderInfo *Coders; + CBindPair *BindPairs; + UInt32 *PackStreams; + UInt64 *UnpackSizes; + UInt32 NumCoders; + UInt32 NumBindPairs; + UInt32 NumPackStreams; + int UnpackCRCDefined; + UInt32 UnpackCRC; + + UInt32 NumUnpackStreams; +} CSzFolder; + +void SzFolder_Init(CSzFolder *p); +UInt64 SzFolder_GetUnpackSize(CSzFolder *p); +int SzFolder_FindBindPairForInStream(CSzFolder *p, UInt32 inStreamIndex); +UInt32 SzFolder_GetNumOutStreams(CSzFolder *p); + +typedef struct +{ + UInt32 Low; + UInt32 High; +} CNtfsFileTime; + +typedef struct +{ + CNtfsFileTime MTime; + UInt64 Size; + char *Name; + UInt32 FileCRC; + + Byte HasStream; + Byte IsDir; + Byte IsAnti; + Byte FileCRCDefined; + Byte MTimeDefined; +} CSzFileItem; + +void SzFile_Init(CSzFileItem *p); + +typedef struct +{ + UInt64 *PackSizes; + Byte *PackCRCsDefined; + UInt32 *PackCRCs; + CSzFolder *Folders; + CSzFileItem *Files; + UInt32 NumPackStreams; + UInt32 NumFolders; + UInt32 NumFiles; +} CSzAr; + +void SzAr_Init(CSzAr *p); +void SzAr_Free(CSzAr *p, ISzAlloc *alloc); + +#endif diff --git a/snesreader/7z_C/7zStream.c b/snesreader/7z_C/7zStream.c new file mode 100644 index 00000000..6dc333ef --- /dev/null +++ b/snesreader/7z_C/7zStream.c @@ -0,0 +1,184 @@ +/* 7zStream.c -- 7z Stream functions +2008-11-23 : Igor Pavlov : Public domain */ + +#include + +#include "Types.h" + +#if NEVER_CALLED +SRes SeqInStream_Read2(ISeqInStream *stream, void *buf, size_t size, SRes errorType) +{ + while (size != 0) + { + size_t processed = size; + RINOK(stream->Read(stream, buf, &processed)); + if (processed == 0) + return errorType; + buf = (void *)((Byte *)buf + processed); + size -= processed; + } + return SZ_OK; +} + +SRes SeqInStream_Read(ISeqInStream *stream, void *buf, size_t size) +{ + return SeqInStream_Read2(stream, buf, size, SZ_ERROR_INPUT_EOF); +} + +SRes SeqInStream_ReadByte(ISeqInStream *stream, Byte *buf) +{ + size_t processed = 1; + RINOK(stream->Read(stream, buf, &processed)); + return (processed == 1) ? SZ_OK : SZ_ERROR_INPUT_EOF; +} +#endif + +SRes LookInStream_SeekTo(ILookInStream *stream, UInt64 offset) +{ + Int64 t = offset; + return stream->Seek(stream, &t, SZ_SEEK_SET); +} + +#if NEVER_CALLED +SRes LookInStream_LookRead(ILookInStream *stream, void *buf, size_t *size) +{ + void *lookBuf; + if (*size == 0) + return SZ_OK; + RINOK(stream->Look(stream, &lookBuf, size)); + memcpy(buf, lookBuf, *size); + return stream->Skip(stream, *size); +} +#endif + +SRes LookInStream_Read2(ILookInStream *stream, void *buf, size_t size, SRes errorType) +{ + while (size != 0) + { + size_t processed = size; + RINOK(stream->Read(stream, buf, &processed)); + if (processed == 0) + return errorType; + buf = (void *)((Byte *)buf + processed); + size -= processed; + } + return SZ_OK; +} + +SRes LookInStream_Read(ILookInStream *stream, void *buf, size_t size) +{ + return LookInStream_Read2(stream, buf, size, SZ_ERROR_INPUT_EOF); +} + +static SRes LookToRead_Look_Lookahead(void *pp, void **buf, size_t *size) +{ + SRes res = SZ_OK; +#if !NEVER_CALLED + (void)pp; + (void)buf; + (void)size; +#else + CLookToRead *p = (CLookToRead *)pp; + size_t size2 = p->size - p->pos; + if (size2 == 0 && *size > 0) + { + p->pos = 0; + size2 = LookToRead_BUF_SIZE; + res = p->realStream->Read(p->realStream, p->buf, &size2); + p->size = size2; + } + if (size2 < *size) + *size = size2; + *buf = p->buf + p->pos; +#endif + return res; +} + +static SRes LookToRead_Look_Exact(void *pp, void **buf, size_t *size) +{ + SRes res = SZ_OK; + CLookToRead *p = (CLookToRead *)pp; + size_t size2 = p->size - p->pos; + if (size2 == 0 && *size > 0) + { + p->pos = 0; + if (*size > LookToRead_BUF_SIZE) + *size = LookToRead_BUF_SIZE; + res = p->realStream->Read(p->realStream, p->buf, size); + size2 = p->size = *size; + } + if (size2 < *size) + *size = size2; + *buf = p->buf + p->pos; + return res; +} + +static SRes LookToRead_Skip(void *pp, size_t offset) +{ + CLookToRead *p = (CLookToRead *)pp; + p->pos += offset; + return SZ_OK; +} + +static SRes LookToRead_Read(void *pp, void *buf, size_t *size) +{ + CLookToRead *p = (CLookToRead *)pp; + size_t rem = p->size - p->pos; + if (rem == 0) + return p->realStream->Read(p->realStream, buf, size); + if (rem > *size) + rem = *size; + memcpy(buf, p->buf + p->pos, rem); + p->pos += rem; + *size = rem; + return SZ_OK; +} + +static SRes LookToRead_Seek(void *pp, Int64 *pos, ESzSeek origin) +{ + CLookToRead *p = (CLookToRead *)pp; + p->pos = p->size = 0; + return p->realStream->Seek(p->realStream, pos, origin); +} + +void LookToRead_CreateVTable(CLookToRead *p, int lookahead) +{ +#if !NEVER_CALLED + lookahead = 0; +#endif + p->s.Look = lookahead ? + LookToRead_Look_Lookahead : + LookToRead_Look_Exact; + p->s.Skip = LookToRead_Skip; + p->s.Read = LookToRead_Read; + p->s.Seek = LookToRead_Seek; +} + +void LookToRead_Init(CLookToRead *p) +{ + p->pos = p->size = 0; +} + +#if NEVER_CALLED +static SRes SecToLook_Read(void *pp, void *buf, size_t *size) +{ + CSecToLook *p = (CSecToLook *)pp; + return LookInStream_LookRead(p->realStream, buf, size); +} + +void SecToLook_CreateVTable(CSecToLook *p) +{ + p->s.Read = SecToLook_Read; +} + +static SRes SecToRead_Read(void *pp, void *buf, size_t *size) +{ + CSecToRead *p = (CSecToRead *)pp; + return p->realStream->Read(p->realStream, buf, size); +} + +void SecToRead_CreateVTable(CSecToRead *p) +{ + p->s.Read = SecToRead_Read; +} +#endif diff --git a/snesreader/7z_C/Bcj2.c b/snesreader/7z_C/Bcj2.c new file mode 100644 index 00000000..bc3dae92 --- /dev/null +++ b/snesreader/7z_C/Bcj2.c @@ -0,0 +1,132 @@ +/* Bcj2.c -- Converter for x86 code (BCJ2) +2008-10-04 : Igor Pavlov : Public domain */ + +#include "Bcj2.h" + +#ifdef _LZMA_PROB32 +#define CProb UInt32 +#else +#define CProb UInt16 +#endif + +#define IsJcc(b0, b1) ((b0) == 0x0F && ((b1) & 0xF0) == 0x80) +#define IsJ(b0, b1) ((b1 & 0xFE) == 0xE8 || IsJcc(b0, b1)) + +#define kNumTopBits 24 +#define kTopValue ((UInt32)1 << kNumTopBits) + +#define kNumBitModelTotalBits 11 +#define kBitModelTotal (1 << kNumBitModelTotalBits) +#define kNumMoveBits 5 + +#define RC_READ_BYTE (*buffer++) +#define RC_TEST { if (buffer == bufferLim) return SZ_ERROR_DATA; } +#define RC_INIT2 code = 0; range = 0xFFFFFFFF; \ + { int i; for (i = 0; i < 5; i++) { RC_TEST; code = (code << 8) | RC_READ_BYTE; }} + +#define NORMALIZE if (range < kTopValue) { RC_TEST; range <<= 8; code = (code << 8) | RC_READ_BYTE; } + +#define IF_BIT_0(p) ttt = *(p); bound = (range >> kNumBitModelTotalBits) * ttt; if (code < bound) +#define UPDATE_0(p) range = bound; *(p) = (CProb)(ttt + ((kBitModelTotal - ttt) >> kNumMoveBits)); NORMALIZE; +#define UPDATE_1(p) range -= bound; code -= bound; *(p) = (CProb)(ttt - (ttt >> kNumMoveBits)); NORMALIZE; + +int Bcj2_Decode( + const Byte *buf0, SizeT size0, + const Byte *buf1, SizeT size1, + const Byte *buf2, SizeT size2, + const Byte *buf3, SizeT size3, + Byte *outBuf, SizeT outSize) +{ + CProb p[256 + 2]; + SizeT inPos = 0, outPos = 0; + + const Byte *buffer, *bufferLim; + UInt32 range, code; + Byte prevByte = 0; + { + unsigned int i; + for (i = 0; i < sizeof(p) / sizeof(p[0]); i++) + p[i] = kBitModelTotal >> 1; + } + buffer = buf3; + bufferLim = buffer + size3; + RC_INIT2 + + if (outSize == 0) + return SZ_OK; + + for (;;) + { + Byte b; + CProb *prob; + UInt32 bound; + UInt32 ttt; + + SizeT limit = size0 - inPos; + if (outSize - outPos < limit) + limit = outSize - outPos; + while (limit != 0) + { + Byte bb = buf0[inPos]; + outBuf[outPos++] = bb; + if (IsJ(prevByte, bb)) + break; + inPos++; + prevByte = bb; + limit--; + } + + if (limit == 0 || outPos == outSize) + break; + + b = buf0[inPos++]; + + if (b == 0xE8) + prob = p + prevByte; + else if (b == 0xE9) + prob = p + 256; + else + prob = p + 257; + + IF_BIT_0(prob) + { + UPDATE_0(prob) + prevByte = b; + } + else + { + UInt32 dest; + const Byte *v; + UPDATE_1(prob) + if (b == 0xE8) + { + v = buf1; + if (size1 < 4) + return SZ_ERROR_DATA; + buf1 += 4; + size1 -= 4; + } + else + { + v = buf2; + if (size2 < 4) + return SZ_ERROR_DATA; + buf2 += 4; + size2 -= 4; + } + dest = (((UInt32)v[0] << 24) | ((UInt32)v[1] << 16) | + ((UInt32)v[2] << 8) | ((UInt32)v[3])) - ((UInt32)outPos + 4); + outBuf[outPos++] = (Byte)dest; + if (outPos == outSize) + break; + outBuf[outPos++] = (Byte)(dest >> 8); + if (outPos == outSize) + break; + outBuf[outPos++] = (Byte)(dest >> 16); + if (outPos == outSize) + break; + outBuf[outPos++] = prevByte = (Byte)(dest >> 24); + } + } + return (outPos == outSize) ? SZ_OK : SZ_ERROR_DATA; +} diff --git a/snesreader/7z_C/Bcj2.h b/snesreader/7z_C/Bcj2.h new file mode 100644 index 00000000..32d450b3 --- /dev/null +++ b/snesreader/7z_C/Bcj2.h @@ -0,0 +1,30 @@ +/* Bcj2.h -- Converter for x86 code (BCJ2) +2008-10-04 : Igor Pavlov : Public domain */ + +#ifndef __BCJ2_H +#define __BCJ2_H + +#include "Types.h" + +/* +Conditions: + outSize <= FullOutputSize, + where FullOutputSize is full size of output stream of x86_2 filter. + +If buf0 overlaps outBuf, there are two required conditions: + 1) (buf0 >= outBuf) + 2) (buf0 + size0 >= outBuf + FullOutputSize). + +Returns: + SZ_OK + SZ_ERROR_DATA - Data error +*/ + +int Bcj2_Decode( + const Byte *buf0, SizeT size0, + const Byte *buf1, SizeT size1, + const Byte *buf2, SizeT size2, + const Byte *buf3, SizeT size3, + Byte *outBuf, SizeT outSize); + +#endif diff --git a/snesreader/7z_C/Bra.h b/snesreader/7z_C/Bra.h new file mode 100644 index 00000000..45e231e8 --- /dev/null +++ b/snesreader/7z_C/Bra.h @@ -0,0 +1,60 @@ +/* Bra.h -- Branch converters for executables +2008-10-04 : Igor Pavlov : Public domain */ + +#ifndef __BRA_H +#define __BRA_H + +#include "Types.h" + +/* +These functions convert relative addresses to absolute addresses +in CALL instructions to increase the compression ratio. + + In: + data - data buffer + size - size of data + ip - current virtual Instruction Pinter (IP) value + state - state variable for x86 converter + encoding - 0 (for decoding), 1 (for encoding) + + Out: + state - state variable for x86 converter + + Returns: + The number of processed bytes. If you call these functions with multiple calls, + you must start next call with first byte after block of processed bytes. + + Type Endian Alignment LookAhead + + x86 little 1 4 + ARMT little 2 2 + ARM little 4 0 + PPC big 4 0 + SPARC big 4 0 + IA64 little 16 0 + + size must be >= Alignment + LookAhead, if it's not last block. + If (size < Alignment + LookAhead), converter returns 0. + + Example: + + UInt32 ip = 0; + for () + { + ; size must be >= Alignment + LookAhead, if it's not last block + SizeT processed = Convert(data, size, ip, 1); + data += processed; + size -= processed; + ip += processed; + } +*/ + +#define x86_Convert_Init(state) { state = 0; } +SizeT x86_Convert(Byte *data, SizeT size, UInt32 ip, UInt32 *state, int encoding); +SizeT ARM_Convert(Byte *data, SizeT size, UInt32 ip, int encoding); +SizeT ARMT_Convert(Byte *data, SizeT size, UInt32 ip, int encoding); +SizeT PPC_Convert(Byte *data, SizeT size, UInt32 ip, int encoding); +SizeT SPARC_Convert(Byte *data, SizeT size, UInt32 ip, int encoding); +SizeT IA64_Convert(Byte *data, SizeT size, UInt32 ip, int encoding); + +#endif diff --git a/snesreader/7z_C/Bra86.c b/snesreader/7z_C/Bra86.c new file mode 100644 index 00000000..1ee0e709 --- /dev/null +++ b/snesreader/7z_C/Bra86.c @@ -0,0 +1,85 @@ +/* Bra86.c -- Converter for x86 code (BCJ) +2008-10-04 : Igor Pavlov : Public domain */ + +#include "Bra.h" + +#define Test86MSByte(b) ((b) == 0 || (b) == 0xFF) + +const Byte kMaskToAllowedStatus[8] = {1, 1, 1, 0, 1, 0, 0, 0}; +const Byte kMaskToBitNumber[8] = {0, 1, 2, 2, 3, 3, 3, 3}; + +SizeT x86_Convert(Byte *data, SizeT size, UInt32 ip, UInt32 *state, int encoding) +{ + SizeT bufferPos = 0, prevPosT; + UInt32 prevMask = *state & 0x7; + if (size < 5) + return 0; + ip += 5; + prevPosT = (SizeT)0 - 1; + + for (;;) + { + Byte *p = data + bufferPos; + Byte *limit = data + size - 4; + for (; p < limit; p++) + if ((*p & 0xFE) == 0xE8) + break; + bufferPos = (SizeT)(p - data); + if (p >= limit) + break; + prevPosT = bufferPos - prevPosT; + if (prevPosT > 3) + prevMask = 0; + else + { + prevMask = (prevMask << ((int)prevPosT - 1)) & 0x7; + if (prevMask != 0) + { + Byte b = p[4 - kMaskToBitNumber[prevMask]]; + if (!kMaskToAllowedStatus[prevMask] || Test86MSByte(b)) + { + prevPosT = bufferPos; + prevMask = ((prevMask << 1) & 0x7) | 1; + bufferPos++; + continue; + } + } + } + prevPosT = bufferPos; + + if (Test86MSByte(p[4])) + { + UInt32 src = ((UInt32)p[4] << 24) | ((UInt32)p[3] << 16) | ((UInt32)p[2] << 8) | ((UInt32)p[1]); + UInt32 dest; + for (;;) + { + Byte b; + int index; + if (encoding) + dest = (ip + (UInt32)bufferPos) + src; + else + dest = src - (ip + (UInt32)bufferPos); + if (prevMask == 0) + break; + index = kMaskToBitNumber[prevMask] * 8; + b = (Byte)(dest >> (24 - index)); + if (!Test86MSByte(b)) + break; + src = dest ^ ((1 << (32 - index)) - 1); + } + p[4] = (Byte)(~(((dest >> 24) & 1) - 1)); + p[3] = (Byte)(dest >> 16); + p[2] = (Byte)(dest >> 8); + p[1] = (Byte)dest; + bufferPos += 5; + } + else + { + prevMask = ((prevMask << 1) & 0x7) | 1; + bufferPos++; + } + } + prevPosT = bufferPos - prevPosT; + *state = ((prevPosT > 3) ? 0 : ((prevMask << ((int)prevPosT - 1)) & 0x7)); + return bufferPos; +} diff --git a/snesreader/7z_C/CpuArch.h b/snesreader/7z_C/CpuArch.h new file mode 100644 index 00000000..7384b0c3 --- /dev/null +++ b/snesreader/7z_C/CpuArch.h @@ -0,0 +1,69 @@ +/* CpuArch.h +2008-08-05 +Igor Pavlov +Public domain */ + +#ifndef __CPUARCH_H +#define __CPUARCH_H + +/* +LITTLE_ENDIAN_UNALIGN means: + 1) CPU is LITTLE_ENDIAN + 2) it's allowed to make unaligned memory accesses +if LITTLE_ENDIAN_UNALIGN is not defined, it means that we don't know +about these properties of platform. +*/ + +#if defined(_M_IX86) || defined(_M_X64) || defined(_M_AMD64) || defined(__i386__) || defined(__x86_64__) +#define LITTLE_ENDIAN_UNALIGN +#endif + +#ifdef LITTLE_ENDIAN_UNALIGN + +#define GetUi16(p) (*(const UInt16 *)(p)) +#define GetUi32(p) (*(const UInt32 *)(p)) +#define GetUi64(p) (*(const UInt64 *)(p)) +#define SetUi32(p, d) *(UInt32 *)(p) = (d); + +#else + +#define GetUi16(p) (((const Byte *)(p))[0] | ((UInt16)((const Byte *)(p))[1] << 8)) + +#define GetUi32(p) ( \ + ((const Byte *)(p))[0] | \ + ((UInt32)((const Byte *)(p))[1] << 8) | \ + ((UInt32)((const Byte *)(p))[2] << 16) | \ + ((UInt32)((const Byte *)(p))[3] << 24)) + +#define GetUi64(p) (GetUi32(p) | ((UInt64)GetUi32(((const Byte *)(p)) + 4) << 32)) + +#define SetUi32(p, d) { UInt32 _x_ = (d); \ + ((Byte *)(p))[0] = (Byte)_x_; \ + ((Byte *)(p))[1] = (Byte)(_x_ >> 8); \ + ((Byte *)(p))[2] = (Byte)(_x_ >> 16); \ + ((Byte *)(p))[3] = (Byte)(_x_ >> 24); } + +#endif + +#if defined(LITTLE_ENDIAN_UNALIGN) && defined(_WIN64) && (_MSC_VER >= 1300) + +#pragma intrinsic(_byteswap_ulong) +#pragma intrinsic(_byteswap_uint64) +#define GetBe32(p) _byteswap_ulong(*(const UInt32 *)(const Byte *)(p)) +#define GetBe64(p) _byteswap_uint64(*(const UInt64 *)(const Byte *)(p)) + +#else + +#define GetBe32(p) ( \ + ((UInt32)((const Byte *)(p))[0] << 24) | \ + ((UInt32)((const Byte *)(p))[1] << 16) | \ + ((UInt32)((const Byte *)(p))[2] << 8) | \ + ((const Byte *)(p))[3] ) + +#define GetBe64(p) (((UInt64)GetBe32(p) << 32) | GetBe32(((const Byte *)(p)) + 4)) + +#endif + +#define GetBe16(p) (((UInt16)((const Byte *)(p))[0] << 8) | ((const Byte *)(p))[1]) + +#endif diff --git a/snesreader/7z_C/LzmaDec.c b/snesreader/7z_C/LzmaDec.c new file mode 100644 index 00000000..fb08e786 --- /dev/null +++ b/snesreader/7z_C/LzmaDec.c @@ -0,0 +1,1010 @@ +/* LzmaDec.c -- LZMA Decoder +2008-11-06 : Igor Pavlov : Public domain */ + +#include "LzmaDec.h" + +#include + +#define kNumTopBits 24 +#define kTopValue ((UInt32)1 << kNumTopBits) + +#define kNumBitModelTotalBits 11 +#define kBitModelTotal (1 << kNumBitModelTotalBits) +#define kNumMoveBits 5 + +#define RC_INIT_SIZE 5 + +#define NORMALIZE if (range < kTopValue) { range <<= 8; code = (code << 8) | (*buf++); } + +#define IF_BIT_0(p) ttt = *(p); NORMALIZE; bound = (range >> kNumBitModelTotalBits) * ttt; if (code < bound) +#define UPDATE_0(p) range = bound; *(p) = (CLzmaProb)(ttt + ((kBitModelTotal - ttt) >> kNumMoveBits)); +#define UPDATE_1(p) range -= bound; code -= bound; *(p) = (CLzmaProb)(ttt - (ttt >> kNumMoveBits)); +#define GET_BIT2(p, i, A0, A1) IF_BIT_0(p) \ + { UPDATE_0(p); i = (i + i); A0; } else \ + { UPDATE_1(p); i = (i + i) + 1; A1; } +#define GET_BIT(p, i) GET_BIT2(p, i, ; , ;) + +#define TREE_GET_BIT(probs, i) { GET_BIT((probs + i), i); } +#define TREE_DECODE(probs, limit, i) \ + { i = 1; do { TREE_GET_BIT(probs, i); } while (i < limit); i -= limit; } + +/* #define _LZMA_SIZE_OPT */ + +#ifdef _LZMA_SIZE_OPT +#define TREE_6_DECODE(probs, i) TREE_DECODE(probs, (1 << 6), i) +#else +#define TREE_6_DECODE(probs, i) \ + { i = 1; \ + TREE_GET_BIT(probs, i); \ + TREE_GET_BIT(probs, i); \ + TREE_GET_BIT(probs, i); \ + TREE_GET_BIT(probs, i); \ + TREE_GET_BIT(probs, i); \ + TREE_GET_BIT(probs, i); \ + i -= 0x40; } +#endif + +#define NORMALIZE_CHECK if (range < kTopValue) { if (buf >= bufLimit) return DUMMY_ERROR; range <<= 8; code = (code << 8) | (*buf++); } + +#define IF_BIT_0_CHECK(p) ttt = *(p); NORMALIZE_CHECK; bound = (range >> kNumBitModelTotalBits) * ttt; if (code < bound) +#define UPDATE_0_CHECK range = bound; +#define UPDATE_1_CHECK range -= bound; code -= bound; +#define GET_BIT2_CHECK(p, i, A0, A1) IF_BIT_0_CHECK(p) \ + { UPDATE_0_CHECK; i = (i + i); A0; } else \ + { UPDATE_1_CHECK; i = (i + i) + 1; A1; } +#define GET_BIT_CHECK(p, i) GET_BIT2_CHECK(p, i, ; , ;) +#define TREE_DECODE_CHECK(probs, limit, i) \ + { i = 1; do { GET_BIT_CHECK(probs + i, i) } while (i < limit); i -= limit; } + + +#define kNumPosBitsMax 4 +#define kNumPosStatesMax (1 << kNumPosBitsMax) + +#define kLenNumLowBits 3 +#define kLenNumLowSymbols (1 << kLenNumLowBits) +#define kLenNumMidBits 3 +#define kLenNumMidSymbols (1 << kLenNumMidBits) +#define kLenNumHighBits 8 +#define kLenNumHighSymbols (1 << kLenNumHighBits) + +#define LenChoice 0 +#define LenChoice2 (LenChoice + 1) +#define LenLow (LenChoice2 + 1) +#define LenMid (LenLow + (kNumPosStatesMax << kLenNumLowBits)) +#define LenHigh (LenMid + (kNumPosStatesMax << kLenNumMidBits)) +#define kNumLenProbs (LenHigh + kLenNumHighSymbols) + + +#define kNumStates 12 +#define kNumLitStates 7 + +#define kStartPosModelIndex 4 +#define kEndPosModelIndex 14 +#define kNumFullDistances (1 << (kEndPosModelIndex >> 1)) + +#define kNumPosSlotBits 6 +#define kNumLenToPosStates 4 + +#define kNumAlignBits 4 +#define kAlignTableSize (1 << kNumAlignBits) + +#define kMatchMinLen 2 +#define kMatchSpecLenStart (kMatchMinLen + kLenNumLowSymbols + kLenNumMidSymbols + kLenNumHighSymbols) + +#define IsMatch 0 +#define IsRep (IsMatch + (kNumStates << kNumPosBitsMax)) +#define IsRepG0 (IsRep + kNumStates) +#define IsRepG1 (IsRepG0 + kNumStates) +#define IsRepG2 (IsRepG1 + kNumStates) +#define IsRep0Long (IsRepG2 + kNumStates) +#define PosSlot (IsRep0Long + (kNumStates << kNumPosBitsMax)) +#define SpecPos (PosSlot + (kNumLenToPosStates << kNumPosSlotBits)) +#define Align (SpecPos + kNumFullDistances - kEndPosModelIndex) +#define LenCoder (Align + kAlignTableSize) +#define RepLenCoder (LenCoder + kNumLenProbs) +#define Literal (RepLenCoder + kNumLenProbs) + +#define LZMA_BASE_SIZE 1846 +#define LZMA_LIT_SIZE 768 + +#define LzmaProps_GetNumProbs(p) ((UInt32)LZMA_BASE_SIZE + (LZMA_LIT_SIZE << ((p)->lc + (p)->lp))) + +#if Literal != LZMA_BASE_SIZE +StopCompilingDueBUG +#endif + +static const Byte kLiteralNextStates[kNumStates * 2] = +{ + 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 4, 5, + 7, 7, 7, 7, 7, 7, 7, 10, 10, 10, 10, 10 +}; + +#define LZMA_DIC_MIN (1 << 12) + +/* First LZMA-symbol is always decoded. +And it decodes new LZMA-symbols while (buf < bufLimit), but "buf" is without last normalization +Out: + Result: + SZ_OK - OK + SZ_ERROR_DATA - Error + p->remainLen: + < kMatchSpecLenStart : normal remain + = kMatchSpecLenStart : finished + = kMatchSpecLenStart + 1 : Flush marker + = kMatchSpecLenStart + 2 : State Init Marker +*/ + +static int MY_FAST_CALL LzmaDec_DecodeReal(CLzmaDec *p, SizeT limit, const Byte *bufLimit) +{ + CLzmaProb *probs = p->probs; + + unsigned state = p->state; + UInt32 rep0 = p->reps[0], rep1 = p->reps[1], rep2 = p->reps[2], rep3 = p->reps[3]; + unsigned pbMask = ((unsigned)1 << (p->prop.pb)) - 1; + unsigned lpMask = ((unsigned)1 << (p->prop.lp)) - 1; + unsigned lc = p->prop.lc; + + Byte *dic = p->dic; + SizeT dicBufSize = p->dicBufSize; + SizeT dicPos = p->dicPos; + + UInt32 processedPos = p->processedPos; + UInt32 checkDicSize = p->checkDicSize; + unsigned len = 0; + + const Byte *buf = p->buf; + UInt32 range = p->range; + UInt32 code = p->code; + + do + { + CLzmaProb *prob; + UInt32 bound; + unsigned ttt; + unsigned posState = processedPos & pbMask; + + prob = probs + IsMatch + (state << kNumPosBitsMax) + posState; + IF_BIT_0(prob) + { + unsigned symbol; + UPDATE_0(prob); + prob = probs + Literal; + if (checkDicSize != 0 || processedPos != 0) + prob += (LZMA_LIT_SIZE * (((processedPos & lpMask) << lc) + + (dic[(dicPos == 0 ? dicBufSize : dicPos) - 1] >> (8 - lc)))); + + if (state < kNumLitStates) + { + symbol = 1; + do { GET_BIT(prob + symbol, symbol) } while (symbol < 0x100); + } + else + { + unsigned matchByte = p->dic[(dicPos - rep0) + ((dicPos < rep0) ? dicBufSize : 0)]; + unsigned offs = 0x100; + symbol = 1; + do + { + unsigned bit; + CLzmaProb *probLit; + matchByte <<= 1; + bit = (matchByte & offs); + probLit = prob + offs + bit + symbol; + GET_BIT2(probLit, symbol, offs &= ~bit, offs &= bit) + } + while (symbol < 0x100); + } + dic[dicPos++] = (Byte)symbol; + processedPos++; + + state = kLiteralNextStates[state]; + /* if (state < 4) state = 0; else if (state < 10) state -= 3; else state -= 6; */ + continue; + } + else + { + UPDATE_1(prob); + prob = probs + IsRep + state; + IF_BIT_0(prob) + { + UPDATE_0(prob); + state += kNumStates; + prob = probs + LenCoder; + } + else + { + UPDATE_1(prob); + if (checkDicSize == 0 && processedPos == 0) + return SZ_ERROR_DATA; + prob = probs + IsRepG0 + state; + IF_BIT_0(prob) + { + UPDATE_0(prob); + prob = probs + IsRep0Long + (state << kNumPosBitsMax) + posState; + IF_BIT_0(prob) + { + UPDATE_0(prob); + dic[dicPos] = dic[(dicPos - rep0) + ((dicPos < rep0) ? dicBufSize : 0)]; + dicPos++; + processedPos++; + state = state < kNumLitStates ? 9 : 11; + continue; + } + UPDATE_1(prob); + } + else + { + UInt32 distance; + UPDATE_1(prob); + prob = probs + IsRepG1 + state; + IF_BIT_0(prob) + { + UPDATE_0(prob); + distance = rep1; + } + else + { + UPDATE_1(prob); + prob = probs + IsRepG2 + state; + IF_BIT_0(prob) + { + UPDATE_0(prob); + distance = rep2; + } + else + { + UPDATE_1(prob); + distance = rep3; + rep3 = rep2; + } + rep2 = rep1; + } + rep1 = rep0; + rep0 = distance; + } + state = state < kNumLitStates ? 8 : 11; + prob = probs + RepLenCoder; + } + { + unsigned limit2, offset; + CLzmaProb *probLen = prob + LenChoice; + IF_BIT_0(probLen) + { + UPDATE_0(probLen); + probLen = prob + LenLow + (posState << kLenNumLowBits); + offset = 0; + limit2 = (1 << kLenNumLowBits); + } + else + { + UPDATE_1(probLen); + probLen = prob + LenChoice2; + IF_BIT_0(probLen) + { + UPDATE_0(probLen); + probLen = prob + LenMid + (posState << kLenNumMidBits); + offset = kLenNumLowSymbols; + limit2 = (1 << kLenNumMidBits); + } + else + { + UPDATE_1(probLen); + probLen = prob + LenHigh; + offset = kLenNumLowSymbols + kLenNumMidSymbols; + limit2 = (1 << kLenNumHighBits); + } + } + TREE_DECODE(probLen, limit2, len); + len += offset; + } + + if (state >= kNumStates) + { + UInt32 distance; + prob = probs + PosSlot + + ((len < kNumLenToPosStates ? len : kNumLenToPosStates - 1) << kNumPosSlotBits); + TREE_6_DECODE(prob, distance); + if (distance >= kStartPosModelIndex) + { + unsigned posSlot = (unsigned)distance; + int numDirectBits = (int)(((distance >> 1) - 1)); + distance = (2 | (distance & 1)); + if (posSlot < kEndPosModelIndex) + { + distance <<= numDirectBits; + prob = probs + SpecPos + distance - posSlot - 1; + { + UInt32 mask = 1; + unsigned i = 1; + do + { + GET_BIT2(prob + i, i, ; , distance |= mask); + mask <<= 1; + } + while (--numDirectBits != 0); + } + } + else + { + numDirectBits -= kNumAlignBits; + do + { + NORMALIZE + range >>= 1; + + { + UInt32 t; + code -= range; + t = (0 - ((UInt32)code >> 31)); /* (UInt32)((Int32)code >> 31) */ + distance = (distance << 1) + (t + 1); + code += range & t; + } + /* + distance <<= 1; + if (code >= range) + { + code -= range; + distance |= 1; + } + */ + } + while (--numDirectBits != 0); + prob = probs + Align; + distance <<= kNumAlignBits; + { + unsigned i = 1; + GET_BIT2(prob + i, i, ; , distance |= 1); + GET_BIT2(prob + i, i, ; , distance |= 2); + GET_BIT2(prob + i, i, ; , distance |= 4); + GET_BIT2(prob + i, i, ; , distance |= 8); + } + if (distance == (UInt32)0xFFFFFFFF) + { + len += kMatchSpecLenStart; + state -= kNumStates; + break; + } + } + } + rep3 = rep2; + rep2 = rep1; + rep1 = rep0; + rep0 = distance + 1; + if (checkDicSize == 0) + { + if (distance >= processedPos) + return SZ_ERROR_DATA; + } + else if (distance >= checkDicSize) + return SZ_ERROR_DATA; + state = (state < kNumStates + kNumLitStates) ? kNumLitStates : kNumLitStates + 3; + /* state = kLiteralNextStates[state]; */ + } + + len += kMatchMinLen; + + if (limit == dicPos) + return SZ_ERROR_DATA; + { + SizeT rem = limit - dicPos; + unsigned curLen = ((rem < len) ? (unsigned)rem : len); + SizeT pos = (dicPos - rep0) + ((dicPos < rep0) ? dicBufSize : 0); + + processedPos += curLen; + + len -= curLen; + if (pos + curLen <= dicBufSize) + { + Byte *dest = dic + dicPos; + ptrdiff_t src = (ptrdiff_t)pos - (ptrdiff_t)dicPos; + const Byte *lim = dest + curLen; + dicPos += curLen; + do + *(dest) = (Byte)*(dest + src); + while (++dest != lim); + } + else + { + do + { + dic[dicPos++] = dic[pos]; + if (++pos == dicBufSize) + pos = 0; + } + while (--curLen != 0); + } + } + } + } + while (dicPos < limit && buf < bufLimit); + NORMALIZE; + p->buf = buf; + p->range = range; + p->code = code; + p->remainLen = len; + p->dicPos = dicPos; + p->processedPos = processedPos; + p->reps[0] = rep0; + p->reps[1] = rep1; + p->reps[2] = rep2; + p->reps[3] = rep3; + p->state = state; + + return SZ_OK; +} + +static void MY_FAST_CALL LzmaDec_WriteRem(CLzmaDec *p, SizeT limit) +{ + if (p->remainLen != 0 && p->remainLen < kMatchSpecLenStart) + { + Byte *dic = p->dic; + SizeT dicPos = p->dicPos; + SizeT dicBufSize = p->dicBufSize; + unsigned len = p->remainLen; + UInt32 rep0 = p->reps[0]; + if (limit - dicPos < len) + len = (unsigned)(limit - dicPos); + + if (p->checkDicSize == 0 && p->prop.dicSize - p->processedPos <= len) + p->checkDicSize = p->prop.dicSize; + + p->processedPos += len; + p->remainLen -= len; + while (len-- != 0) + { + dic[dicPos] = dic[(dicPos - rep0) + ((dicPos < rep0) ? dicBufSize : 0)]; + dicPos++; + } + p->dicPos = dicPos; + } +} + +static int MY_FAST_CALL LzmaDec_DecodeReal2(CLzmaDec *p, SizeT limit, const Byte *bufLimit) +{ + do + { + SizeT limit2 = limit; + if (p->checkDicSize == 0) + { + UInt32 rem = p->prop.dicSize - p->processedPos; + if (limit - p->dicPos > rem) + limit2 = p->dicPos + rem; + } + RINOK(LzmaDec_DecodeReal(p, limit2, bufLimit)); + if (p->processedPos >= p->prop.dicSize) + p->checkDicSize = p->prop.dicSize; + LzmaDec_WriteRem(p, limit); + } + while (p->dicPos < limit && p->buf < bufLimit && p->remainLen < kMatchSpecLenStart); + + if (p->remainLen > kMatchSpecLenStart) + { + p->remainLen = kMatchSpecLenStart; + } + return 0; +} + +typedef enum +{ + DUMMY_ERROR, /* unexpected end of input stream */ + DUMMY_LIT, + DUMMY_MATCH, + DUMMY_REP +} ELzmaDummy; + +static ELzmaDummy LzmaDec_TryDummy(const CLzmaDec *p, const Byte *buf, SizeT inSize) +{ + UInt32 range = p->range; + UInt32 code = p->code; + const Byte *bufLimit = buf + inSize; + CLzmaProb *probs = p->probs; + unsigned state = p->state; + ELzmaDummy res; + + { + CLzmaProb *prob; + UInt32 bound; + unsigned ttt; + unsigned posState = (p->processedPos) & ((1 << p->prop.pb) - 1); + + prob = probs + IsMatch + (state << kNumPosBitsMax) + posState; + IF_BIT_0_CHECK(prob) + { + UPDATE_0_CHECK + + /* if (bufLimit - buf >= 7) return DUMMY_LIT; */ + + prob = probs + Literal; + if (p->checkDicSize != 0 || p->processedPos != 0) + prob += (LZMA_LIT_SIZE * + ((((p->processedPos) & ((1 << (p->prop.lp)) - 1)) << p->prop.lc) + + (p->dic[(p->dicPos == 0 ? p->dicBufSize : p->dicPos) - 1] >> (8 - p->prop.lc)))); + + if (state < kNumLitStates) + { + unsigned symbol = 1; + do { GET_BIT_CHECK(prob + symbol, symbol) } while (symbol < 0x100); + } + else + { + unsigned matchByte = p->dic[p->dicPos - p->reps[0] + + ((p->dicPos < p->reps[0]) ? p->dicBufSize : 0)]; + unsigned offs = 0x100; + unsigned symbol = 1; + do + { + unsigned bit; + CLzmaProb *probLit; + matchByte <<= 1; + bit = (matchByte & offs); + probLit = prob + offs + bit + symbol; + GET_BIT2_CHECK(probLit, symbol, offs &= ~bit, offs &= bit) + } + while (symbol < 0x100); + } + res = DUMMY_LIT; + } + else + { + unsigned len; + UPDATE_1_CHECK; + + prob = probs + IsRep + state; + IF_BIT_0_CHECK(prob) + { + UPDATE_0_CHECK; + state = 0; + prob = probs + LenCoder; + res = DUMMY_MATCH; + } + else + { + UPDATE_1_CHECK; + res = DUMMY_REP; + prob = probs + IsRepG0 + state; + IF_BIT_0_CHECK(prob) + { + UPDATE_0_CHECK; + prob = probs + IsRep0Long + (state << kNumPosBitsMax) + posState; + IF_BIT_0_CHECK(prob) + { + UPDATE_0_CHECK; + NORMALIZE_CHECK; + return DUMMY_REP; + } + else + { + UPDATE_1_CHECK; + } + } + else + { + UPDATE_1_CHECK; + prob = probs + IsRepG1 + state; + IF_BIT_0_CHECK(prob) + { + UPDATE_0_CHECK; + } + else + { + UPDATE_1_CHECK; + prob = probs + IsRepG2 + state; + IF_BIT_0_CHECK(prob) + { + UPDATE_0_CHECK; + } + else + { + UPDATE_1_CHECK; + } + } + } + state = kNumStates; + prob = probs + RepLenCoder; + } + { + unsigned limit, offset; + CLzmaProb *probLen = prob + LenChoice; + IF_BIT_0_CHECK(probLen) + { + UPDATE_0_CHECK; + probLen = prob + LenLow + (posState << kLenNumLowBits); + offset = 0; + limit = 1 << kLenNumLowBits; + } + else + { + UPDATE_1_CHECK; + probLen = prob + LenChoice2; + IF_BIT_0_CHECK(probLen) + { + UPDATE_0_CHECK; + probLen = prob + LenMid + (posState << kLenNumMidBits); + offset = kLenNumLowSymbols; + limit = 1 << kLenNumMidBits; + } + else + { + UPDATE_1_CHECK; + probLen = prob + LenHigh; + offset = kLenNumLowSymbols + kLenNumMidSymbols; + limit = 1 << kLenNumHighBits; + } + } + TREE_DECODE_CHECK(probLen, limit, len); + len += offset; + } + + if (state < 4) + { + unsigned posSlot; + prob = probs + PosSlot + + ((len < kNumLenToPosStates ? len : kNumLenToPosStates - 1) << + kNumPosSlotBits); + TREE_DECODE_CHECK(prob, 1 << kNumPosSlotBits, posSlot); + if (posSlot >= kStartPosModelIndex) + { + int numDirectBits = ((posSlot >> 1) - 1); + + /* if (bufLimit - buf >= 8) return DUMMY_MATCH; */ + + if (posSlot < kEndPosModelIndex) + { + prob = probs + SpecPos + ((2 | (posSlot & 1)) << numDirectBits) - posSlot - 1; + } + else + { + numDirectBits -= kNumAlignBits; + do + { + NORMALIZE_CHECK + range >>= 1; + code -= range & (((code - range) >> 31) - 1); + /* if (code >= range) code -= range; */ + } + while (--numDirectBits != 0); + prob = probs + Align; + numDirectBits = kNumAlignBits; + } + { + unsigned i = 1; + do + { + GET_BIT_CHECK(prob + i, i); + } + while (--numDirectBits != 0); + } + } + } + } + } + NORMALIZE_CHECK; + return res; +} + + +static void LzmaDec_InitRc(CLzmaDec *p, const Byte *data) +{ + p->code = ((UInt32)data[1] << 24) | ((UInt32)data[2] << 16) | ((UInt32)data[3] << 8) | ((UInt32)data[4]); + p->range = 0xFFFFFFFF; + p->needFlush = 0; +} + +static +void LzmaDec_InitDicAndState(CLzmaDec *p, Bool initDic, Bool initState) +{ + p->needFlush = 1; + p->remainLen = 0; + p->tempBufSize = 0; + + if (initDic) + { + p->processedPos = 0; + p->checkDicSize = 0; + p->needInitState = 1; + } + if (initState) + p->needInitState = 1; +} + +void LzmaDec_Init(CLzmaDec *p) +{ + p->dicPos = 0; + LzmaDec_InitDicAndState(p, True, True); +} + +static void LzmaDec_InitStateReal(CLzmaDec *p) +{ + UInt32 numProbs = Literal + ((UInt32)LZMA_LIT_SIZE << (p->prop.lc + p->prop.lp)); + UInt32 i; + CLzmaProb *probs = p->probs; + for (i = 0; i < numProbs; i++) + probs[i] = kBitModelTotal >> 1; + p->reps[0] = p->reps[1] = p->reps[2] = p->reps[3] = 1; + p->state = 0; + p->needInitState = 0; +} + +SRes LzmaDec_DecodeToDic(CLzmaDec *p, SizeT dicLimit, const Byte *src, SizeT *srcLen, + ELzmaFinishMode finishMode, ELzmaStatus *status) +{ + SizeT inSize = *srcLen; + (*srcLen) = 0; + LzmaDec_WriteRem(p, dicLimit); + + *status = LZMA_STATUS_NOT_SPECIFIED; + + while (p->remainLen != kMatchSpecLenStart) + { + int checkEndMarkNow; + + if (p->needFlush != 0) + { + for (; inSize > 0 && p->tempBufSize < RC_INIT_SIZE; (*srcLen)++, inSize--) + p->tempBuf[p->tempBufSize++] = *src++; + if (p->tempBufSize < RC_INIT_SIZE) + { + *status = LZMA_STATUS_NEEDS_MORE_INPUT; + return SZ_OK; + } + if (p->tempBuf[0] != 0) + return SZ_ERROR_DATA; + + LzmaDec_InitRc(p, p->tempBuf); + p->tempBufSize = 0; + } + + checkEndMarkNow = 0; + if (p->dicPos >= dicLimit) + { + if (p->remainLen == 0 && p->code == 0) + { + *status = LZMA_STATUS_MAYBE_FINISHED_WITHOUT_MARK; + return SZ_OK; + } + if (finishMode == LZMA_FINISH_ANY) + { + *status = LZMA_STATUS_NOT_FINISHED; + return SZ_OK; + } + if (p->remainLen != 0) + { + *status = LZMA_STATUS_NOT_FINISHED; + return SZ_ERROR_DATA; + } + checkEndMarkNow = 1; + } + + if (p->needInitState) + LzmaDec_InitStateReal(p); + + if (p->tempBufSize == 0) + { + SizeT processed; + const Byte *bufLimit; + if (inSize < LZMA_REQUIRED_INPUT_MAX || checkEndMarkNow) + { + int dummyRes = LzmaDec_TryDummy(p, src, inSize); + if (dummyRes == DUMMY_ERROR) + { + memcpy(p->tempBuf, src, inSize); + p->tempBufSize = (unsigned)inSize; + (*srcLen) += inSize; + *status = LZMA_STATUS_NEEDS_MORE_INPUT; + return SZ_OK; + } + if (checkEndMarkNow && dummyRes != DUMMY_MATCH) + { + *status = LZMA_STATUS_NOT_FINISHED; + return SZ_ERROR_DATA; + } + bufLimit = src; + } + else + bufLimit = src + inSize - LZMA_REQUIRED_INPUT_MAX; + p->buf = src; + if (LzmaDec_DecodeReal2(p, dicLimit, bufLimit) != 0) + return SZ_ERROR_DATA; + processed = (SizeT)(p->buf - src); + (*srcLen) += processed; + src += processed; + inSize -= processed; + } + else + { + unsigned rem = p->tempBufSize, lookAhead = 0; + while (rem < LZMA_REQUIRED_INPUT_MAX && lookAhead < inSize) + p->tempBuf[rem++] = src[lookAhead++]; + p->tempBufSize = rem; + if (rem < LZMA_REQUIRED_INPUT_MAX || checkEndMarkNow) + { + int dummyRes = LzmaDec_TryDummy(p, p->tempBuf, rem); + if (dummyRes == DUMMY_ERROR) + { + (*srcLen) += lookAhead; + *status = LZMA_STATUS_NEEDS_MORE_INPUT; + return SZ_OK; + } + if (checkEndMarkNow && dummyRes != DUMMY_MATCH) + { + *status = LZMA_STATUS_NOT_FINISHED; + return SZ_ERROR_DATA; + } + } + p->buf = p->tempBuf; + if (LzmaDec_DecodeReal2(p, dicLimit, p->buf) != 0) + return SZ_ERROR_DATA; + lookAhead -= (rem - (unsigned)(p->buf - p->tempBuf)); + (*srcLen) += lookAhead; + src += lookAhead; + inSize -= lookAhead; + p->tempBufSize = 0; + } + } + if (p->code == 0) + *status = LZMA_STATUS_FINISHED_WITH_MARK; + return (p->code == 0) ? SZ_OK : SZ_ERROR_DATA; +} + +#if NEVER_CALLED +SRes LzmaDec_DecodeToBuf(CLzmaDec *p, Byte *dest, SizeT *destLen, const Byte *src, SizeT *srcLen, ELzmaFinishMode finishMode, ELzmaStatus *status) +{ + SizeT outSize = *destLen; + SizeT inSize = *srcLen; + *srcLen = *destLen = 0; + for (;;) + { + SizeT inSizeCur = inSize, outSizeCur, dicPos; + ELzmaFinishMode curFinishMode; + SRes res; + if (p->dicPos == p->dicBufSize) + p->dicPos = 0; + dicPos = p->dicPos; + if (outSize > p->dicBufSize - dicPos) + { + outSizeCur = p->dicBufSize; + curFinishMode = LZMA_FINISH_ANY; + } + else + { + outSizeCur = dicPos + outSize; + curFinishMode = finishMode; + } + + res = LzmaDec_DecodeToDic(p, outSizeCur, src, &inSizeCur, curFinishMode, status); + src += inSizeCur; + inSize -= inSizeCur; + *srcLen += inSizeCur; + outSizeCur = p->dicPos - dicPos; + memcpy(dest, p->dic + dicPos, outSizeCur); + dest += outSizeCur; + outSize -= outSizeCur; + *destLen += outSizeCur; + if (res != 0) + return res; + if (outSizeCur == 0 || outSize == 0) + return SZ_OK; + } +} +#endif + +void LzmaDec_FreeProbs(CLzmaDec *p, ISzAlloc *alloc) +{ + alloc->Free(alloc, p->probs); + p->probs = 0; +} + +static void LzmaDec_FreeDict(CLzmaDec *p, ISzAlloc *alloc) +{ + alloc->Free(alloc, p->dic); + p->dic = 0; +} + +void LzmaDec_Free(CLzmaDec *p, ISzAlloc *alloc) +{ + LzmaDec_FreeProbs(p, alloc); + LzmaDec_FreeDict(p, alloc); +} + +SRes LzmaProps_Decode(CLzmaProps *p, const Byte *data, unsigned size) +{ + UInt32 dicSize; + Byte d; + + if (size < LZMA_PROPS_SIZE) + return SZ_ERROR_UNSUPPORTED; + else + dicSize = data[1] | ((UInt32)data[2] << 8) | ((UInt32)data[3] << 16) | ((UInt32)data[4] << 24); + + if (dicSize < LZMA_DIC_MIN) + dicSize = LZMA_DIC_MIN; + p->dicSize = dicSize; + + d = data[0]; + if (d >= (9 * 5 * 5)) + return SZ_ERROR_UNSUPPORTED; + + p->lc = d % 9; + d /= 9; + p->pb = d / 5; + p->lp = d % 5; + + return SZ_OK; +} + +static SRes LzmaDec_AllocateProbs2(CLzmaDec *p, const CLzmaProps *propNew, ISzAlloc *alloc) +{ + UInt32 numProbs = LzmaProps_GetNumProbs(propNew); + if (p->probs == 0 || numProbs != p->numProbs) + { + LzmaDec_FreeProbs(p, alloc); + p->probs = (CLzmaProb *)alloc->Alloc(alloc, numProbs * sizeof(CLzmaProb)); + p->numProbs = numProbs; + if (p->probs == 0) + return SZ_ERROR_MEM; + } + return SZ_OK; +} + +SRes LzmaDec_AllocateProbs(CLzmaDec *p, const Byte *props, unsigned propsSize, ISzAlloc *alloc) +{ + CLzmaProps propNew; + RINOK(LzmaProps_Decode(&propNew, props, propsSize)); + RINOK(LzmaDec_AllocateProbs2(p, &propNew, alloc)); + p->prop = propNew; + return SZ_OK; +} + +SRes LzmaDec_Allocate(CLzmaDec *p, const Byte *props, unsigned propsSize, ISzAlloc *alloc) +{ + CLzmaProps propNew; + SizeT dicBufSize; + RINOK(LzmaProps_Decode(&propNew, props, propsSize)); + RINOK(LzmaDec_AllocateProbs2(p, &propNew, alloc)); + dicBufSize = propNew.dicSize; + if (p->dic == 0 || dicBufSize != p->dicBufSize) + { + LzmaDec_FreeDict(p, alloc); + p->dic = (Byte *)alloc->Alloc(alloc, dicBufSize); + if (p->dic == 0) + { + LzmaDec_FreeProbs(p, alloc); + return SZ_ERROR_MEM; + } + } + p->dicBufSize = dicBufSize; + p->prop = propNew; + return SZ_OK; +} + +SRes LzmaDecode(Byte *dest, SizeT *destLen, const Byte *src, SizeT *srcLen, + const Byte *propData, unsigned propSize, ELzmaFinishMode finishMode, + ELzmaStatus *status, ISzAlloc *alloc) +{ + CLzmaDec p; + SRes res; + SizeT inSize = *srcLen; + SizeT outSize = *destLen; + *srcLen = *destLen = 0; + if (inSize < RC_INIT_SIZE) + return SZ_ERROR_INPUT_EOF; + + LzmaDec_Construct(&p); + res = LzmaDec_AllocateProbs(&p, propData, propSize, alloc); + if (res != 0) + return res; + p.dic = dest; + p.dicBufSize = outSize; + + LzmaDec_Init(&p); + + *srcLen = inSize; + res = LzmaDec_DecodeToDic(&p, outSize, src, srcLen, finishMode, status); + + if (res == SZ_OK && *status == LZMA_STATUS_NEEDS_MORE_INPUT) + res = SZ_ERROR_INPUT_EOF; + + (*destLen) = p.dicPos; + LzmaDec_FreeProbs(&p, alloc); + return res; +} diff --git a/snesreader/7z_C/LzmaDec.h b/snesreader/7z_C/LzmaDec.h new file mode 100644 index 00000000..98cdbe94 --- /dev/null +++ b/snesreader/7z_C/LzmaDec.h @@ -0,0 +1,223 @@ +/* LzmaDec.h -- LZMA Decoder +2008-10-04 : Igor Pavlov : Public domain */ + +#ifndef __LZMADEC_H +#define __LZMADEC_H + +#include "Types.h" + +/* #define _LZMA_PROB32 */ +/* _LZMA_PROB32 can increase the speed on some CPUs, + but memory usage for CLzmaDec::probs will be doubled in that case */ + +#ifdef _LZMA_PROB32 +#define CLzmaProb UInt32 +#else +#define CLzmaProb UInt16 +#endif + + +/* ---------- LZMA Properties ---------- */ + +#define LZMA_PROPS_SIZE 5 + +typedef struct _CLzmaProps +{ + unsigned lc, lp, pb; + UInt32 dicSize; +} CLzmaProps; + +/* LzmaProps_Decode - decodes properties +Returns: + SZ_OK + SZ_ERROR_UNSUPPORTED - Unsupported properties +*/ + +SRes LzmaProps_Decode(CLzmaProps *p, const Byte *data, unsigned size); + + +/* ---------- LZMA Decoder state ---------- */ + +/* LZMA_REQUIRED_INPUT_MAX = number of required input bytes for worst case. + Num bits = log2((2^11 / 31) ^ 22) + 26 < 134 + 26 = 160; */ + +#define LZMA_REQUIRED_INPUT_MAX 20 + +typedef struct +{ + CLzmaProps prop; + CLzmaProb *probs; + Byte *dic; + const Byte *buf; + UInt32 range, code; + SizeT dicPos; + SizeT dicBufSize; + UInt32 processedPos; + UInt32 checkDicSize; + unsigned state; + UInt32 reps[4]; + unsigned remainLen; + int needFlush; + int needInitState; + UInt32 numProbs; + unsigned tempBufSize; + Byte tempBuf[LZMA_REQUIRED_INPUT_MAX]; +} CLzmaDec; + +#define LzmaDec_Construct(p) { (p)->dic = 0; (p)->probs = 0; } + +void LzmaDec_Init(CLzmaDec *p); + +/* There are two types of LZMA streams: + 0) Stream with end mark. That end mark adds about 6 bytes to compressed size. + 1) Stream without end mark. You must know exact uncompressed size to decompress such stream. */ + +typedef enum +{ + LZMA_FINISH_ANY, /* finish at any point */ + LZMA_FINISH_END /* block must be finished at the end */ +} ELzmaFinishMode; + +/* ELzmaFinishMode has meaning only if the decoding reaches output limit !!! + + You must use LZMA_FINISH_END, when you know that current output buffer + covers last bytes of block. In other cases you must use LZMA_FINISH_ANY. + + If LZMA decoder sees end marker before reaching output limit, it returns SZ_OK, + and output value of destLen will be less than output buffer size limit. + You can check status result also. + + You can use multiple checks to test data integrity after full decompression: + 1) Check Result and "status" variable. + 2) Check that output(destLen) = uncompressedSize, if you know real uncompressedSize. + 3) Check that output(srcLen) = compressedSize, if you know real compressedSize. + You must use correct finish mode in that case. */ + +typedef enum +{ + LZMA_STATUS_NOT_SPECIFIED, /* use main error code instead */ + LZMA_STATUS_FINISHED_WITH_MARK, /* stream was finished with end mark. */ + LZMA_STATUS_NOT_FINISHED, /* stream was not finished */ + LZMA_STATUS_NEEDS_MORE_INPUT, /* you must provide more input bytes */ + LZMA_STATUS_MAYBE_FINISHED_WITHOUT_MARK /* there is probability that stream was finished without end mark */ +} ELzmaStatus; + +/* ELzmaStatus is used only as output value for function call */ + + +/* ---------- Interfaces ---------- */ + +/* There are 3 levels of interfaces: + 1) Dictionary Interface + 2) Buffer Interface + 3) One Call Interface + You can select any of these interfaces, but don't mix functions from different + groups for same object. */ + + +/* There are two variants to allocate state for Dictionary Interface: + 1) LzmaDec_Allocate / LzmaDec_Free + 2) LzmaDec_AllocateProbs / LzmaDec_FreeProbs + You can use variant 2, if you set dictionary buffer manually. + For Buffer Interface you must always use variant 1. + +LzmaDec_Allocate* can return: + SZ_OK + SZ_ERROR_MEM - Memory allocation error + SZ_ERROR_UNSUPPORTED - Unsupported properties +*/ + +SRes LzmaDec_AllocateProbs(CLzmaDec *p, const Byte *props, unsigned propsSize, ISzAlloc *alloc); +void LzmaDec_FreeProbs(CLzmaDec *p, ISzAlloc *alloc); + +SRes LzmaDec_Allocate(CLzmaDec *state, const Byte *prop, unsigned propsSize, ISzAlloc *alloc); +void LzmaDec_Free(CLzmaDec *state, ISzAlloc *alloc); + +/* ---------- Dictionary Interface ---------- */ + +/* You can use it, if you want to eliminate the overhead for data copying from + dictionary to some other external buffer. + You must work with CLzmaDec variables directly in this interface. + + STEPS: + LzmaDec_Constr() + LzmaDec_Allocate() + for (each new stream) + { + LzmaDec_Init() + while (it needs more decompression) + { + LzmaDec_DecodeToDic() + use data from CLzmaDec::dic and update CLzmaDec::dicPos + } + } + LzmaDec_Free() +*/ + +/* LzmaDec_DecodeToDic + + The decoding to internal dictionary buffer (CLzmaDec::dic). + You must manually update CLzmaDec::dicPos, if it reaches CLzmaDec::dicBufSize !!! + +finishMode: + It has meaning only if the decoding reaches output limit (dicLimit). + LZMA_FINISH_ANY - Decode just dicLimit bytes. + LZMA_FINISH_END - Stream must be finished after dicLimit. + +Returns: + SZ_OK + status: + LZMA_STATUS_FINISHED_WITH_MARK + LZMA_STATUS_NOT_FINISHED + LZMA_STATUS_NEEDS_MORE_INPUT + LZMA_STATUS_MAYBE_FINISHED_WITHOUT_MARK + SZ_ERROR_DATA - Data error +*/ + +SRes LzmaDec_DecodeToDic(CLzmaDec *p, SizeT dicLimit, + const Byte *src, SizeT *srcLen, ELzmaFinishMode finishMode, ELzmaStatus *status); + + +/* ---------- Buffer Interface ---------- */ + +/* It's zlib-like interface. + See LzmaDec_DecodeToDic description for information about STEPS and return results, + but you must use LzmaDec_DecodeToBuf instead of LzmaDec_DecodeToDic and you don't need + to work with CLzmaDec variables manually. + +finishMode: + It has meaning only if the decoding reaches output limit (*destLen). + LZMA_FINISH_ANY - Decode just destLen bytes. + LZMA_FINISH_END - Stream must be finished after (*destLen). +*/ + +SRes LzmaDec_DecodeToBuf(CLzmaDec *p, Byte *dest, SizeT *destLen, + const Byte *src, SizeT *srcLen, ELzmaFinishMode finishMode, ELzmaStatus *status); + + +/* ---------- One Call Interface ---------- */ + +/* LzmaDecode + +finishMode: + It has meaning only if the decoding reaches output limit (*destLen). + LZMA_FINISH_ANY - Decode just destLen bytes. + LZMA_FINISH_END - Stream must be finished after (*destLen). + +Returns: + SZ_OK + status: + LZMA_STATUS_FINISHED_WITH_MARK + LZMA_STATUS_NOT_FINISHED + LZMA_STATUS_MAYBE_FINISHED_WITHOUT_MARK + SZ_ERROR_DATA - Data error + SZ_ERROR_MEM - Memory allocation error + SZ_ERROR_UNSUPPORTED - Unsupported properties + SZ_ERROR_INPUT_EOF - It needs more bytes in input buffer (src). +*/ + +SRes LzmaDecode(Byte *dest, SizeT *destLen, const Byte *src, SizeT *srcLen, + const Byte *propData, unsigned propSize, ELzmaFinishMode finishMode, + ELzmaStatus *status, ISzAlloc *alloc); + +#endif diff --git a/snesreader/7z_C/Types.h b/snesreader/7z_C/Types.h new file mode 100644 index 00000000..286ce83b --- /dev/null +++ b/snesreader/7z_C/Types.h @@ -0,0 +1,206 @@ +/* Types.h -- Basic types +2008-11-23 : Igor Pavlov : Public domain */ + +#ifndef __7Z_TYPES_H +#define __7Z_TYPES_H + +#include + +#ifdef __cplusplus + extern "C" { +#endif + +#define SZ_OK 0 + +#define SZ_ERROR_DATA 1 +#define SZ_ERROR_MEM 2 +#define SZ_ERROR_CRC 3 +#define SZ_ERROR_UNSUPPORTED 4 +#define SZ_ERROR_PARAM 5 +#define SZ_ERROR_INPUT_EOF 6 +#define SZ_ERROR_OUTPUT_EOF 7 +#define SZ_ERROR_READ 8 +#define SZ_ERROR_WRITE 9 +#define SZ_ERROR_PROGRESS 10 +#define SZ_ERROR_FAIL 11 +#define SZ_ERROR_THREAD 12 + +#define SZ_ERROR_ARCHIVE 16 +#define SZ_ERROR_NO_ARCHIVE 17 + +typedef int SRes; + +#ifndef RINOK +#define RINOK(x) { int __result__ = (x); if (__result__ != 0) return __result__; } +#endif + +typedef unsigned char Byte; +typedef short Int16; +typedef unsigned short UInt16; + +#ifdef _LZMA_UINT32_IS_ULONG +typedef long Int32; +typedef unsigned long UInt32; +#else +typedef int Int32; +typedef unsigned int UInt32; +#endif + +#ifdef _SZ_NO_INT_64 + +/* define _SZ_NO_INT_64, if your compiler doesn't support 64-bit integers. + NOTES: Some code will work incorrectly in that case! */ + +typedef long Int64; +typedef unsigned long UInt64; + +#else + +#if defined(_MSC_VER) || defined(__BORLANDC__) +typedef __int64 Int64; +typedef unsigned __int64 UInt64; +#else +typedef long long int Int64; +typedef unsigned long long int UInt64; +#endif + +#endif + +#ifdef _LZMA_NO_SYSTEM_SIZE_T +typedef UInt32 SizeT; +#else +typedef size_t SizeT; +#endif + +typedef int Bool; +#define True 1 +#define False 0 + + +#ifdef _MSC_VER + +#if _MSC_VER >= 1300 +#define MY_NO_INLINE __declspec(noinline) +#else +#define MY_NO_INLINE +#endif + +#define MY_CDECL __cdecl +#define MY_STD_CALL __stdcall +#define MY_FAST_CALL MY_NO_INLINE __fastcall + +#else + +#define MY_CDECL +#define MY_STD_CALL +#define MY_FAST_CALL + +#endif + + +/* The following interfaces use first parameter as pointer to structure */ + +typedef struct +{ + SRes (*Read)(void *p, void *buf, size_t *size); + /* if (input(*size) != 0 && output(*size) == 0) means end_of_stream. + (output(*size) < input(*size)) is allowed */ +} ISeqInStream; + +/* it can return SZ_ERROR_INPUT_EOF */ +SRes SeqInStream_Read(ISeqInStream *stream, void *buf, size_t size); +SRes SeqInStream_Read2(ISeqInStream *stream, void *buf, size_t size, SRes errorType); +SRes SeqInStream_ReadByte(ISeqInStream *stream, Byte *buf); + +typedef struct +{ + size_t (*Write)(void *p, const void *buf, size_t size); + /* Returns: result - the number of actually written bytes. + (result < size) means error */ +} ISeqOutStream; + +typedef enum +{ + SZ_SEEK_SET = 0, + SZ_SEEK_CUR = 1, + SZ_SEEK_END = 2 +} ESzSeek; + +typedef struct +{ + SRes (*Read)(void *p, void *buf, size_t *size); /* same as ISeqInStream::Read */ + SRes (*Seek)(void *p, Int64 *pos, ESzSeek origin); +} ISeekInStream; + +typedef struct +{ + SRes (*Look)(void *p, void **buf, size_t *size); + /* if (input(*size) != 0 && output(*size) == 0) means end_of_stream. + (output(*size) > input(*size)) is not allowed + (output(*size) < input(*size)) is allowed */ + SRes (*Skip)(void *p, size_t offset); + /* offset must be <= output(*size) of Look */ + + SRes (*Read)(void *p, void *buf, size_t *size); + /* reads directly (without buffer). It's same as ISeqInStream::Read */ + SRes (*Seek)(void *p, Int64 *pos, ESzSeek origin); +} ILookInStream; + +SRes LookInStream_LookRead(ILookInStream *stream, void *buf, size_t *size); +SRes LookInStream_SeekTo(ILookInStream *stream, UInt64 offset); + +/* reads via ILookInStream::Read */ +SRes LookInStream_Read2(ILookInStream *stream, void *buf, size_t size, SRes errorType); +SRes LookInStream_Read(ILookInStream *stream, void *buf, size_t size); + +#define LookToRead_BUF_SIZE (1 << 14) + +typedef struct +{ + ILookInStream s; + ISeekInStream *realStream; + size_t pos; + size_t size; + Byte buf[LookToRead_BUF_SIZE]; +} CLookToRead; + +void LookToRead_CreateVTable(CLookToRead *p, int lookahead); +void LookToRead_Init(CLookToRead *p); + +typedef struct +{ + ISeqInStream s; + ILookInStream *realStream; +} CSecToLook; + +void SecToLook_CreateVTable(CSecToLook *p); + +typedef struct +{ + ISeqInStream s; + ILookInStream *realStream; +} CSecToRead; + +void SecToRead_CreateVTable(CSecToRead *p); + +typedef struct +{ + SRes (*Progress)(void *p, UInt64 inSize, UInt64 outSize); + /* Returns: result. (result != SZ_OK) means break. + Value (UInt64)(Int64)-1 for size means unknown value. */ +} ICompressProgress; + +typedef struct +{ + void *(*Alloc)(void *p, size_t size); + void (*Free)(void *p, void *address); /* address can be 0 */ +} ISzAlloc; + +#define IAlloc_Alloc(p, size) (p)->Alloc((p), size) +#define IAlloc_Free(p, a) (p)->Free((p), a) + +#ifdef __cplusplus + } +#endif + +#endif diff --git a/snesreader/7z_C/lzma.txt b/snesreader/7z_C/lzma.txt new file mode 100644 index 00000000..d4f4af92 --- /dev/null +++ b/snesreader/7z_C/lzma.txt @@ -0,0 +1,594 @@ +LZMA SDK 4.65 +------------- + +LZMA SDK provides the documentation, samples, header files, libraries, +and tools you need to develop applications that use LZMA compression. + +LZMA is default and general compression method of 7z format +in 7-Zip compression program (www.7-zip.org). LZMA provides high +compression ratio and very fast decompression. + +LZMA is an improved version of famous LZ77 compression algorithm. +It was improved in way of maximum increasing of compression ratio, +keeping high decompression speed and low memory requirements for +decompressing. + + + +LICENSE +------- + +LZMA SDK is written and placed in the public domain by Igor Pavlov. + + +LZMA SDK Contents +----------------- + +LZMA SDK includes: + + - ANSI-C/C++/C#/Java source code for LZMA compressing and decompressing + - Compiled file->file LZMA compressing/decompressing program for Windows system + + +UNIX/Linux version +------------------ +To compile C++ version of file->file LZMA encoding, go to directory +C++/7zip/Compress/LZMA_Alone +and call make to recompile it: + make -f makefile.gcc clean all + +In some UNIX/Linux versions you must compile LZMA with static libraries. +To compile with static libraries, you can use +LIB = -lm -static + + +Files +--------------------- +lzma.txt - LZMA SDK description (this file) +7zFormat.txt - 7z Format description +7zC.txt - 7z ANSI-C Decoder description +methods.txt - Compression method IDs for .7z +lzma.exe - Compiled file->file LZMA encoder/decoder for Windows +history.txt - history of the LZMA SDK + + +Source code structure +--------------------- + +C/ - C files + 7zCrc*.* - CRC code + Alloc.* - Memory allocation functions + Bra*.* - Filters for x86, IA-64, ARM, ARM-Thumb, PowerPC and SPARC code + LzFind.* - Match finder for LZ (LZMA) encoders + LzFindMt.* - Match finder for LZ (LZMA) encoders for multithreading encoding + LzHash.h - Additional file for LZ match finder + LzmaDec.* - LZMA decoding + LzmaEnc.* - LZMA encoding + LzmaLib.* - LZMA Library for DLL calling + Types.h - Basic types for another .c files + Threads.* - The code for multithreading. + + LzmaLib - LZMA Library (.DLL for Windows) + + LzmaUtil - LZMA Utility (file->file LZMA encoder/decoder). + + Archive - files related to archiving + 7z - 7z ANSI-C Decoder + +CPP/ -- CPP files + + Common - common files for C++ projects + Windows - common files for Windows related code + + 7zip - files related to 7-Zip Project + + Common - common files for 7-Zip + + Compress - files related to compression/decompression + + Copy - Copy coder + RangeCoder - Range Coder (special code of compression/decompression) + LZMA - LZMA compression/decompression on C++ + LZMA_Alone - file->file LZMA compression/decompression + Branch - Filters for x86, IA-64, ARM, ARM-Thumb, PowerPC and SPARC code + + Archive - files related to archiving + + Common - common files for archive handling + 7z - 7z C++ Encoder/Decoder + + Bundles - Modules that are bundles of other modules + + Alone7z - 7zr.exe: Standalone version of 7z.exe that supports only 7z/LZMA/BCJ/BCJ2 + Format7zR - 7zr.dll: Reduced version of 7za.dll: extracting/compressing to 7z/LZMA/BCJ/BCJ2 + Format7zExtractR - 7zxr.dll: Reduced version of 7zxa.dll: extracting from 7z/LZMA/BCJ/BCJ2. + + UI - User Interface files + + Client7z - Test application for 7za.dll, 7zr.dll, 7zxr.dll + Common - Common UI files + Console - Code for console archiver + + + +CS/ - C# files + 7zip + Common - some common files for 7-Zip + Compress - files related to compression/decompression + LZ - files related to LZ (Lempel-Ziv) compression algorithm + LZMA - LZMA compression/decompression + LzmaAlone - file->file LZMA compression/decompression + RangeCoder - Range Coder (special code of compression/decompression) + +Java/ - Java files + SevenZip + Compression - files related to compression/decompression + LZ - files related to LZ (Lempel-Ziv) compression algorithm + LZMA - LZMA compression/decompression + RangeCoder - Range Coder (special code of compression/decompression) + + +C/C++ source code of LZMA SDK is part of 7-Zip project. +7-Zip source code can be downloaded from 7-Zip's SourceForge page: + + http://sourceforge.net/projects/sevenzip/ + + + +LZMA features +------------- + - Variable dictionary size (up to 1 GB) + - Estimated compressing speed: about 2 MB/s on 2 GHz CPU + - Estimated decompressing speed: + - 20-30 MB/s on 2 GHz Core 2 or AMD Athlon 64 + - 1-2 MB/s on 200 MHz ARM, MIPS, PowerPC or other simple RISC + - Small memory requirements for decompressing (16 KB + DictionarySize) + - Small code size for decompressing: 5-8 KB + +LZMA decoder uses only integer operations and can be +implemented in any modern 32-bit CPU (or on 16-bit CPU with some conditions). + +Some critical operations that affect the speed of LZMA decompression: + 1) 32*16 bit integer multiply + 2) Misspredicted branches (penalty mostly depends from pipeline length) + 3) 32-bit shift and arithmetic operations + +The speed of LZMA decompressing mostly depends from CPU speed. +Memory speed has no big meaning. But if your CPU has small data cache, +overall weight of memory speed will slightly increase. + + +How To Use +---------- + +Using LZMA encoder/decoder executable +-------------------------------------- + +Usage: LZMA inputFile outputFile [...] + + e: encode file + + d: decode file + + b: Benchmark. There are two tests: compressing and decompressing + with LZMA method. Benchmark shows rating in MIPS (million + instructions per second). Rating value is calculated from + measured speed and it is normalized with Intel's Core 2 results. + Also Benchmark checks possible hardware errors (RAM + errors in most cases). Benchmark uses these settings: + (-a1, -d21, -fb32, -mfbt4). You can change only -d parameter. + Also you can change the number of iterations. Example for 30 iterations: + LZMA b 30 + Default number of iterations is 10. + + + + + -a{N}: set compression mode 0 = fast, 1 = normal + default: 1 (normal) + + d{N}: Sets Dictionary size - [0, 30], default: 23 (8MB) + The maximum value for dictionary size is 1 GB = 2^30 bytes. + Dictionary size is calculated as DictionarySize = 2^N bytes. + For decompressing file compressed by LZMA method with dictionary + size D = 2^N you need about D bytes of memory (RAM). + + -fb{N}: set number of fast bytes - [5, 273], default: 128 + Usually big number gives a little bit better compression ratio + and slower compression process. + + -lc{N}: set number of literal context bits - [0, 8], default: 3 + Sometimes lc=4 gives gain for big files. + + -lp{N}: set number of literal pos bits - [0, 4], default: 0 + lp switch is intended for periodical data when period is + equal 2^N. For example, for 32-bit (4 bytes) + periodical data you can use lp=2. Often it's better to set lc0, + if you change lp switch. + + -pb{N}: set number of pos bits - [0, 4], default: 2 + pb switch is intended for periodical data + when period is equal 2^N. + + -mf{MF_ID}: set Match Finder. Default: bt4. + Algorithms from hc* group doesn't provide good compression + ratio, but they often works pretty fast in combination with + fast mode (-a0). + + Memory requirements depend from dictionary size + (parameter "d" in table below). + + MF_ID Memory Description + + bt2 d * 9.5 + 4MB Binary Tree with 2 bytes hashing. + bt3 d * 11.5 + 4MB Binary Tree with 3 bytes hashing. + bt4 d * 11.5 + 4MB Binary Tree with 4 bytes hashing. + hc4 d * 7.5 + 4MB Hash Chain with 4 bytes hashing. + + -eos: write End Of Stream marker. By default LZMA doesn't write + eos marker, since LZMA decoder knows uncompressed size + stored in .lzma file header. + + -si: Read data from stdin (it will write End Of Stream marker). + -so: Write data to stdout + + +Examples: + +1) LZMA e file.bin file.lzma -d16 -lc0 + +compresses file.bin to file.lzma with 64 KB dictionary (2^16=64K) +and 0 literal context bits. -lc0 allows to reduce memory requirements +for decompression. + + +2) LZMA e file.bin file.lzma -lc0 -lp2 + +compresses file.bin to file.lzma with settings suitable +for 32-bit periodical data (for example, ARM or MIPS code). + +3) LZMA d file.lzma file.bin + +decompresses file.lzma to file.bin. + + +Compression ratio hints +----------------------- + +Recommendations +--------------- + +To increase the compression ratio for LZMA compressing it's desirable +to have aligned data (if it's possible) and also it's desirable to locate +data in such order, where code is grouped in one place and data is +grouped in other place (it's better than such mixing: code, data, code, +data, ...). + + +Filters +------- +You can increase the compression ratio for some data types, using +special filters before compressing. For example, it's possible to +increase the compression ratio on 5-10% for code for those CPU ISAs: +x86, IA-64, ARM, ARM-Thumb, PowerPC, SPARC. + +You can find C source code of such filters in C/Bra*.* files + +You can check the compression ratio gain of these filters with such +7-Zip commands (example for ARM code): +No filter: + 7z a a1.7z a.bin -m0=lzma + +With filter for little-endian ARM code: + 7z a a2.7z a.bin -m0=arm -m1=lzma + +It works in such manner: +Compressing = Filter_encoding + LZMA_encoding +Decompressing = LZMA_decoding + Filter_decoding + +Compressing and decompressing speed of such filters is very high, +so it will not increase decompressing time too much. +Moreover, it reduces decompression time for LZMA_decoding, +since compression ratio with filtering is higher. + +These filters convert CALL (calling procedure) instructions +from relative offsets to absolute addresses, so such data becomes more +compressible. + +For some ISAs (for example, for MIPS) it's impossible to get gain from such filter. + + +LZMA compressed file format +--------------------------- +Offset Size Description + 0 1 Special LZMA properties (lc,lp, pb in encoded form) + 1 4 Dictionary size (little endian) + 5 8 Uncompressed size (little endian). -1 means unknown size + 13 Compressed data + + +ANSI-C LZMA Decoder +~~~~~~~~~~~~~~~~~~~ + +Please note that interfaces for ANSI-C code were changed in LZMA SDK 4.58. +If you want to use old interfaces you can download previous version of LZMA SDK +from sourceforge.net site. + +To use ANSI-C LZMA Decoder you need the following files: +1) LzmaDec.h + LzmaDec.c + Types.h +LzmaUtil/LzmaUtil.c is example application that uses these files. + + +Memory requirements for LZMA decoding +------------------------------------- + +Stack usage of LZMA decoding function for local variables is not +larger than 200-400 bytes. + +LZMA Decoder uses dictionary buffer and internal state structure. +Internal state structure consumes + state_size = (4 + (1.5 << (lc + lp))) KB +by default (lc=3, lp=0), state_size = 16 KB. + + +How To decompress data +---------------------- + +LZMA Decoder (ANSI-C version) now supports 2 interfaces: +1) Single-call Decompressing +2) Multi-call State Decompressing (zlib-like interface) + +You must use external allocator: +Example: +void *SzAlloc(void *p, size_t size) { p = p; return malloc(size); } +void SzFree(void *p, void *address) { p = p; free(address); } +ISzAlloc alloc = { SzAlloc, SzFree }; + +You can use p = p; operator to disable compiler warnings. + + +Single-call Decompressing +------------------------- +When to use: RAM->RAM decompressing +Compile files: LzmaDec.h + LzmaDec.c + Types.h +Compile defines: no defines +Memory Requirements: + - Input buffer: compressed size + - Output buffer: uncompressed size + - LZMA Internal Structures: state_size (16 KB for default settings) + +Interface: + int LzmaDecode(Byte *dest, SizeT *destLen, const Byte *src, SizeT *srcLen, + const Byte *propData, unsigned propSize, ELzmaFinishMode finishMode, + ELzmaStatus *status, ISzAlloc *alloc); + In: + dest - output data + destLen - output data size + src - input data + srcLen - input data size + propData - LZMA properties (5 bytes) + propSize - size of propData buffer (5 bytes) + finishMode - It has meaning only if the decoding reaches output limit (*destLen). + LZMA_FINISH_ANY - Decode just destLen bytes. + LZMA_FINISH_END - Stream must be finished after (*destLen). + You can use LZMA_FINISH_END, when you know that + current output buffer covers last bytes of stream. + alloc - Memory allocator. + + Out: + destLen - processed output size + srcLen - processed input size + + Output: + SZ_OK + status: + LZMA_STATUS_FINISHED_WITH_MARK + LZMA_STATUS_NOT_FINISHED + LZMA_STATUS_MAYBE_FINISHED_WITHOUT_MARK + SZ_ERROR_DATA - Data error + SZ_ERROR_MEM - Memory allocation error + SZ_ERROR_UNSUPPORTED - Unsupported properties + SZ_ERROR_INPUT_EOF - It needs more bytes in input buffer (src). + + If LZMA decoder sees end_marker before reaching output limit, it returns OK result, + and output value of destLen will be less than output buffer size limit. + + You can use multiple checks to test data integrity after full decompression: + 1) Check Result and "status" variable. + 2) Check that output(destLen) = uncompressedSize, if you know real uncompressedSize. + 3) Check that output(srcLen) = compressedSize, if you know real compressedSize. + You must use correct finish mode in that case. */ + + +Multi-call State Decompressing (zlib-like interface) +---------------------------------------------------- + +When to use: file->file decompressing +Compile files: LzmaDec.h + LzmaDec.c + Types.h + +Memory Requirements: + - Buffer for input stream: any size (for example, 16 KB) + - Buffer for output stream: any size (for example, 16 KB) + - LZMA Internal Structures: state_size (16 KB for default settings) + - LZMA dictionary (dictionary size is encoded in LZMA properties header) + +1) read LZMA properties (5 bytes) and uncompressed size (8 bytes, little-endian) to header: + unsigned char header[LZMA_PROPS_SIZE + 8]; + ReadFile(inFile, header, sizeof(header) + +2) Allocate CLzmaDec structures (state + dictionary) using LZMA properties + + CLzmaDec state; + LzmaDec_Constr(&state); + res = LzmaDec_Allocate(&state, header, LZMA_PROPS_SIZE, &g_Alloc); + if (res != SZ_OK) + return res; + +3) Init LzmaDec structure before any new LZMA stream. And call LzmaDec_DecodeToBuf in loop + + LzmaDec_Init(&state); + for (;;) + { + ... + int res = LzmaDec_DecodeToBuf(CLzmaDec *p, Byte *dest, SizeT *destLen, + const Byte *src, SizeT *srcLen, ELzmaFinishMode finishMode); + ... + } + + +4) Free all allocated structures + LzmaDec_Free(&state, &g_Alloc); + +For full code example, look at C/LzmaUtil/LzmaUtil.c code. + + +How To compress data +-------------------- + +Compile files: LzmaEnc.h + LzmaEnc.c + Types.h + +LzFind.c + LzFind.h + LzFindMt.c + LzFindMt.h + LzHash.h + +Memory Requirements: + - (dictSize * 11.5 + 6 MB) + state_size + +Lzma Encoder can use two memory allocators: +1) alloc - for small arrays. +2) allocBig - for big arrays. + +For example, you can use Large RAM Pages (2 MB) in allocBig allocator for +better compression speed. Note that Windows has bad implementation for +Large RAM Pages. +It's OK to use same allocator for alloc and allocBig. + + +Single-call Compression with callbacks +-------------------------------------- + +Check C/LzmaUtil/LzmaUtil.c as example, + +When to use: file->file decompressing + +1) you must implement callback structures for interfaces: +ISeqInStream +ISeqOutStream +ICompressProgress +ISzAlloc + +static void *SzAlloc(void *p, size_t size) { p = p; return MyAlloc(size); } +static void SzFree(void *p, void *address) { p = p; MyFree(address); } +static ISzAlloc g_Alloc = { SzAlloc, SzFree }; + + CFileSeqInStream inStream; + CFileSeqOutStream outStream; + + inStream.funcTable.Read = MyRead; + inStream.file = inFile; + outStream.funcTable.Write = MyWrite; + outStream.file = outFile; + + +2) Create CLzmaEncHandle object; + + CLzmaEncHandle enc; + + enc = LzmaEnc_Create(&g_Alloc); + if (enc == 0) + return SZ_ERROR_MEM; + + +3) initialize CLzmaEncProps properties; + + LzmaEncProps_Init(&props); + + Then you can change some properties in that structure. + +4) Send LZMA properties to LZMA Encoder + + res = LzmaEnc_SetProps(enc, &props); + +5) Write encoded properties to header + + Byte header[LZMA_PROPS_SIZE + 8]; + size_t headerSize = LZMA_PROPS_SIZE; + UInt64 fileSize; + int i; + + res = LzmaEnc_WriteProperties(enc, header, &headerSize); + fileSize = MyGetFileLength(inFile); + for (i = 0; i < 8; i++) + header[headerSize++] = (Byte)(fileSize >> (8 * i)); + MyWriteFileAndCheck(outFile, header, headerSize) + +6) Call encoding function: + res = LzmaEnc_Encode(enc, &outStream.funcTable, &inStream.funcTable, + NULL, &g_Alloc, &g_Alloc); + +7) Destroy LZMA Encoder Object + LzmaEnc_Destroy(enc, &g_Alloc, &g_Alloc); + + +If callback function return some error code, LzmaEnc_Encode also returns that code. + + +Single-call RAM->RAM Compression +-------------------------------- + +Single-call RAM->RAM Compression is similar to Compression with callbacks, +but you provide pointers to buffers instead of pointers to stream callbacks: + +HRes LzmaEncode(Byte *dest, SizeT *destLen, const Byte *src, SizeT srcLen, + CLzmaEncProps *props, Byte *propsEncoded, SizeT *propsSize, int writeEndMark, + ICompressProgress *progress, ISzAlloc *alloc, ISzAlloc *allocBig); + +Return code: + SZ_OK - OK + SZ_ERROR_MEM - Memory allocation error + SZ_ERROR_PARAM - Incorrect paramater + SZ_ERROR_OUTPUT_EOF - output buffer overflow + SZ_ERROR_THREAD - errors in multithreading functions (only for Mt version) + + + +LZMA Defines +------------ + +_LZMA_SIZE_OPT - Enable some optimizations in LZMA Decoder to get smaller executable code. + +_LZMA_PROB32 - It can increase the speed on some 32-bit CPUs, but memory usage for + some structures will be doubled in that case. + +_LZMA_UINT32_IS_ULONG - Define it if int is 16-bit on your compiler and long is 32-bit. + +_LZMA_NO_SYSTEM_SIZE_T - Define it if you don't want to use size_t type. + + +C++ LZMA Encoder/Decoder +~~~~~~~~~~~~~~~~~~~~~~~~ +C++ LZMA code use COM-like interfaces. So if you want to use it, +you can study basics of COM/OLE. +C++ LZMA code is just wrapper over ANSI-C code. + + +C++ Notes +~~~~~~~~~~~~~~~~~~~~~~~~ +If you use some C++ code folders in 7-Zip (for example, C++ code for .7z handling), +you must check that you correctly work with "new" operator. +7-Zip can be compiled with MSVC 6.0 that doesn't throw "exception" from "new" operator. +So 7-Zip uses "CPP\Common\NewHandler.cpp" that redefines "new" operator: +operator new(size_t size) +{ + void *p = ::malloc(size); + if (p == 0) + throw CNewException(); + return p; +} +If you use MSCV that throws exception for "new" operator, you can compile without +"NewHandler.cpp". So standard exception will be used. Actually some code of +7-Zip catches any exception in internal code and converts it to HRESULT code. +So you don't need to catch CNewException, if you call COM interfaces of 7-Zip. + +--- + +http://www.7-zip.org +http://www.7-zip.org/sdk.html +http://www.7-zip.org/support.html diff --git a/snesreader/7z_C/readme.txt b/snesreader/7z_C/readme.txt new file mode 100644 index 00000000..a07f1fcc --- /dev/null +++ b/snesreader/7z_C/readme.txt @@ -0,0 +1,19 @@ +Modified LZMA 4.65 +------------------ +This is just the ANSI C 7-zip extraction code from the LZMA 4.65 source +code release, with unnecessary files removed. I've made minor changes to +allow the code to compile with almost all warnings enabled in GCC. + +* Made relevant functions extern "C" so that they can be called from +C++. + +* Put all files in same directory and removed "../../" from #includes. + +* Made private (unprototyped) functions static. + +* #if'd out code that is never called. + +* Removed a couple of Windows references. + +-- +Shay Green diff --git a/snesreader/Makefile b/snesreader/Makefile new file mode 100644 index 00000000..761317b0 --- /dev/null +++ b/snesreader/Makefile @@ -0,0 +1,187 @@ +include nall/Makefile + +qtlibs := QtCore QtGui +include nall/qt/Makefile + +c := $(compiler) -std=gnu99 +cpp := $(subst cc,++,$(compiler)) -std=gnu++0x +flags := -O3 -I. -Iobj -fomit-frame-pointer +link := + +ifeq ($(platform),x) + flags := -fPIC $(flags) + link += -s +else ifeq ($(platform),osx) + flags := -fPIC $(flags) +endif + +objects := snesreader + +# fex +objects += Binary_Extractor blargg_common blargg_errors Data_Reader fex File_Extractor Gzip_Extractor Gzip_Reader Rar_Extractor Zip7_Extractor Zip_Extractor Zlib_Inflater +# zlib +objects += adler32 crc32 inffast inflate inftrees zutil +# 7-zip +objects += 7zAlloc 7zBuf 7zCrc 7zDecode 7zExtract 7zHeader 7zIn 7zItem 7zStream Bcj2 Bra86 LzmaDec +# unrar +objects += archive arcread coder crc encname extract getbits model rarvm rarvmtbl rawread suballoc unicode unpack unpack15 unpack20 unrar unrar_misc unrar_open +# micro-bunzip +objects += micro-bunzip +# jma +objects += jma jcrc32 lzmadecode 7zlzma iiostrm inbyte lzma winout + +compile = \ + $(strip \ + $(if $(filter %.c,$<), \ + $(c) $(flags) $1 -c $< -o $@, \ + $(if $(filter %.cpp,$<), \ + $(cpp) $(flags) $1 -c $< -o $@ \ + ) \ + ) \ + ) + +%.o: $<; $(call compile) + +all: build; + +objects := $(patsubst %,obj/%.o,$(objects)) +moc_headers := $(call rwildcard,./,%.moc.hpp) +moc_objects := $(foreach f,$(moc_headers),obj/$(notdir $(patsubst %.moc.hpp,%.moc,$f))) + +# automatically run moc on all .moc.hpp (MOC header) files +%.moc: $<; $(moc) -i $< -o $@ + +# automatically generate %.moc build rules +__list = $(moc_headers) +$(foreach f,$(moc_objects), \ + $(eval __file = $(word 1,$(__list))) \ + $(eval __list = $(wordlist 2,$(words $(__list)),$(__list))) \ + $(eval $f: $(__file)) \ +) + +################## +### snesreader ### +################## + +obj/snesreader.o: snesreader.cpp * + $(call compile,$(qtinc)) + +########### +### fex ### +########### + +obj/Binary_Extractor.o: fex/Binary_Extractor.cpp fex/* +obj/blargg_common.o : fex/blargg_common.cpp fex/* +obj/blargg_errors.o : fex/blargg_errors.cpp fex/* +obj/Data_Reader.o : fex/Data_Reader.cpp fex/* +obj/fex.o : fex/fex.cpp fex/* +obj/File_Extractor.o : fex/File_Extractor.cpp fex/* +obj/Gzip_Extractor.o : fex/Gzip_Extractor.cpp fex/* +obj/Gzip_Reader.o : fex/Gzip_Reader.cpp fex/* +obj/Rar_Extractor.o : fex/Rar_Extractor.cpp fex/* +obj/Zip7_Extractor.o : fex/Zip7_Extractor.cpp fex/* +obj/Zip_Extractor.o : fex/Zip_Extractor.cpp fex/* +obj/Zlib_Inflater.o : fex/Zlib_Inflater.cpp fex/* + +############ +### zlib ### +############ + +obj/adler32.o : zlib/adler32.c zlib/* +obj/crc32.o : zlib/crc32.c zlib/* +obj/inffast.o : zlib/inffast.c zlib/* +obj/inflate.o : zlib/inflate.c zlib/* +obj/inftrees.o: zlib/inftrees.c zlib/* +obj/zutil.o : zlib/zutil.c zlib/* + +############# +### 7-zip ### +############# + +obj/7zAlloc.o : 7z_C/7zAlloc.c 7z_C/* +obj/7zBuf.o : 7z_C/7zBuf.c 7z_C/* +obj/7zCrc.o : 7z_C/7zCrc.c 7z_C/* +obj/7zDecode.o : 7z_C/7zDecode.c 7z_C/* +obj/7zExtract.o: 7z_C/7zExtract.c 7z_C/* +obj/7zHeader.o : 7z_C/7zHeader.c 7z_C/* +obj/7zIn.o : 7z_C/7zIn.c 7z_C/* +obj/7zItem.o : 7z_C/7zItem.c 7z_C/* +obj/7zStream.o : 7z_C/7zStream.c 7z_C/* +obj/Bcj2.o : 7z_C/Bcj2.c 7z_C/* +obj/Bra86.o : 7z_C/Bra86.c 7z_C/* +obj/LzmaDec.o : 7z_C/LzmaDec.c 7z_C/* + +#################### +### micro-bunzip ### +#################### + +obj/micro-bunzip.o: micro-bunzip/micro-bunzip.c micro-bunzip/* + +############# +### unrar ### +############# + +obj/archive.o : unrar/archive.cpp unrar/* +obj/arcread.o : unrar/arcread.cpp unrar/* +obj/coder.o : unrar/coder.cpp unrar/* +obj/crc.o : unrar/crc.cpp unrar/* +obj/encname.o : unrar/encname.cpp unrar/* +obj/extract.o : unrar/extract.cpp unrar/* +obj/getbits.o : unrar/getbits.cpp unrar/* +obj/model.o : unrar/model.cpp unrar/* +obj/rarvm.o : unrar/rarvm.cpp unrar/* +obj/rarvmtbl.o : unrar/rarvmtbl.cpp unrar/* +obj/rawread.o : unrar/rawread.cpp unrar/* +obj/suballoc.o : unrar/suballoc.cpp unrar/* +obj/unicode.o : unrar/unicode.cpp unrar/* +obj/unpack.o : unrar/unpack.cpp unrar/* +obj/unpack15.o : unrar/unpack15.cpp unrar/* +obj/unpack20.o : unrar/unpack20.cpp unrar/* +obj/unrar.o : unrar/unrar.cpp unrar/* +obj/unrar_misc.o: unrar/unrar_misc.cpp unrar/* +obj/unrar_open.o: unrar/unrar_open.cpp unrar/* + +############## +### libjma ### +############## + +obj/jma.o : libjma/jma.cpp libjma/* +obj/jcrc32.o : libjma/jcrc32.cpp libjma/* +obj/lzmadecode.o: libjma/lzmadecode.cpp libjma/* +obj/7zlzma.o : libjma/7zlzma.cpp libjma/* +obj/iiostrm.o : libjma/iiostrm.cpp libjma/* +obj/inbyte.o : libjma/inbyte.cpp libjma/* +obj/lzma.o : libjma/lzma.cpp libjma/* +obj/winout.o : libjma/winout.cpp libjma/* + +############### +### targets ### +############### + +build: $(moc_objects) $(objects) +ifeq ($(platform),x) + ar rcs libsnesreader.a $(objects) + $(cpp) $(link) -o libsnesreader.so -shared -Wl,-soname,libsnesreader.so.1 $(objects) $(qtlib) +else ifeq ($(platform),osx) + ar rcs libsnesreader.a $(objects) + $(cpp) $(link) -o libsnesreader.dylib -shared -dynamiclib $(objects) $(qtlib) +else ifeq ($(platform),win) + $(cpp) $(link) -o snesreader.dll -shared -Wl,--out-implib,libsnesreader.a $(objects) $(qtlib) +endif + +install: +ifeq ($(platform),x) + install -D -m 755 libsnesreader.a $(DESTDIR)$(prefix)/lib + install -D -m 755 libsnesreader.so $(DESTDIR)$(prefix)/lib + ldconfig -n $(DESTDIR)$(prefix)/lib +else ifeq ($(platform),osx) + cp libsnesreader.dylib /usr/local/lib/libsnesreader.dylib +endif + +clean: + -@$(call delete,obj/*.o) + -@$(call delete,obj/*.moc) + -@$(call delete,libsnesreader.a) + -@$(call delete,libsnesreader.so) + -@$(call delete,libsnesreader.dylib) + -@$(call delete,snesreader.dll) diff --git a/snesreader/cc.bat b/snesreader/cc.bat new file mode 100644 index 00000000..8359a530 --- /dev/null +++ b/snesreader/cc.bat @@ -0,0 +1,2 @@ +@mingw32-make +@pause \ No newline at end of file diff --git a/snesreader/clean.bat b/snesreader/clean.bat new file mode 100644 index 00000000..d8bb7e0b --- /dev/null +++ b/snesreader/clean.bat @@ -0,0 +1 @@ +@mingw32-make clean diff --git a/snesreader/fex/Binary_Extractor.cpp b/snesreader/fex/Binary_Extractor.cpp new file mode 100644 index 00000000..8c85b992 --- /dev/null +++ b/snesreader/fex/Binary_Extractor.cpp @@ -0,0 +1,77 @@ +// File_Extractor 1.0.0. http://www.slack.net/~ant/ + +#include "Binary_Extractor.h" + +/* Copyright (C) 2005-2009 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +// TODO: could close file once data has been read into memory + +static File_Extractor* new_binary() +{ + return BLARGG_NEW Binary_Extractor; +} + +fex_type_t_ const fex_bin_type [1] = {{ + "", + &new_binary, + "file", + NULL +}}; + +Binary_Extractor::Binary_Extractor() : + File_Extractor( fex_bin_type ) +{ } + +Binary_Extractor::~Binary_Extractor() +{ + close(); +} + +blargg_err_t Binary_Extractor::open_path_v() +{ + set_name( arc_path() ); + return blargg_ok; +} + +blargg_err_t Binary_Extractor::open_v() +{ + set_name( arc_path() ); + set_info( arc().remain(), 0, 0 ); + return blargg_ok; +} + +void Binary_Extractor::close_v() +{ } + +blargg_err_t Binary_Extractor::next_v() +{ + return blargg_ok; +} + +blargg_err_t Binary_Extractor::rewind_v() +{ + return open_path_v(); +} + +blargg_err_t Binary_Extractor::stat_v() +{ + RETURN_ERR( open_arc_file() ); + RETURN_ERR( arc().seek( 0 ) ); + return open_v(); +} + +blargg_err_t Binary_Extractor::extract_v( void* p, int n ) +{ + return arc().read( p, n ); +} diff --git a/snesreader/fex/Binary_Extractor.h b/snesreader/fex/Binary_Extractor.h new file mode 100644 index 00000000..339a0873 --- /dev/null +++ b/snesreader/fex/Binary_Extractor.h @@ -0,0 +1,26 @@ +// Presents a single file as an "archive" of just that file. + +// File_Extractor 1.0.0 +#ifndef BINARY_EXTRACTOR_H +#define BINARY_EXTRACTOR_H + +#include "File_Extractor.h" + +class Binary_Extractor : public File_Extractor { +public: + Binary_Extractor(); + virtual ~Binary_Extractor(); + +protected: + virtual blargg_err_t open_path_v(); + virtual blargg_err_t open_v(); + virtual void close_v(); + + virtual blargg_err_t next_v(); + virtual blargg_err_t rewind_v(); + + virtual blargg_err_t stat_v(); + virtual blargg_err_t extract_v( void*, int ); +}; + +#endif diff --git a/snesreader/fex/Data_Reader.cpp b/snesreader/fex/Data_Reader.cpp new file mode 100644 index 00000000..cbea9f47 --- /dev/null +++ b/snesreader/fex/Data_Reader.cpp @@ -0,0 +1,551 @@ +// File_Extractor 1.0.0. http://www.slack.net/~ant/ + +#include "Data_Reader.h" + +#include "blargg_endian.h" +#include +#include + +#if BLARGG_UTF8_PATHS + #include +#endif + +/* Copyright (C) 2005-2009 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +// Data_Reader + +blargg_err_t Data_Reader::read( void* p, int n ) +{ + assert( n >= 0 ); + + if ( n < 0 ) + return blargg_err_caller; + + if ( n <= 0 ) + return blargg_ok; + + if ( n > remain() ) + return blargg_err_file_eof; + + blargg_err_t err = read_v( p, n ); + if ( !err ) + remain_ -= n; + + return err; +} + +blargg_err_t Data_Reader::read_avail( void* p, int* n_ ) +{ + assert( *n_ >= 0 ); + + int n = min( *n_, remain() ); + *n_ = 0; + + if ( n < 0 ) + return blargg_err_caller; + + if ( n <= 0 ) + return blargg_ok; + + blargg_err_t err = read_v( p, n ); + if ( !err ) + { + remain_ -= n; + *n_ = n; + } + + return err; +} + +blargg_err_t Data_Reader::read_avail( void* p, long* n ) +{ + int i = STATIC_CAST(int, *n); + blargg_err_t err = read_avail( p, &i ); + *n = i; + return err; +} + +blargg_err_t Data_Reader::skip_v( int count ) +{ + char buf [512]; + while ( count ) + { + int n = min( count, (int) sizeof buf ); + count -= n; + RETURN_ERR( read_v( buf, n ) ); + } + return blargg_ok; +} + +blargg_err_t Data_Reader::skip( int n ) +{ + assert( n >= 0 ); + + if ( n < 0 ) + return blargg_err_caller; + + if ( n <= 0 ) + return blargg_ok; + + if ( n > remain() ) + return blargg_err_file_eof; + + blargg_err_t err = skip_v( n ); + if ( !err ) + remain_ -= n; + + return err; +} + + +// File_Reader + +blargg_err_t File_Reader::seek( int n ) +{ + assert( n >= 0 ); + + if ( n < 0 ) + return blargg_err_caller; + + if ( n == tell() ) + return blargg_ok; + + if ( n > size() ) + return blargg_err_file_eof; + + blargg_err_t err = seek_v( n ); + if ( !err ) + set_tell( n ); + + return err; +} + +blargg_err_t File_Reader::skip_v( int n ) +{ + return seek_v( tell() + n ); +} + + +// Subset_Reader + +Subset_Reader::Subset_Reader( Data_Reader* dr, int size ) : + in( dr ) +{ + set_remain( min( size, dr->remain() ) ); +} + +blargg_err_t Subset_Reader::read_v( void* p, int s ) +{ + return in->read( p, s ); +} + + +// Remaining_Reader + +Remaining_Reader::Remaining_Reader( void const* h, int size, Data_Reader* r ) : + in( r ) +{ + header = h; + header_remain = size; + + set_remain( size + r->remain() ); +} + +blargg_err_t Remaining_Reader::read_v( void* out, int count ) +{ + int first = min( count, header_remain ); + if ( first ) + { + memcpy( out, header, first ); + header = STATIC_CAST(char const*, header) + first; + header_remain -= first; + } + + return in->read( STATIC_CAST(char*, out) + first, count - first ); +} + + +// Mem_File_Reader + +Mem_File_Reader::Mem_File_Reader( const void* p, long s ) : + begin( STATIC_CAST(const char*, p) ) +{ + set_size( s ); +} + +blargg_err_t Mem_File_Reader::read_v( void* p, int s ) +{ + memcpy( p, begin + tell(), s ); + return blargg_ok; +} + +blargg_err_t Mem_File_Reader::seek_v( int ) +{ + return blargg_ok; +} + + +// Callback_Reader + +Callback_Reader::Callback_Reader( callback_t c, long s, void* d ) : + callback( c ), + user_data( d ) +{ + set_remain( s ); +} + +blargg_err_t Callback_Reader::read_v( void* out, int count ) +{ + return callback( user_data, out, count ); +} + + +// Callback_File_Reader + +Callback_File_Reader::Callback_File_Reader( callback_t c, long s, void* d ) : + callback( c ), + user_data( d ) +{ + set_size( s ); +} + +blargg_err_t Callback_File_Reader::read_v( void* out, int count ) +{ + return callback( user_data, out, count, tell() ); +} + +blargg_err_t Callback_File_Reader::seek_v( int ) +{ + return blargg_ok; +} + + +// BLARGG_UTF8_PATHS + +#if BLARGG_UTF8_PATHS + +// Thanks to byuu for the idea for BLARGG_UTF8_PATHS and the implementations + +// Converts wide-character path to UTF-8. Free result with free(). Only supported on Windows. +char* blargg_to_utf8( const wchar_t* wpath ) +{ + if ( wpath == NULL ) + return NULL; + + int needed = WideCharToMultiByte( CP_UTF8, 0, wpath, -1, NULL, 0, NULL, NULL ); + if ( needed <= 0 ) + return NULL; + + char* path = (char*) malloc( needed ); + if ( path == NULL ) + return NULL; + + int actual = WideCharToMultiByte( CP_UTF8, 0, wpath, -1, path, needed, NULL, NULL ); + if ( actual == 0 ) + { + free( path ); + return NULL; + } + + assert( actual == needed ); + return path; +} + +// Converts UTF-8 path to wide-character. Free result with free() Only supported on Windows. +wchar_t* blargg_to_wide( const char* path ) +{ + if ( path == NULL ) + return NULL; + + int needed = MultiByteToWideChar( CP_UTF8, 0, path, -1, NULL, 0 ); + if ( needed <= 0 ) + return NULL; + + wchar_t* wpath = (wchar_t*) malloc( needed * sizeof *wpath ); + if ( wpath == NULL ) + return NULL; + + int actual = MultiByteToWideChar( CP_UTF8, 0, path, -1, wpath, needed ); + if ( actual == 0 ) + { + free( wpath ); + return NULL; + } + + assert( actual == needed ); + return wpath; +} + +static FILE* blargg_fopen( const char path [], const char mode [] ) +{ + FILE* file = NULL; + wchar_t* wmode = NULL; + wchar_t* wpath = NULL; + + wpath = blargg_to_wide( path ); + if ( wpath ) + { + wmode = blargg_to_wide( mode ); + if ( wmode ) + file = _wfopen( wpath, wmode ); + } + + // Save and restore errno in case free() clears it + int saved_errno = errno; + free( wmode ); + free( wpath ); + errno = saved_errno; + + return file; +} + +#else + +static inline FILE* blargg_fopen( const char path [], const char mode [] ) +{ + return fopen( path, mode ); +} + +#endif + + +// Std_File_Reader + +Std_File_Reader::Std_File_Reader() +{ + file_ = NULL; +} + +Std_File_Reader::~Std_File_Reader() +{ + close(); +} + +static blargg_err_t blargg_fopen( FILE** out, const char path [] ) +{ + errno = 0; + *out = blargg_fopen( path, "rb" ); + if ( !*out ) + { + #ifdef ENOENT + if ( errno == ENOENT ) + return blargg_err_file_missing; + #endif + #ifdef ENOMEM + if ( errno == ENOMEM ) + return blargg_err_memory; + #endif + return blargg_err_file_read; + } + + return blargg_ok; +} + +static blargg_err_t blargg_fsize( FILE* f, long* out ) +{ + if ( fseek( f, 0, SEEK_END ) ) + return blargg_err_file_io; + + *out = ftell( f ); + if ( *out < 0 ) + return blargg_err_file_io; + + if ( fseek( f, 0, SEEK_SET ) ) + return blargg_err_file_io; + + return blargg_ok; +} + +blargg_err_t Std_File_Reader::open( const char path [] ) +{ + close(); + + FILE* f; + RETURN_ERR( blargg_fopen( &f, path ) ); + + long s; + blargg_err_t err = blargg_fsize( f, &s ); + if ( err ) + { + fclose( f ); + return err; + } + + file_ = f; + set_size( s ); + + return blargg_ok; +} + +void Std_File_Reader::make_unbuffered() +{ + if ( setvbuf( STATIC_CAST(FILE*, file_), NULL, _IONBF, 0 ) ) + check( false ); // shouldn't fail, but OK if it does +} + +blargg_err_t Std_File_Reader::read_v( void* p, int s ) +{ + if ( (size_t) s != fread( p, 1, s, STATIC_CAST(FILE*, file_) ) ) + { + // Data_Reader's wrapper should prevent EOF + check( !feof( STATIC_CAST(FILE*, file_) ) ); + + return blargg_err_file_io; + } + + return blargg_ok; +} + +blargg_err_t Std_File_Reader::seek_v( int n ) +{ + if ( fseek( STATIC_CAST(FILE*, file_), n, SEEK_SET ) ) + { + // Data_Reader's wrapper should prevent EOF + check( !feof( STATIC_CAST(FILE*, file_) ) ); + + return blargg_err_file_io; + } + + return blargg_ok; +} + +void Std_File_Reader::close() +{ + if ( file_ ) + { + fclose( STATIC_CAST(FILE*, file_) ); + file_ = NULL; + } +} + + +// Gzip_File_Reader + +#ifdef HAVE_ZLIB_H + +#include "zlib.h" + +static const char* get_gzip_eof( const char path [], long* eof ) +{ + FILE* file; + RETURN_ERR( blargg_fopen( &file, path ) ); + + int const h_size = 4; + unsigned char h [h_size]; + + // read four bytes to ensure that we can seek to -4 later + if ( fread( h, 1, h_size, file ) != (size_t) h_size || h[0] != 0x1F || h[1] != 0x8B ) + { + // Not gzipped + if ( ferror( file ) ) + return blargg_err_file_io; + + if ( fseek( file, 0, SEEK_END ) ) + return blargg_err_file_io; + + *eof = ftell( file ); + if ( *eof < 0 ) + return blargg_err_file_io; + } + else + { + // Gzipped; get uncompressed size from end + if ( fseek( file, -h_size, SEEK_END ) ) + return blargg_err_file_io; + + if ( fread( h, 1, h_size, file ) != (size_t) h_size ) + return blargg_err_file_io; + + *eof = get_le32( h ); + } + + if ( fclose( file ) ) + check( false ); + + return blargg_ok; +} + +Gzip_File_Reader::Gzip_File_Reader() +{ + file_ = NULL; +} + +Gzip_File_Reader::~Gzip_File_Reader() +{ + close(); +} + +blargg_err_t Gzip_File_Reader::open( const char path [] ) +{ + close(); + + long s; + RETURN_ERR( get_gzip_eof( path, &s ) ); + + file_ = gzopen( path, "rb" ); + if ( !file_ ) + return blargg_err_file_read; + + set_size( s ); + return blargg_ok; +} + +static blargg_err_t convert_gz_error( gzFile file ) +{ + int err; + gzerror( file, &err ); + + switch ( err ) + { + case Z_STREAM_ERROR: break; + case Z_DATA_ERROR: return blargg_err_file_corrupt; + case Z_MEM_ERROR: return blargg_err_memory; + case Z_BUF_ERROR: break; + } + return blargg_err_internal; +} + +blargg_err_t Gzip_File_Reader::read_v( void* p, int s ) +{ + int result = gzread( file_, p, s ); + if ( result != s ) + { + if ( result < 0 ) + return convert_gz_error( file_ ); + + return blargg_err_file_corrupt; + } + + return blargg_ok; +} + +blargg_err_t Gzip_File_Reader::seek_v( int n ) +{ + if ( gzseek( file_, n, SEEK_SET ) < 0 ) + return convert_gz_error( file_ ); + + return blargg_ok; +} + +void Gzip_File_Reader::close() +{ + if ( file_ ) + { + if ( gzclose( file_ ) ) + check( false ); + file_ = NULL; + } +} + +#endif diff --git a/snesreader/fex/Data_Reader.h b/snesreader/fex/Data_Reader.h new file mode 100644 index 00000000..be206f7b --- /dev/null +++ b/snesreader/fex/Data_Reader.h @@ -0,0 +1,264 @@ +// Lightweight interface for reading data from byte stream + +// File_Extractor 1.0.0 +#ifndef DATA_READER_H +#define DATA_READER_H + +#include "blargg_common.h" + +/* Some functions accept a long instead of int for convenience where caller has +a long due to some other interface, and would otherwise have to get a warning, +or cast it (and verify that it wasn't outside the range of an int). + +To really support huge (>2GB) files, long isn't a solution, since there's no +guarantee it's more than 32 bits. We'd need to use long long (if available), or +something compiler-specific, and change all places file sizes or offsets are +used. */ + +// Supports reading and finding out how many bytes are remaining +class Data_Reader { +public: + + // Reads min(*n,remain()) bytes and sets *n to this number, thus trying to read more + // tham remain() bytes doesn't result in error, just *n being set to remain(). + blargg_err_t read_avail( void* p, int* n ); + blargg_err_t read_avail( void* p, long* n ); + + // Reads exactly n bytes, or returns error if they couldn't ALL be read. + // Reading past end of file results in blargg_err_file_eof. + blargg_err_t read( void* p, int n ); + + // Number of bytes remaining until end of file + int remain() const { return remain_; } + + // Reads and discards n bytes. Skipping past end of file results in blargg_err_file_eof. + blargg_err_t skip( int n ); + + virtual ~Data_Reader() { } + +private: + // noncopyable + Data_Reader( const Data_Reader& ); + Data_Reader& operator = ( const Data_Reader& ); + +// Derived interface +protected: + Data_Reader() : remain_( 0 ) { } + + // Sets remain + void set_remain( int n ) { assert( n >= 0 ); remain_ = n; } + + // Do same as read(). Guaranteed that 0 < n <= remain(). Value of remain() is updated + // AFTER this call succeeds, not before. set_remain() should NOT be called from this. + virtual blargg_err_t read_v( void*, int n ) BLARGG_PURE( { (void)n; return blargg_ok; } ) + + // Do same as skip(). Guaranteed that 0 < n <= remain(). Default just reads data + // and discards it. Value of remain() is updated AFTER this call succeeds, not + // before. set_remain() should NOT be called from this. + virtual blargg_err_t skip_v( int n ); + +// Implementation +public: + BLARGG_DISABLE_NOTHROW + +private: + int remain_; +}; + + +// Supports seeking in addition to Data_Reader operations +class File_Reader : public Data_Reader { +public: + + // Size of file + int size() const { return size_; } + + // Current position in file + int tell() const { return size_ - remain(); } + + // Goes to new position + blargg_err_t seek( int ); + +// Derived interface +protected: + // Sets size and resets position + void set_size( int n ) { size_ = n; Data_Reader::set_remain( n ); } + void set_size( long n ) { set_size( STATIC_CAST(int, n) ); } + + // Sets reported position + void set_tell( int i ) { assert( 0 <= i && i <= size_ ); Data_Reader::set_remain( size_ - i ); } + + // Do same as seek(). Guaranteed that 0 <= n <= size(). Value of tell() is updated + // AFTER this call succeeds, not before. set_* functions should NOT be called from this. + virtual blargg_err_t seek_v( int n ) BLARGG_PURE( { (void)n; return blargg_ok; } ) + +// Implementation +protected: + File_Reader() : size_( 0 ) { } + + virtual blargg_err_t skip_v( int ); + +private: + int size_; + + void set_remain(); // avoid accidental use of set_remain +}; + + +// Reads from file on disk +class Std_File_Reader : public File_Reader { +public: + + // Opens file + blargg_err_t open( const char path [] ); + + // Closes file if one was open + void close(); + + // Switches to unbuffered mode. Useful if buffering is already being + // done at a higher level. + void make_unbuffered(); + +// Implementation +public: + Std_File_Reader(); + virtual ~Std_File_Reader(); + +protected: + virtual blargg_err_t read_v( void*, int ); + virtual blargg_err_t seek_v( int ); + +private: + void* file_; +}; + + +// Treats range of memory as a file +class Mem_File_Reader : public File_Reader { +public: + + Mem_File_Reader( const void* begin, long size ); + +// Implementation +protected: + virtual blargg_err_t read_v( void*, int ); + virtual blargg_err_t seek_v( int ); + +private: + const char* const begin; +}; + + +// Allows only count bytes to be read from reader passed +class Subset_Reader : public Data_Reader { +public: + + Subset_Reader( Data_Reader*, int count ); + +// Implementation +protected: + virtual blargg_err_t read_v( void*, int ); + +private: + Data_Reader* const in; +}; + + +// Joins already-read header and remaining data into original file. +// Meant for cases where you've already read header and don't want +// to seek and re-read data (for efficiency). +class Remaining_Reader : public Data_Reader { +public: + + Remaining_Reader( void const* header, int header_size, Data_Reader* ); + +// Implementation +protected: + virtual blargg_err_t read_v( void*, int ); + +private: + Data_Reader* const in; + void const* header; + int header_remain; +}; + + +// Invokes callback function to read data +extern "C" { // necessary to be usable from C + typedef const char* (*callback_reader_func_t)( + void* user_data, // Same value passed to constructor + void* out, // Buffer to place data into + int count // Number of bytes to read + ); +} +class Callback_Reader : public Data_Reader { +public: + typedef callback_reader_func_t callback_t; + Callback_Reader( callback_t, long size, void* user_data ); + +// Implementation +protected: + virtual blargg_err_t read_v( void*, int ); + +private: + callback_t const callback; + void* const user_data; +}; + + +// Invokes callback function to read data +extern "C" { // necessary to be usable from C + typedef const char* (*callback_file_reader_func_t)( + void* user_data, // Same value passed to constructor + void* out, // Buffer to place data into + int count, // Number of bytes to read + int pos // Position in file to read from + ); +} +class Callback_File_Reader : public File_Reader { +public: + typedef callback_file_reader_func_t callback_t; + Callback_File_Reader( callback_t, long size, void* user_data ); + +// Implementation +protected: + virtual blargg_err_t read_v( void*, int ); + virtual blargg_err_t seek_v( int ); + +private: + callback_t const callback; + void* const user_data; +}; + + +#ifdef HAVE_ZLIB_H + +// Reads file compressed with gzip (or uncompressed) +class Gzip_File_Reader : public File_Reader { +public: + + // Opens possibly gzipped file + blargg_err_t open( const char path [] ); + + // Closes file if one was open + void close(); + +// Implementation +public: + Gzip_File_Reader(); + ~Gzip_File_Reader(); + +protected: + virtual blargg_err_t read_v( void*, int ); + virtual blargg_err_t seek_v( int ); + +private: + // void* so "zlib.h" doesn't have to be included here + void* file_; +}; +#endif + +char* blargg_to_utf8( const wchar_t* ); +wchar_t* blargg_to_wide( const char* ); + +#endif diff --git a/snesreader/fex/File_Extractor.cpp b/snesreader/fex/File_Extractor.cpp new file mode 100644 index 00000000..e060e095 --- /dev/null +++ b/snesreader/fex/File_Extractor.cpp @@ -0,0 +1,341 @@ +// File_Extractor 1.0.0. http://www.slack.net/~ant/ + +#include "File_Extractor.h" + +/* Copyright (C) 2005-2009 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +File_Extractor::fex_t( fex_type_t t ) : + type_( t ) +{ + own_file_ = NULL; + + close_(); +} + +// Open + +blargg_err_t File_Extractor::set_path( const char* path ) +{ + if ( !path ) + path = ""; + + RETURN_ERR( path_.resize( strlen( path ) + 1 ) ); + memcpy( path_.begin(), path, path_.size() ); + return blargg_ok; +} + +blargg_err_t File_Extractor::open( const char path [] ) +{ + close(); + + RETURN_ERR( set_path( path ) ); + + blargg_err_t err = open_path_v(); + if ( err ) + close(); + else + opened_ = true; + + return err; +} + +blargg_err_t File_Extractor::open_path_v() +{ + RETURN_ERR( open_arc_file() ); + + return open_v(); +} + +inline +static void make_unbuffered( Std_File_Reader* r ) +{ + r->make_unbuffered(); +} + +inline +static void make_unbuffered( void* ) +{ } + +blargg_err_t File_Extractor::open_arc_file( bool unbuffered ) +{ + if ( reader_ ) + return blargg_ok; + + FEX_FILE_READER* in = BLARGG_NEW FEX_FILE_READER; + CHECK_ALLOC( in ); + + blargg_err_t err = in->open( arc_path() ); + if ( err ) + { + delete in; + } + else + { + reader_ = in; + own_file(); + if ( unbuffered ) + make_unbuffered( in ); + } + + return err; +} + +blargg_err_t File_Extractor::open( File_Reader* input, const char* path ) +{ + close(); + + RETURN_ERR( set_path( path ) ); + + RETURN_ERR( input->seek( 0 ) ); + + reader_ = input; + blargg_err_t err = open_v(); + if ( err ) + close(); + else + opened_ = true; + + return err; +} + +// Close + +void File_Extractor::close() +{ + close_v(); + close_(); +} + +void File_Extractor::close_() +{ + delete own_file_; + own_file_ = NULL; + + tell_ = 0; + reader_ = NULL; + opened_ = false; + + path_.clear(); + clear_file(); +} + +File_Extractor::~fex_t() +{ + check( !opened() ); // fails if derived destructor didn't call close() + + delete own_file_; +} + +// Scanning + +void File_Extractor::clear_file() +{ + name_ = NULL; + wname_ = NULL; + done_ = true; + stat_called = false; + data_ptr_ = NULL; + + set_info( 0 ); + own_data_.clear(); + clear_file_v(); +} + +void File_Extractor::set_name( const char new_name [], const wchar_t* new_wname ) +{ + name_ = new_name; + wname_ = new_wname; + done_ = false; +} + +void File_Extractor::set_info( int new_size, unsigned date, unsigned crc ) +{ + size_ = new_size; + date_ = (date != 0xFFFFFFFF ? date : 0); + crc32_ = crc; + set_remain( new_size ); +} + +blargg_err_t File_Extractor::next_() +{ + tell_++; + clear_file(); + + blargg_err_t err = next_v(); + if ( err ) + clear_file(); + + return err; +} + +blargg_err_t File_Extractor::next() +{ + assert( !done() ); + return next_(); +} + +blargg_err_t File_Extractor::rewind() +{ + assert( opened() ); + + tell_ = 0; + clear_file(); + + blargg_err_t err = rewind_v(); + if ( err ) + clear_file(); + + return err; +} + +blargg_err_t File_Extractor::stat() +{ + assert( !done() ); + + if ( !stat_called ) + { + RETURN_ERR( stat_v() ); + stat_called = true; + } + return blargg_ok; +} + +// Tell/seek + +int const pos_offset = 1; + +fex_pos_t File_Extractor::tell_arc() const +{ + assert( opened() ); + + fex_pos_t pos = tell_arc_v(); + assert( pos >= 0 ); + + return pos + pos_offset; +} + +blargg_err_t File_Extractor::seek_arc( fex_pos_t pos ) +{ + assert( opened() ); + assert( pos != 0 ); + + clear_file(); + + blargg_err_t err = seek_arc_v( pos - pos_offset ); + if ( err ) + clear_file(); + + return err; +} + +fex_pos_t File_Extractor::tell_arc_v() const +{ + return tell_; +} + +blargg_err_t File_Extractor::seek_arc_v( fex_pos_t pos ) +{ + // >= because seeking to current file should always reset read pointer etc. + if ( tell_ >= pos ) + RETURN_ERR( rewind() ); + + while ( tell_ < pos ) + { + RETURN_ERR( next_() ); + + if ( done() ) + { + assert( false ); + return blargg_err_caller; + } + } + + assert( tell_ == pos ); + + return blargg_ok; +} + +// Extraction + +blargg_err_t File_Extractor::rewind_file() +{ + RETURN_ERR( stat() ); + + if ( tell() > 0 ) + { + if ( data_ptr_ ) + { + set_remain( size() ); + } + else + { + RETURN_ERR( seek_arc( tell_arc() ) ); + RETURN_ERR( stat() ); + } + } + + return blargg_ok; +} + +blargg_err_t File_Extractor::data( const void** data_out ) +{ + assert( !done() ); + + *data_out = NULL; + if ( !data_ptr_ ) + { + int old_tell = tell(); + + RETURN_ERR( rewind_file() ); + + void const* ptr; + RETURN_ERR( data_v( &ptr ) ); + data_ptr_ = ptr; + + // Now that data is in memory, we can seek by simply setting remain + set_remain( size() - old_tell ); + } + + *data_out = data_ptr_; + return blargg_ok; +} + +blargg_err_t File_Extractor::data_v( void const** out ) +{ + RETURN_ERR( own_data_.resize( size() ) ); + *out = own_data_.begin(); + + blargg_err_t err = extract_v( own_data_.begin(), own_data_.size() ); + if ( err ) + own_data_.clear(); + + return err; +} + +blargg_err_t File_Extractor::extract_v( void* out, int count ) +{ + void const* p; + RETURN_ERR( data( &p ) ); + memcpy( out, STATIC_CAST(char const*,p) + (size() - remain()), count ); + + return blargg_ok; +} + +blargg_err_t File_Extractor::read_v( void* out, int count ) +{ + if ( data_ptr_ ) + return File_Extractor::extract_v( out, count ); + + return extract_v( out, count ); +} diff --git a/snesreader/fex/File_Extractor.h b/snesreader/fex/File_Extractor.h new file mode 100644 index 00000000..ad25d5f8 --- /dev/null +++ b/snesreader/fex/File_Extractor.h @@ -0,0 +1,191 @@ +// Compressed file archive interface + +// File_Extractor 1.0.0 +#ifndef FILE_EXTRACTOR_H +#define FILE_EXTRACTOR_H + +#include "blargg_common.h" +#include "Data_Reader.h" +#include "fex.h" + +struct fex_t : private Data_Reader { +public: + virtual ~fex_t(); + +// Open/close + + // Opens archive from custom data source. Keeps pointer until close(). + blargg_err_t open( File_Reader* input, const char* path = NULL ); + + // Takes ownership of File_Reader* passed to open(), so that close() + // will delete it. + void own_file() { own_file_ = reader_; } + + // See fex.h + blargg_err_t open( const char path [] ); + fex_type_t type() const { return type_; } + void close(); + +// Scanning + + // See fex.h + bool done() const { return done_; } + blargg_err_t next(); + blargg_err_t rewind(); + fex_pos_t tell_arc() const; + blargg_err_t seek_arc( fex_pos_t ); + +// Info + + // See fex.h + const char* name() const { return name_; } + const wchar_t* wname() const { return wname_; } + blargg_err_t stat(); + int size() const { assert( stat_called ); return size_; } + unsigned int dos_date() const { return date_; } + unsigned int crc32() const { return crc32_; } + +// Extraction + + // Data_Reader to current file's data, so standard Data_Reader interface can + // be used, rather than having to treat archives specially. stat() must have + // been called. + Data_Reader& reader() { assert( stat_called ); return *this; } + + // See fex.h + blargg_err_t data( const void** data_out ); + int tell() const { return size_ - remain(); } + +// Derived interface +protected: + + // Sets type of object + fex_t( fex_type_t ); + + // Path to archive file, or "" if none supplied + const char* arc_path() const { return path_.begin(); } + + // Opens archive file if it's not already. If unbuffered is true, opens file + // without any buffering. + blargg_err_t open_arc_file( bool unbuffered = false ); + + // Archive file + File_Reader& arc() const { return *reader_; } + + // Sets current file name + void set_name( const char name [], const wchar_t* wname = NULL ); + + // Sets current file information + void set_info( int size, unsigned date = 0, unsigned crc = 0 ); + +// User overrides + + // Overrides must do indicated task. Non-pure functions have reasonable default + // implementation. Overrides should avoid calling public functions like + // next() and rewind(). + + // Open archive using file_path(). OK to delay actual file opening until later. + // Default just calls open_arc_file(), then open_v(). + virtual blargg_err_t open_path_v(); + + // Open archive using file() for source data. If unsupported, return error. + virtual blargg_err_t open_v() BLARGG_PURE( ; ) + + // Go to next file in archive and call set_name() and optionally set_info() + virtual blargg_err_t next_v() BLARGG_PURE( ; ) + + // Go back to first file in archive + virtual blargg_err_t rewind_v() BLARGG_PURE( ; ) + + // Close archive. Called even if open_path_v() or open_v() return unsuccessfully. + virtual void close_v() BLARGG_PURE( ; ) + + // Clear any fields related to current file + virtual void clear_file_v() { } + + // Call set_info() if not already called by next_v() + virtual blargg_err_t stat_v() { return blargg_ok; } + + // Return value that allows later return to this file. Result must be >= 0. + virtual fex_pos_t tell_arc_v() const; + + // Return to previously saved position + virtual blargg_err_t seek_arc_v( fex_pos_t ); + + // One or both of the following must be overridden + + // Provide pointer to data for current file in archive + virtual blargg_err_t data_v( const void** out ); + + // Extract next n bytes + virtual blargg_err_t extract_v( void* out, int n ); + +// Implementation +public: + BLARGG_DISABLE_NOTHROW + +private: + fex_type_t const type_; + + // Archive file + blargg_vector path_; + File_Reader* reader_; + File_Reader* own_file_; + bool opened_; + + // Position in archive + fex_pos_t tell_; // only used by default implementation of tell/seek + bool done_; + + // Info for current file in archive + const char* name_; + const wchar_t* wname_; + unsigned date_; + unsigned crc32_; + int size_; + bool stat_called; + + // Current file contents + void const* data_ptr_; // NULL if not read into memory + blargg_vector own_data_; + + bool opened() const { return opened_; } + void clear_file(); + void close_(); + blargg_err_t set_path( const char* path ); + blargg_err_t rewind_file(); + blargg_err_t next_(); + + // Data_Reader overrides + // TODO: override skip_v? + virtual blargg_err_t read_v( void* out, int n ); +}; + +struct fex_type_t_ +{ + const char* extension; + File_Extractor* (*new_fex)(); + const char* name; + blargg_err_t (*init)(); // Called by fex_init(). Can be NULL. +}; + +extern const fex_type_t_ + fex_7z_type [1], + fex_gz_type [1], + fex_rar_type [1], + fex_zip_type [1], + fex_bin_type [1]; + +inline blargg_err_t File_Extractor::open_v() { return blargg_ok; } +inline blargg_err_t File_Extractor::next_v() { return blargg_ok; } +inline blargg_err_t File_Extractor::rewind_v() { return blargg_ok; } +inline void File_Extractor::close_v() { } + +// Default to Std_File_Reader for archive access +#ifndef FEX_FILE_READER + #define FEX_FILE_READER Std_File_Reader +#elif defined (FEX_FILE_READER_INCLUDE) + #include FEX_FILE_READER_INCLUDE +#endif + +#endif diff --git a/snesreader/fex/Gzip_Extractor.cpp b/snesreader/fex/Gzip_Extractor.cpp new file mode 100644 index 00000000..f169fed9 --- /dev/null +++ b/snesreader/fex/Gzip_Extractor.cpp @@ -0,0 +1,98 @@ +// File_Extractor 1.0.0. http://www.slack.net/~ant/ + +#include "Gzip_Extractor.h" + +/* Copyright (C) 2005-2009 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +// TODO: could close file once data has been read into memory + +static blargg_err_t init_gzip_file() +{ + get_crc_table(); // initialize zlib's CRC-32 tables + return blargg_ok; +} + +static File_Extractor* new_gzip() +{ + return BLARGG_NEW Gzip_Extractor; +} + +fex_type_t_ const fex_gz_type [1] = {{ + ".gz", + &new_gzip, + "gzipped file", + &init_gzip_file +}}; + +Gzip_Extractor::Gzip_Extractor() : + File_Extractor( fex_gz_type ) +{ } + +Gzip_Extractor::~Gzip_Extractor() +{ + close(); +} + +blargg_err_t Gzip_Extractor::open_path_v() +{ + // skip opening file + return open_v(); +} + +blargg_err_t Gzip_Extractor::stat_v() +{ + RETURN_ERR( open_arc_file( true ) ); + if ( !gr.opened() || gr.tell() != 0 ) + RETURN_ERR( gr.open( &arc() ) ); + + set_info( gr.remain(), 0, gr.crc32() ); + return blargg_ok; +} + +blargg_err_t Gzip_Extractor::open_v() +{ + // Remove .gz suffix + size_t len = strlen( arc_path() ); + if ( fex_has_extension( arc_path(), ".gz" ) ) + len -= 3; + + RETURN_ERR( name.resize( len + 1 ) ); + memcpy( name.begin(), arc_path(), name.size() ); + name [name.size() - 1] = '\0'; + + set_name( name.begin() ); + return blargg_ok; +} + +void Gzip_Extractor::close_v() +{ + name.clear(); + gr.close(); +} + +blargg_err_t Gzip_Extractor::next_v() +{ + return blargg_ok; +} + +blargg_err_t Gzip_Extractor::rewind_v() +{ + set_name( name.begin() ); + return blargg_ok; +} + +blargg_err_t Gzip_Extractor::extract_v( void* p, int n ) +{ + return gr.read( p, n ); +} diff --git a/snesreader/fex/Gzip_Extractor.h b/snesreader/fex/Gzip_Extractor.h new file mode 100644 index 00000000..814dc9b3 --- /dev/null +++ b/snesreader/fex/Gzip_Extractor.h @@ -0,0 +1,34 @@ +// Presents a gzipped file as an "archive" of just that file. +// Also handles non-gzipped files. + +// File_Extractor 1.0.0 +#ifndef GZIP_EXTRACTOR_H +#define GZIP_EXTRACTOR_H + +#include "File_Extractor.h" +#include "Gzip_Reader.h" + +class Gzip_Extractor : public File_Extractor { +public: + Gzip_Extractor(); + virtual ~Gzip_Extractor(); + +protected: + virtual blargg_err_t open_path_v(); + virtual blargg_err_t open_v(); + virtual void close_v(); + + virtual blargg_err_t next_v(); + virtual blargg_err_t rewind_v(); + + virtual blargg_err_t stat_v(); + virtual blargg_err_t extract_v( void*, int ); + +private: + Gzip_Reader gr; + blargg_vector name; + + void set_info_(); +}; + +#endif diff --git a/snesreader/fex/Gzip_Reader.cpp b/snesreader/fex/Gzip_Reader.cpp new file mode 100644 index 00000000..2aad302c --- /dev/null +++ b/snesreader/fex/Gzip_Reader.cpp @@ -0,0 +1,85 @@ +// File_Extractor 1.0.0. http://www.slack.net/~ant/ + +#include "Gzip_Reader.h" + +#include "blargg_endian.h" + +/* Copyright (C) 2006-2009 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +Gzip_Reader::Gzip_Reader() +{ + close(); +} + +Gzip_Reader::~Gzip_Reader() +{ } + +static blargg_err_t gzip_reader_read( void* file, void* out, int* count ) +{ + return STATIC_CAST(File_Reader*,file)->read_avail( out, count ); +} + +blargg_err_t Gzip_Reader::calc_size() +{ + size_ = in->size(); + crc32_ = 0; + if ( inflater.deflated() ) + { + byte trailer [8]; + int old_pos = in->tell(); + RETURN_ERR( in->seek( size_ - sizeof trailer ) ); + RETURN_ERR( in->read( trailer, sizeof trailer ) ); + RETURN_ERR( in->seek( old_pos ) ); + crc32_ = get_le32( trailer + 0 ); + + unsigned n = get_le32( trailer + 4 ); + if ( n > INT_MAX ) + return BLARGG_ERR( BLARGG_ERR_FILE_FEATURE, "gzip larger than 2GB" ); + + size_ = n; + } + return blargg_ok; +} + +blargg_err_t Gzip_Reader::open( File_Reader* new_in ) +{ + close(); + + in = new_in; + RETURN_ERR( in->seek( 0 ) ); + RETURN_ERR( inflater.begin( gzip_reader_read, new_in ) ); + RETURN_ERR( inflater.set_mode( inflater.mode_auto ) ); + RETURN_ERR( calc_size() ); + set_remain( size_ ); + + return blargg_ok; +} + +void Gzip_Reader::close() +{ + in = NULL; + inflater.end(); +} + +blargg_err_t Gzip_Reader::read_v( void* out, int count ) +{ + assert( in ); + int actual = count; + RETURN_ERR( inflater.read( out, &actual ) ); + + if ( actual != count ) + return blargg_err_file_corrupt; + + return blargg_ok; +} diff --git a/snesreader/fex/Gzip_Reader.h b/snesreader/fex/Gzip_Reader.h new file mode 100644 index 00000000..a9b2d6a9 --- /dev/null +++ b/snesreader/fex/Gzip_Reader.h @@ -0,0 +1,46 @@ +// Transparently decompresses gzip files, as well as uncompressed + +// File_Extractor 1.0.0 +#ifndef GZIP_READER_H +#define GZIP_READER_H + +#include "Data_Reader.h" +#include "Zlib_Inflater.h" + +class Gzip_Reader : public Data_Reader { +public: + // Keeps pointer to reader until close(). If + blargg_err_t open( File_Reader* ); + + // True if file is open + bool opened() const { return in != NULL; } + + // Frees memory + void close(); + + // True if file is compressed + bool deflated() const { return inflater.deflated(); } + + // CRC-32 of data, of 0 if unavailable + unsigned int crc32() const { return crc32_; } + + // Number of bytes read since opening + int tell() const { return size_ - remain(); } + +public: + Gzip_Reader(); + virtual ~Gzip_Reader(); + +protected: + virtual blargg_err_t read_v( void*, int ); + +private: + File_Reader* in; + unsigned crc32_; + int size_; + Zlib_Inflater inflater; + + blargg_err_t calc_size(); +}; + +#endif diff --git a/snesreader/fex/Rar_Extractor.cpp b/snesreader/fex/Rar_Extractor.cpp new file mode 100644 index 00000000..afade7fa --- /dev/null +++ b/snesreader/fex/Rar_Extractor.cpp @@ -0,0 +1,197 @@ +// File_Extractor 1.0.0. http://www.slack.net/~ant/ + +#include "blargg_common.h" + +#if FEX_ENABLE_RAR + +#include "Rar_Extractor.h" + +/* Copyright (C) 2009 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +static blargg_err_t init_rar() +{ + unrar_init(); + return blargg_ok; +} + +static File_Extractor* new_rar() +{ + return BLARGG_NEW Rar_Extractor; +} + +fex_type_t_ const fex_rar_type [1] = {{ + ".rar", + &new_rar, + "RAR archive", + &init_rar +}}; + +blargg_err_t Rar_Extractor::convert_err( unrar_err_t err ) +{ + blargg_err_t reader_err = reader.err; + reader.err = blargg_ok; + if ( reader_err ) + check( err == unrar_next_err ); + + switch ( err ) + { + case unrar_ok: return blargg_ok; + case unrar_err_memory: return blargg_err_memory; + case unrar_err_open: return blargg_err_file_read; + case unrar_err_not_arc: return blargg_err_file_type; + case unrar_err_corrupt: return blargg_err_file_corrupt; + case unrar_err_io: return blargg_err_file_io; + case unrar_err_arc_eof: return blargg_err_internal; + case unrar_err_encrypted: return BLARGG_ERR( BLARGG_ERR_FILE_FEATURE, "RAR encryption not supported" ); + case unrar_err_segmented: return BLARGG_ERR( BLARGG_ERR_FILE_FEATURE, "RAR segmentation not supported" ); + case unrar_err_huge: return BLARGG_ERR( BLARGG_ERR_FILE_FEATURE, "Huge RAR files not supported" ); + case unrar_err_old_algo: return BLARGG_ERR( BLARGG_ERR_FILE_FEATURE, "Old RAR compression not supported" ); + case unrar_err_new_algo: return BLARGG_ERR( BLARGG_ERR_FILE_FEATURE, "RAR uses unknown newer compression" ); + case unrar_next_err: break; + default: + check( false ); // unhandled RAR error + } + + if ( reader_err ) + return reader_err; + + check( false ); + return BLARGG_ERR( BLARGG_ERR_INTERNAL, "RAR archive" ); +} + +static inline unrar_err_t handle_err( Rar_Extractor::read_callback_t* h, blargg_err_t err ) +{ + if ( !err ) + return unrar_ok; + + h->err = err; + return unrar_next_err; +} + +extern "C" +{ + static unrar_err_t my_unrar_read( void* data, void* out, int* count, unrar_pos_t pos ) + { + // TODO: 64-bit file support + + Rar_Extractor::read_callback_t* h = STATIC_CAST(Rar_Extractor::read_callback_t*,data); + if ( h->pos != pos ) + { + blargg_err_t err = h->in->seek( pos ); + if ( err ) + return handle_err( h, err ); + + h->pos = pos; + } + + blargg_err_t err = h->in->read_avail( out, count ); + if ( err ) + return handle_err( h, err ); + + h->pos += *count; + + return unrar_ok; + } +} + +Rar_Extractor::Rar_Extractor() : + File_Extractor( fex_rar_type ) +{ + unrar = NULL; +} + +Rar_Extractor::~Rar_Extractor() +{ + close(); +} + +blargg_err_t Rar_Extractor::open_v() +{ + reader.pos = 0; + reader.in = &arc(); + reader.err = blargg_ok; + + RETURN_ERR( arc().seek( 0 ) ); + RETURN_ERR( convert_err( unrar_open_custom( &unrar, &my_unrar_read, &reader ) ) ); + return skip_unextractables(); +} + +void Rar_Extractor::close_v() +{ + unrar_close( unrar ); + + unrar = NULL; + reader.in = NULL; +} + +blargg_err_t Rar_Extractor::skip_unextractables() +{ + while ( !unrar_done( unrar ) && unrar_try_extract( unrar ) ) + RETURN_ERR( next_raw() ); + + if ( !unrar_done( unrar ) ) + { + unrar_info_t const* info = unrar_info( unrar ); + + set_name( info->name, (info->name_w && *info->name_w) ? info->name_w : NULL ); + set_info( info->size, info->dos_date, (info->is_crc32 ? info->crc : 0) ); + } + + return blargg_ok; +} + +blargg_err_t Rar_Extractor::next_raw() +{ + return convert_err( unrar_next( unrar ) ); +} + +blargg_err_t Rar_Extractor::next_v() +{ + RETURN_ERR( next_raw() ); + return skip_unextractables(); +} + +blargg_err_t Rar_Extractor::rewind_v() +{ + RETURN_ERR( convert_err( unrar_rewind( unrar ) ) ); + return skip_unextractables(); +} + +fex_pos_t Rar_Extractor::tell_arc_v() const +{ + return unrar_tell( unrar ); +} + +blargg_err_t Rar_Extractor::seek_arc_v( fex_pos_t pos ) +{ + RETURN_ERR( convert_err( unrar_seek( unrar, pos ) ) ); + return skip_unextractables(); +} + +blargg_err_t Rar_Extractor::data_v( void const** out ) +{ + return convert_err( unrar_extract_mem( unrar, out ) ); +} + +blargg_err_t Rar_Extractor::extract_v( void* out, int count ) +{ + // We can read entire file directly into user buffer + if ( count == size() ) + return convert_err( unrar_extract( unrar, out, count ) ); + + // This will call data_v() and copy from that buffer for us + return File_Extractor::extract_v( out, count ); +} + +#endif diff --git a/snesreader/fex/Rar_Extractor.h b/snesreader/fex/Rar_Extractor.h new file mode 100644 index 00000000..9a74dea3 --- /dev/null +++ b/snesreader/fex/Rar_Extractor.h @@ -0,0 +1,43 @@ +// RAR archive extractor + +// File_Extractor 1.0.0 +#ifndef RAR_EXTRACTOR_H +#define RAR_EXTRACTOR_H + +#include "File_Extractor.h" +#include "unrar/unrar.h" + +class Rar_Extractor : public File_Extractor { +public: + Rar_Extractor(); + virtual ~Rar_Extractor(); + + struct read_callback_t + { + const char* err; + int pos; + File_Reader* in; + }; + +protected: + virtual blargg_err_t open_v(); + virtual void close_v(); + + virtual blargg_err_t next_v(); + virtual blargg_err_t rewind_v(); + virtual fex_pos_t tell_arc_v() const; + virtual blargg_err_t seek_arc_v( fex_pos_t ); + + virtual blargg_err_t data_v( void const** ); + virtual blargg_err_t extract_v( void*, int ); + +private: + unrar_t* unrar; + read_callback_t reader; + + blargg_err_t convert_err( unrar_err_t ); + blargg_err_t skip_unextractables(); + blargg_err_t next_raw(); +}; + +#endif diff --git a/snesreader/fex/Zip7_Extractor.cpp b/snesreader/fex/Zip7_Extractor.cpp new file mode 100644 index 00000000..1803a71c --- /dev/null +++ b/snesreader/fex/Zip7_Extractor.cpp @@ -0,0 +1,252 @@ +// File_Extractor 1.0.0. http://www.slack.net/~ant/ + +#include "Zip7_Extractor.h" + +#include "7z_C/7zExtract.h" +#include "7z_C/7zAlloc.h" +#include "7z_C/7zCrc.h" + +/* Copyright (C) 2005-2009 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +static ISzAlloc zip7_alloc = { SzAlloc, SzFree }; +static ISzAlloc zip7_alloc_temp = { SzAllocTemp, SzFreeTemp }; + +struct Zip7_Extractor_Impl : + ISeekInStream +{ + CLookToRead look; + CSzArEx db; + + // SzExtract state + UInt32 block_index; + Byte* buf; + size_t buf_size; + + File_Reader* in; + const char* in_err; +}; + +extern "C" +{ + // 7-zip callbacks pass an ISeekInStream* for data, so we must cast it + // back to ISeekInStream* FIRST, then cast to our Impl structure + + static SRes zip7_read_( void* vstream, void* out, size_t* size ) + { + assert( out && size ); + ISeekInStream* stream = STATIC_CAST(ISeekInStream*,vstream); + Zip7_Extractor_Impl* impl = STATIC_CAST(Zip7_Extractor_Impl*,stream); + + long lsize = *size; + blargg_err_t err = impl->in->read_avail( out, &lsize ); + if ( err ) + { + *size = 0; + impl->in_err = err; + return SZ_ERROR_READ; + } + + *size = lsize; + return SZ_OK; + } + + static SRes zip7_seek_( void* vstream, Int64* pos, ESzSeek mode ) + { + ISeekInStream* stream = STATIC_CAST(ISeekInStream*,vstream); + Zip7_Extractor_Impl* impl = STATIC_CAST(Zip7_Extractor_Impl*,stream); + + assert( mode != SZ_SEEK_CUR ); // never used + + if ( mode == SZ_SEEK_END ) + { + assert( *pos == 0 ); // only used to find file length + *pos = impl->in->size(); + return SZ_OK; + } + + assert( mode == SZ_SEEK_SET ); + blargg_err_t err = impl->in->seek( *pos ); + if ( err ) + { + // don't set in_err in this case, since it might be benign + if ( err == blargg_err_file_eof ) + return SZ_ERROR_INPUT_EOF; + + impl->in_err = err; + return SZ_ERROR_READ; + } + + return SZ_OK; + } +} + +blargg_err_t Zip7_Extractor::zip7_err( int err ) +{ + // TODO: ignore in_err in some cases? unsure about which error to use + blargg_err_t in_err = impl->in_err; + impl->in_err = NULL; + if ( in_err ) + { + check( err != SZ_OK ); + return in_err; + } + + switch ( err ) + { + case SZ_OK: return blargg_ok; + case SZ_ERROR_MEM: return blargg_err_memory; + case SZ_ERROR_READ: return blargg_err_file_io; + case SZ_ERROR_CRC: + case SZ_ERROR_DATA: + case SZ_ERROR_INPUT_EOF: + case SZ_ERROR_ARCHIVE: return blargg_err_file_corrupt; + case SZ_ERROR_UNSUPPORTED: return blargg_err_file_feature; + case SZ_ERROR_NO_ARCHIVE: return blargg_err_file_type; + } + + return blargg_err_generic; +} + +static blargg_err_t init_7z() +{ + static bool inited; + if ( !inited ) + { + inited = true; + CrcGenerateTable(); + } + return blargg_ok; +} + +static File_Extractor* new_7z() +{ + return BLARGG_NEW Zip7_Extractor; +} + +fex_type_t_ const fex_7z_type [1] = {{ + ".7z", + &new_7z, + "7-zip archive", + &init_7z +}}; + +Zip7_Extractor::Zip7_Extractor() : + File_Extractor( fex_7z_type ) +{ + impl = NULL; +} + +Zip7_Extractor::~Zip7_Extractor() +{ + close(); +} + +blargg_err_t Zip7_Extractor::open_v() +{ + RETURN_ERR( init_7z() ); + + if ( !impl ) + { + impl = (Zip7_Extractor_Impl*) malloc( sizeof *impl ); + CHECK_ALLOC( impl ); + } + + impl->in = &arc(); + impl->block_index = (UInt32) -1; + impl->buf = NULL; + impl->buf_size = 0; + + LookToRead_CreateVTable( &impl->look, false ); + impl->ISeekInStream::Read = zip7_read_; + impl->ISeekInStream::Seek = zip7_seek_; + impl->look.realStream = impl; + LookToRead_Init( &impl->look ); + + SzArEx_Init( &impl->db ); + + impl->in_err = NULL; + RETURN_ERR( zip7_err( SzArEx_Open( &impl->db, &impl->look.s, + &zip7_alloc, &zip7_alloc_temp ) ) ); + + return seek_arc_v( 0 ); +} + +void Zip7_Extractor::close_v() +{ + if ( impl ) + { + if ( impl->in ) + { + impl->in = NULL; + SzArEx_Free( &impl->db, &zip7_alloc ); + } + IAlloc_Free( &zip7_alloc, impl->buf ); + free( impl ); + impl = NULL; + } +} + +blargg_err_t Zip7_Extractor::next_v() +{ + while ( ++index < (int) impl->db.db.NumFiles ) + { + CSzFileItem const& item = impl->db.db.Files [index]; + if ( !item.IsDir ) + { + // TODO: Support date. + // NTFS representation, stored as 64-bit value. + // Divide by 10000000 (ten million) to get seconds + //item.MTime.Low + (.High << 32) + // How to convert to DOS style? + + set_name( item.Name ); + set_info( item.Size, 0, (item.FileCRCDefined ? item.FileCRC : 0) ); + break; + } + } + + return blargg_ok; +} + +blargg_err_t Zip7_Extractor::rewind_v() +{ + return seek_arc_v( 0 ); +} + +fex_pos_t Zip7_Extractor::tell_arc_v() const +{ + return index; +} + +blargg_err_t Zip7_Extractor::seek_arc_v( fex_pos_t pos ) +{ + assert( 0 <= pos && pos <= (int) impl->db.db.NumFiles ); + + index = pos - 1; + return next_v(); +} + +blargg_err_t Zip7_Extractor::data_v( void const** out ) +{ + impl->in_err = NULL; + size_t offset = 0; + size_t count = 0; + RETURN_ERR( zip7_err( SzAr_Extract( &impl->db, &impl->look.s, index, + &impl->block_index, &impl->buf, &impl->buf_size, + &offset, &count, &zip7_alloc, &zip7_alloc_temp ) ) ); + assert( count == (size_t) size() ); + + *out = impl->buf + offset; + return blargg_ok; +} diff --git a/snesreader/fex/Zip7_Extractor.h b/snesreader/fex/Zip7_Extractor.h new file mode 100644 index 00000000..f658ba04 --- /dev/null +++ b/snesreader/fex/Zip7_Extractor.h @@ -0,0 +1,34 @@ +// 7-zip archive extractor + +// File_Extractor 1.0.0 +#ifndef ZIP7_EXTRACTOR_H +#define ZIP7_EXTRACTOR_H + +#include "File_Extractor.h" + +struct Zip7_Extractor_Impl; + +class Zip7_Extractor : public File_Extractor { +public: + Zip7_Extractor(); + virtual ~Zip7_Extractor(); + +protected: + virtual blargg_err_t open_v(); + virtual void close_v(); + + virtual blargg_err_t next_v(); + virtual blargg_err_t rewind_v(); + virtual fex_pos_t tell_arc_v() const; + virtual blargg_err_t seek_arc_v( fex_pos_t ); + + virtual blargg_err_t data_v( void const** out ); + +private: + Zip7_Extractor_Impl* impl; + int index; + + blargg_err_t zip7_err( int err ); +}; + +#endif diff --git a/snesreader/fex/Zip_Extractor.cpp b/snesreader/fex/Zip_Extractor.cpp new file mode 100644 index 00000000..8bcc61c3 --- /dev/null +++ b/snesreader/fex/Zip_Extractor.cpp @@ -0,0 +1,390 @@ +// File_Extractor 1.0.0. http://www.slack.net/~ant/ + +#include "Zip_Extractor.h" + +#include "blargg_endian.h" + +/* Copyright (C) 2005-2009 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +/* To avoid copying filename string from catalog, I terminate it by modifying +catalog data. This potentially requires moving the first byte of the type +of the next entry elsewhere; I move it to the first byte of made_by. Kind +of hacky, but I'd rather not have to allocate memory for a copy of it. */ + +#include "blargg_source.h" + +/* Reads this much from end of file when first opening. Only this much is +searched for the end catalog entry. If whole catalog is within this data, +nothing more needs to be read on open. */ +int const end_read_size = 8 * 1024; + +/* Reads are are made using file offset that's a multiple of this, +increasing performance. */ +int const disk_block_size = 4 * 1024; + +// Read buffer used for extracting file data +int const read_buf_size = 16 * 1024; + +struct header_t +{ + char type [4]; + byte vers [2]; + byte flags [2]; + byte method [2]; + byte date [4]; + byte crc [4]; + byte raw_size [4]; + byte size [4]; + byte filename_len [2]; + byte extra_len [2]; + char filename [2]; // [filename_len] + //char extra [extra_len]; +}; +int const header_size = 30; + +struct entry_t +{ + char type [4]; + byte made_by [2]; + byte vers [2]; + byte flags [2]; + byte method [2]; + byte date [4]; + byte crc [4]; + byte raw_size [4]; + byte size [4]; + byte filename_len [2]; + byte extra_len [2]; + byte comment_len [2]; + byte disk [2]; + byte int_attrib [2]; + byte ext_attrib [4]; + byte file_offset [4]; + char filename [2]; // [filename_len] + //char extra [extra_len]; + //char comment [comment_len]; +}; +int const entry_size = 46; + +struct end_entry_t +{ + char type [4]; + byte disk [2]; + byte first_disk [2]; + byte disk_entry_count [2]; + byte entry_count [2]; + byte dir_size [4]; + byte dir_offset [4]; + byte comment_len [2]; + char comment [2]; // [comment_len] +}; +int const end_entry_size = 22; + +static blargg_err_t init_zip() +{ + get_crc_table(); // initialize zlib's CRC-32 tables + return blargg_ok; +} + +static File_Extractor* new_zip() +{ + return BLARGG_NEW Zip_Extractor; +} + +fex_type_t_ const fex_zip_type [1] = {{ + ".zip", + &new_zip, + "ZIP archive", + &init_zip +}}; + +Zip_Extractor::Zip_Extractor() : + File_Extractor( fex_zip_type ) +{ + Zip_Extractor::clear_file_v(); + + // If these fail, structures had extra padding inserted by compiler + assert( offsetof (header_t,filename) == header_size ); + assert( offsetof (entry_t,filename) == entry_size ); + assert( offsetof (end_entry_t,comment) == end_entry_size ); +} + +Zip_Extractor::~Zip_Extractor() +{ + close(); +} + +blargg_err_t Zip_Extractor::open_path_v() +{ + RETURN_ERR( open_arc_file( true ) ); + return File_Extractor::open_path_v(); +} + +inline +void Zip_Extractor::reorder_entry_header( int offset ) +{ + catalog [offset + 0] = 0; + catalog [offset + 4] = 'P'; +} + +blargg_err_t Zip_Extractor::open_v() +{ + if ( arc().size() < end_entry_size ) + return blargg_err_file_type; + + // Read final end_read_size bytes of file + int file_pos = max( 0, arc().size() - end_read_size ); + file_pos -= file_pos % disk_block_size; + RETURN_ERR( catalog.resize( arc().size() - file_pos ) ); + RETURN_ERR( arc().seek( file_pos ) ); + RETURN_ERR( arc().read( catalog.begin(), catalog.size() ) ); + + // Find end-of-catalog entry + int end_pos = catalog.size() - end_entry_size; + while ( end_pos >= 0 && memcmp( &catalog [end_pos], "PK\5\6", 4 ) ) + end_pos--; + if ( end_pos < 0 ) + return blargg_err_file_type; + end_entry_t const& end_entry = (end_entry_t&) catalog [end_pos]; + end_pos += file_pos; + + // some idiotic zip compressors add data to end of zip without setting comment len +// check( arc().size() == end_pos + end_entry_size + get_le16( end_entry.comment_len ) ); + + // Find file offset of beginning of catalog + catalog_begin = get_le32( end_entry.dir_offset ); + int catalog_size = end_pos - catalog_begin; + if ( catalog_size < 0 ) + return blargg_err_file_corrupt; + catalog_size += end_entry_size; + + // See if catalog is entirely contained in bytes already read + int begin_offset = catalog_begin - file_pos; + if ( begin_offset >= 0 ) + memmove( catalog.begin(), &catalog [begin_offset], catalog_size ); + + RETURN_ERR( catalog.resize( catalog_size ) ); + if ( begin_offset < 0 ) + { + // Catalog begins before bytes read, so it needs to be read + RETURN_ERR( arc().seek( catalog_begin ) ); + RETURN_ERR( arc().read( catalog.begin(), catalog.size() ) ); + } + + // First entry in catalog should be a file or end of archive + if ( memcmp( catalog.begin(), "PK\1\2", 4 ) && memcmp( catalog.begin(), "PK\5\6", 4 ) ) + return blargg_err_file_type; + + reorder_entry_header( 0 ); + return rewind_v(); +} + +void Zip_Extractor::close_v() +{ + catalog.clear(); +} + +// Scanning + +inline +static bool is_normal_file( entry_t const& e, unsigned len ) +{ + int last_char = (len ? e.filename [len - 1] : '/'); + bool is_dir = (last_char == '/' || last_char == '\\'); + if ( is_dir && get_le32( e.size ) == 0 ) + return false; + check( !is_dir ); + + // Mac OS X puts meta-information in separate files with normal extensions, + // so they must be filtered out or caller will mistake them for normal files. + if ( e.made_by[1] == 3 ) + { + const char* dir = strrchr( e.filename, '/' ); + if ( dir ) + dir++; + else + dir = e.filename; + + if ( *dir == '.' ) + return false; + + if ( !strcmp( dir, "Icon\x0D" ) ) + return false; + } + + return true; +} + +blargg_err_t Zip_Extractor::update_info( bool advance_first ) +{ + while ( 1 ) + { + entry_t& e = (entry_t&) catalog [catalog_pos]; + + if ( memcmp( e.type, "\0K\1\2P", 5 ) && memcmp( e.type, "PK\1\2", 4 ) ) + { + check( !memcmp( e.type, "\0K\5\6P", 5 ) ); + break; + } + + unsigned len = get_le16( e.filename_len ); + int next_offset = catalog_pos + entry_size + len + get_le16( e.extra_len ) + + get_le16( e.comment_len ); + if ( (unsigned) next_offset > catalog.size() - end_entry_size ) + return blargg_err_file_corrupt; + + if ( catalog [next_offset] == 'P' ) + reorder_entry_header( next_offset ); + + if ( !advance_first ) + { + e.filename [len] = 0; // terminate name + + if ( is_normal_file( e, len ) ) + { + set_name( e.filename ); + set_info( get_le32( e.size ), get_le32( e.date ), get_le32( e.crc ) ); + break; + } + } + + catalog_pos = next_offset; + advance_first = false; + } + + return blargg_ok; +} + +blargg_err_t Zip_Extractor::next_v() +{ + return update_info( true ); +} + +blargg_err_t Zip_Extractor::rewind_v() +{ + return seek_arc_v( 0 ); +} + +fex_pos_t Zip_Extractor::tell_arc_v() const +{ + return catalog_pos; +} + +blargg_err_t Zip_Extractor::seek_arc_v( fex_pos_t pos ) +{ + assert( 0 <= pos && (size_t) pos <= catalog.size() - end_entry_size ); + + catalog_pos = pos; + return update_info( false ); +} + +// Reading + +void Zip_Extractor::clear_file_v() +{ + buf.end(); +} + +blargg_err_t Zip_Extractor::inflater_read( void* data, void* out, int* count ) +{ + Zip_Extractor& self = *STATIC_CAST(Zip_Extractor*,data); + + if ( *count > self.raw_remain ) + *count = self.raw_remain; + + self.raw_remain -= *count; + + return self.arc().read( out, *count ); +} + +blargg_err_t Zip_Extractor::fill_buf( int offset, int buf_size, int initial_read ) +{ + raw_remain = arc().size() - offset; + RETURN_ERR( arc().seek( offset ) ); + return buf.begin( inflater_read, this, buf_size, initial_read ); +} + +blargg_err_t Zip_Extractor::first_read( int count ) +{ + entry_t const& e = (entry_t&) catalog [catalog_pos]; + + // Determine compression + { + int method = get_le16( e.method ); + if ( (method && method != Z_DEFLATED) || get_le16( e.vers ) > 20 ) + return BLARGG_ERR( BLARGG_ERR_FILE_FEATURE, "compression method" ); + file_deflated = (method != 0); + } + + int raw_size = get_le32( e.raw_size ); + + int file_offset = get_le32( e.file_offset ); + int align = file_offset % disk_block_size; + { + // read header + int buf_size = 3 * disk_block_size - 1 + raw_size; // space for all raw data + buf_size -= buf_size % disk_block_size; + int initial_read = buf_size; + if ( !file_deflated || count < size() ) + { + buf_size = read_buf_size; + initial_read = disk_block_size * 2; + } + // TODO: avoid re-reading if buffer already has data we want? + RETURN_ERR( fill_buf( file_offset - align, buf_size, initial_read ) ); + } + header_t const& h = (header_t&) buf.data() [align]; + if ( buf.filled() < align + header_size || memcmp( h.type, "PK\3\4", 4 ) ) + return blargg_err_file_corrupt; + + // CRCs of header and file data + correct_crc = get_le32( h.crc ); + if ( !correct_crc ) + correct_crc = get_le32( e.crc ); + check( correct_crc == get_le32( e.crc ) ); // catalog CRC should match + crc = ::crc32( 0, NULL, 0 ); + + // Data offset + int data_offset = file_offset + header_size + + get_le16( h.filename_len ) + get_le16( h.extra_len ); + if ( data_offset + raw_size > catalog_begin ) + return blargg_err_file_corrupt; + + // Refill buffer if there's lots of extra data after header + int buf_offset = data_offset - file_offset + align; + if ( buf_offset > buf.filled() ) + { + // TODO: this will almost never occur, making it a good place for bugs + buf_offset = data_offset % disk_block_size; + RETURN_ERR( fill_buf( data_offset - buf_offset, read_buf_size, disk_block_size ) ); + } + + raw_remain = raw_size - (buf.filled() - buf_offset); + return buf.set_mode( (file_deflated ? buf.mode_raw_deflate : buf.mode_copy), buf_offset ); +} + +blargg_err_t Zip_Extractor::extract_v( void* out, int count ) +{ + if ( tell() == 0 ) + RETURN_ERR( first_read( count ) ); + + int actual = count; + RETURN_ERR( buf.read( out, &actual ) ); + if ( actual < count ) + return blargg_err_file_corrupt; + + crc = ::crc32( crc, (byte const*) out, count ); + if ( count == reader().remain() && crc != correct_crc ) + return blargg_err_file_corrupt; + + return blargg_ok; +} diff --git a/snesreader/fex/Zip_Extractor.h b/snesreader/fex/Zip_Extractor.h new file mode 100644 index 00000000..9742df99 --- /dev/null +++ b/snesreader/fex/Zip_Extractor.h @@ -0,0 +1,45 @@ +// ZIP archive extractor. Only supports deflation and store (no compression). + +// File_Extractor 1.0.0 +#ifndef ZIP_EXTRACTOR_H +#define ZIP_EXTRACTOR_H + +#include "File_Extractor.h" +#include "Zlib_Inflater.h" + +class Zip_Extractor : public File_Extractor { +public: + Zip_Extractor(); + virtual ~Zip_Extractor(); + +protected: + virtual blargg_err_t open_path_v(); + virtual blargg_err_t open_v(); + virtual void close_v(); + + virtual void clear_file_v(); + virtual blargg_err_t next_v(); + virtual blargg_err_t rewind_v(); + virtual fex_pos_t tell_arc_v() const; + virtual blargg_err_t seek_arc_v( fex_pos_t ); + + virtual blargg_err_t extract_v( void*, int ); + +private: + blargg_vector catalog; + int catalog_begin; // offset of first catalog entry in file (to detect corruption) + int catalog_pos; // position of current entry in catalog + int raw_remain; // bytes remaining to be read from zip file for current file + unsigned crc; // ongoing CRC of extracted bytes + unsigned correct_crc; + bool file_deflated; + Zlib_Inflater buf; + + blargg_err_t fill_buf( int offset, int buf_size, int initial_read ); + blargg_err_t update_info( bool advance_first ); + blargg_err_t first_read( int count ); + void reorder_entry_header( int offset ); + static blargg_err_t inflater_read( void* data, void* out, int* count ); +}; + +#endif diff --git a/snesreader/fex/Zlib_Inflater.cpp b/snesreader/fex/Zlib_Inflater.cpp new file mode 100644 index 00000000..8d31b514 --- /dev/null +++ b/snesreader/fex/Zlib_Inflater.cpp @@ -0,0 +1,257 @@ +// File_Extractor 1.0.0. http://www.slack.net/~ant/ + +#include "Zlib_Inflater.h" + +/* Copyright (C) 2006-2009 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +int const block_size = 4096; + +static const char* get_zlib_err( int code ) +{ + assert( code != Z_OK ); + switch ( code ) + { + case Z_MEM_ERROR: return blargg_err_memory; + case Z_DATA_ERROR: return blargg_err_file_corrupt; + // TODO: handle more error codes + } + + const char* str = zError( code ); + if ( !str ) + str = BLARGG_ERR( BLARGG_ERR_GENERIC, "problem unzipping data" ); + + return str; +} + +void Zlib_Inflater::end() +{ + if ( deflated_ ) + { + deflated_ = false; + if ( inflateEnd( &zbuf ) ) + check( false ); + } + buf.clear(); + + static z_stream const empty = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; + memcpy( &zbuf, &empty, sizeof zbuf ); +} + +Zlib_Inflater::Zlib_Inflater() +{ + deflated_ = false; + end(); // initialize things +} + +Zlib_Inflater::~Zlib_Inflater() +{ + end(); +} + +blargg_err_t Zlib_Inflater::fill_buf( int count ) +{ + byte* out = buf.end() - count; + RETURN_ERR( callback( user_data, out, &count ) ); + zbuf.avail_in = count; + zbuf.next_in = out; + return blargg_ok; +} + +blargg_err_t Zlib_Inflater::begin( callback_t new_callback, void* new_user_data, + int new_buf_size, int initial_read ) +{ + callback = new_callback; + user_data = new_user_data; + + end(); + + // TODO: decide whether using different size on alloc failure is a good idea + //RETURN_ERR( buf.resize( new_buf_size ? new_buf_size : 4 * block_size ) ); + if ( new_buf_size && buf.resize( new_buf_size ) ) + { + ACK_FAILURE(); + new_buf_size = 0; + } + + if ( !new_buf_size ) + { + RETURN_ERR( buf.resize( 4 * block_size ) ); + initial_read = 0; + } + + // Fill buffer with some data, less than normal buffer size since caller might + // just be examining beginning of file. + return fill_buf( initial_read ? initial_read : block_size ); +} + +blargg_err_t Zlib_Inflater::set_mode( mode_t mode, int data_offset ) +{ + zbuf.next_in += data_offset; + zbuf.avail_in -= data_offset; + + if ( mode == mode_auto ) + { + // examine buffer for gzip header + mode = mode_copy; + unsigned const min_gzip_size = 2 + 8 + 8; + if ( zbuf.avail_in >= min_gzip_size && + zbuf.next_in [0] == 0x1F && zbuf.next_in [1] == 0x8B ) + mode = mode_ungz; + } + + if ( mode != mode_copy ) + { + int wb = MAX_WBITS + 16; // have zlib handle gzip header + if ( mode == mode_raw_deflate ) + wb = -MAX_WBITS; + + int zerr = inflateInit2( &zbuf, wb ); + if ( zerr ) + { + zbuf.next_in = NULL; + return get_zlib_err( zerr ); + } + + deflated_ = true; + } + return blargg_ok; +} + +/* +// Reads/inflates entire stream. All input must be in buffer, and count must be total +// of all output. +blargg_err_t read_all( void* out, int count ); + + +// zlib automatically applies this optimization (uses inflateFast) +// TODO: remove +blargg_err_t Zlib_Inflater::read_all( void* out, int count ) +{ + if ( deflated_ ) + { + zbuf.next_out = (Bytef*) out; + zbuf.avail_out = count; + + int err = inflate( &zbuf, Z_FINISH ); + + if ( zbuf.avail_out || err != Z_STREAM_END ) + return blargg_err_file_corrupt; + } + else + { + if ( zbuf.avail_in < count ) + return blargg_err_file_corrupt; + + memcpy( out, zbuf.next_in, count ); + + zbuf.next_in += count; + zbuf.avail_in -= count; + } + + return blargg_ok; +} +*/ + +blargg_err_t Zlib_Inflater::read( void* out, int* count_io ) +{ + int remain = *count_io; + if ( remain && zbuf.next_in ) + { + if ( deflated_ ) + { + zbuf.next_out = (Bytef*) out; + zbuf.avail_out = remain; + + while ( 1 ) + { + uInt old_avail_in = zbuf.avail_in; + int err = inflate( &zbuf, Z_NO_FLUSH ); + if ( err == Z_STREAM_END ) + { + remain = zbuf.avail_out; + end(); + break; // no more data to inflate + } + + if ( err && (err != Z_BUF_ERROR || old_avail_in) ) + return get_zlib_err( err ); + + if ( !zbuf.avail_out ) + { + remain = 0; + break; // requested number of bytes inflated + } + + if ( zbuf.avail_in ) + { + // inflate() should never leave input if there's still space for output + check( false ); + return blargg_err_file_corrupt; + } + + RETURN_ERR( fill_buf( buf.size() ) ); + if ( !zbuf.avail_in ) + return blargg_err_file_corrupt; // stream didn't end but there's no more data + } + } + else + { + while ( 1 ) + { + // copy buffered data + if ( zbuf.avail_in ) + { + long count = zbuf.avail_in; + if ( count > remain ) + count = remain; + memcpy( out, zbuf.next_in, count ); + zbuf.total_out += count; + out = (char*) out + count; + remain -= count; + zbuf.next_in += count; + zbuf.avail_in -= count; + } + + if ( !zbuf.avail_in && zbuf.next_in < buf.end() ) + { + end(); + break; + } + + // read large request directly + if ( remain + zbuf.total_out % block_size >= buf.size() ) + { + int count = remain; + RETURN_ERR( callback( user_data, out, &count ) ); + zbuf.total_out += count; + out = (char*) out + count; + remain -= count; + + if ( remain ) + { + end(); + break; + } + } + + if ( !remain ) + break; + + RETURN_ERR( fill_buf( buf.size() - zbuf.total_out % block_size ) ); + } + } + } + *count_io -= remain; + return blargg_ok; +} diff --git a/snesreader/fex/Zlib_Inflater.h b/snesreader/fex/Zlib_Inflater.h new file mode 100644 index 00000000..8a49ff52 --- /dev/null +++ b/snesreader/fex/Zlib_Inflater.h @@ -0,0 +1,70 @@ +// Simplifies use of zlib for inflating data + +// File_Extractor 1.0.0 +#ifndef ZLIB_INFLATER_H +#define ZLIB_INFLATER_H + +#include "blargg_common.h" +#include "Data_Reader.h" +#include "zlib/zlib.h" + +class Zlib_Inflater { +public: + + // Reads at most min(*count,bytes_until_eof()) bytes into *out and set *count + // to that number, or returns error if that many can't be read. + typedef blargg_err_t (*callback_t)( void* user_data, void* out, int* count ); + + // Begins by setting callback and filling buffer. Default buffer is 16K and + // filled to 4K, or specify buf_size and initial_read for custom buffer size + // and how much to read initially. + blargg_err_t begin( callback_t, void* user_data, + int buf_size = 0, int initial_read = 0 ); + + // Data read into buffer by begin() + const unsigned char* data() const { return zbuf.next_in; } + int filled() const { return zbuf.avail_in; } + + // Begins inflation using specified mode. Using mode_auto selects between + // mode_copy and mode_ungz by examining first two bytes of buffer. Use + // buf_offset to specify where data begins in buffer, in case there is + // header data that should be skipped. + enum mode_t { mode_copy, mode_ungz, mode_raw_deflate, mode_auto }; + blargg_err_t set_mode( mode_t, int buf_offset = 0 ); + + // True if set_mode() has been called with mode_ungz or mode_raw_deflate + bool deflated() const { return deflated_; } + + // Reads/inflates at most *count_io bytes into *out and sets *count_io to actual + // number of bytes read (less than requested if end of data was reached). + // Buffers source data internally, even in copy mode, so input file can be + // unbuffered without sacrificing performance. + blargg_err_t read( void* out, int* count_io ); + + // Total number of bytes read since begin() + int tell() const { return zbuf.total_out; } + + // Ends inflation and frees memory + void end(); + +private: + // noncopyable + Zlib_Inflater( const Zlib_Inflater& ); + Zlib_Inflater& operator = ( const Zlib_Inflater& ); + +// Implementation +public: + Zlib_Inflater(); + ~Zlib_Inflater(); + +private: + z_stream_s zbuf; + blargg_vector buf; + bool deflated_; + callback_t callback; + void* user_data; + + blargg_err_t fill_buf( int count ); +}; + +#endif diff --git a/snesreader/fex/blargg_common.cpp b/snesreader/fex/blargg_common.cpp new file mode 100644 index 00000000..9f3e9ebd --- /dev/null +++ b/snesreader/fex/blargg_common.cpp @@ -0,0 +1,51 @@ +// File_Extractor 1.0.0. http://www.slack.net/~ant/ + +#include "blargg_common.h" + +/* Copyright (C) 2008-2009 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +void blargg_vector_::init() +{ + begin_ = NULL; + size_ = 0; +} + +void blargg_vector_::clear() +{ + void* p = begin_; + begin_ = NULL; + size_ = 0; + free( p ); +} + +blargg_err_t blargg_vector_::resize_( size_t n, size_t elem_size ) +{ + if ( n != size_ ) + { + if ( n == 0 ) + { + // Simpler to handle explicitly. Realloc will handle a size of 0, + // but then we have to avoid raising an error for a NULL return. + clear(); + } + else + { + void* p = realloc( begin_, n * elem_size ); + CHECK_ALLOC( p ); + begin_ = p; + size_ = n; + } + } + return blargg_ok; +} diff --git a/snesreader/fex/blargg_common.h b/snesreader/fex/blargg_common.h new file mode 100644 index 00000000..a11579fd --- /dev/null +++ b/snesreader/fex/blargg_common.h @@ -0,0 +1,206 @@ +// Sets up common environment for Shay Green's libraries. +// To change configuration options, modify blargg_config.h, not this file. + +// File_Extractor 1.0.0 +#ifndef BLARGG_COMMON_H +#define BLARGG_COMMON_H + +#include +#include +#include + +typedef const char* blargg_err_t; // 0 on success, otherwise error string + +// Success; no error +int const blargg_ok = 0; + +// BLARGG_RESTRICT: equivalent to C99's restrict, where supported +#if __GNUC__ >= 3 || _MSC_VER >= 1100 + #define BLARGG_RESTRICT __restrict +#else + #define BLARGG_RESTRICT +#endif + +#if __cplusplus >= 199711 + #define BLARGG_MUTABLE mutable +#else + #define BLARGG_MUTABLE +#endif + +/* BLARGG_4CHAR('a','b','c','d') = 'abcd' (four character integer constant). +I don't just use 'abcd' because that's implementation-dependent. */ +#define BLARGG_4CHAR( a, b, c, d ) \ + ((a&0xFF)*0x1000000 + (b&0xFF)*0x10000 + (c&0xFF)*0x100 + (d&0xFF)) + +/* BLARGG_STATIC_ASSERT( expr ): Generates compile error if expr is 0. +Can be used at file, function, or class scope. */ +#ifdef _MSC_VER + // MSVC6 (_MSC_VER < 1300) __LINE__ fails when /Zl is specified + #define BLARGG_STATIC_ASSERT( expr ) \ + void blargg_failed_( int (*arg) [2 / (int) !!(expr) - 1] ) +#else + // Others fail when declaring same function multiple times in class, + // so differentiate them by line + #define BLARGG_STATIC_ASSERT( expr ) \ + void blargg_failed_( int (*arg) [2 / !!(expr) - 1] [__LINE__] ) +#endif + +/* Pure virtual functions cause a vtable entry to a "called pure virtual" +error handler, requiring linkage to the C++ runtime library. This macro is +used in place of the "= 0", and simply expands to its argument. During +development, it expands to "= 0", allowing detection of missing overrides. */ +#define BLARGG_PURE( def ) def + +/* My code depends on ASCII anywhere a character or string constant is +compared with data read from a file, and anywhere file data is read and +treated as a string. */ +#if '\n'!=0x0A || ' '!=0x20 || '0'!=0x30 || 'A'!=0x41 || 'a'!=0x61 + #error "ASCII character set required" +#endif + +/* My code depends on int being at least 32 bits. Almost everything these days +uses at least 32-bit ints, so it's hard to even find a system with 16-bit ints +to test with. The issue can't be gotten around by using a suitable blargg_int +everywhere either, because int is often converted to implicitly when doing +arithmetic on smaller types. */ +#if UINT_MAX < 0xFFFFFFFF + #error "int must be at least 32 bits" +#endif + +// In case compiler doesn't support these properly. Used rarely. +#define STATIC_CAST(T,expr) static_cast (expr) +#define CONST_CAST( T,expr) const_cast (expr) + +// User configuration can override the above macros if necessary +#include "blargg_config.h" + +/* BLARGG_DEPRECATED [_TEXT] for any declarations/text to be removed in a +future version. In GCC, we can let the compiler warn. In other compilers, +we strip it out unless BLARGG_LEGACY is true. */ +#if BLARGG_LEGACY + // Allow old client code to work without warnings + #define BLARGG_DEPRECATED_TEXT( text ) text + #define BLARGG_DEPRECATED( text ) text +#elif __GNUC__ >= 4 + // In GCC, we can mark declarations and let the compiler warn + #define BLARGG_DEPRECATED_TEXT( text ) text + #define BLARGG_DEPRECATED( text ) __attribute__ ((deprecated)) text +#else + // By default, deprecated items are removed, to avoid use in new code + #define BLARGG_DEPRECATED_TEXT( text ) + #define BLARGG_DEPRECATED( text ) +#endif + +/* BOOST::int8_t, BOOST::int32_t, etc. +I used BOOST since I originally was going to allow use of the boost library +for prividing the definitions. If I'm defining them, they must be scoped or +else they could conflict with the standard ones at global scope. Even if +HAVE_STDINT_H isn't defined, I can't assume the typedefs won't exist at +global scope already. */ +#if defined (HAVE_STDINT_H) || \ + UCHAR_MAX != 0xFF || USHRT_MAX != 0xFFFF || UINT_MAX != 0xFFFFFFFF + #include + #define BOOST +#else + struct BOOST + { + typedef signed char int8_t; + typedef unsigned char uint8_t; + typedef short int16_t; + typedef unsigned short uint16_t; + typedef int int32_t; + typedef unsigned int uint32_t; + }; +#endif + +/* My code is not written with exceptions in mind, so either uses new (nothrow) +OR overrides operator new in my classes. The former is best since clients +creating objects will get standard exceptions on failure, but that causes it +to require the standard C++ library. So, when the client is using the C +interface, I override operator new to use malloc. */ + +// BLARGG_DISABLE_NOTHROW is put inside classes +#ifndef BLARGG_DISABLE_NOTHROW + // throw spec mandatory in ISO C++ if NULL can be returned + #if __cplusplus >= 199711 || __GNUC__ >= 3 || _MSC_VER >= 1300 + #define BLARGG_THROWS_NOTHING throw () + #else + #define BLARGG_THROWS_NOTHING + #endif + + #define BLARGG_DISABLE_NOTHROW \ + void* operator new ( size_t s ) BLARGG_THROWS_NOTHING { return malloc( s ); }\ + void operator delete( void* p ) BLARGG_THROWS_NOTHING { free( p ); } + + #define BLARGG_NEW new +#else + // BLARGG_NEW is used in place of new in library code + #include + #define BLARGG_NEW new (std::nothrow) +#endif + + class blargg_vector_ { + protected: + void* begin_; + size_t size_; + void init(); + blargg_err_t resize_( size_t n, size_t elem_size ); + public: + size_t size() const { return size_; } + void clear(); + }; + +// Very lightweight vector for POD types (no constructor/destructor) +template +class blargg_vector : public blargg_vector_ { + union T_must_be_pod { T t; }; // fails if T is not POD +public: + blargg_vector() { init(); } + ~blargg_vector() { clear(); } + + blargg_err_t resize( size_t n ) { return resize_( n, sizeof (T) ); } + + T* begin() { return static_cast (begin_); } + const T* begin() const { return static_cast (begin_); } + + T* end() { return static_cast (begin_) + size_; } + const T* end() const { return static_cast (begin_) + size_; } + + T& operator [] ( size_t n ) + { + assert( n < size_ ); + return static_cast (begin_) [n]; + } + + const T& operator [] ( size_t n ) const + { + assert( n < size_ ); + return static_cast (begin_) [n]; + } +}; + +// Callback function with user data. +// blargg_callback set_callback; // for user, this acts like... +// void set_callback( T func, void* user_data = NULL ); // ...this +// To call function, do set_callback.f( .. set_callback.data ... ); +template +struct blargg_callback +{ + T f; + void* data; + blargg_callback() { f = NULL; } + void operator () ( T callback, void* user_data = NULL ) { f = callback; data = user_data; } +}; + +#ifndef _WIN32 + // Not supported on any other platforms + #undef BLARGG_UTF8_PATHS +#endif + +BLARGG_DEPRECATED( typedef signed int blargg_long; ) +BLARGG_DEPRECATED( typedef unsigned int blargg_ulong; ) +#if BLARGG_LEGACY + #define BOOST_STATIC_ASSERT BLARGG_STATIC_ASSERT +#endif + +#endif diff --git a/snesreader/fex/blargg_config.h b/snesreader/fex/blargg_config.h new file mode 100644 index 00000000..eb862609 --- /dev/null +++ b/snesreader/fex/blargg_config.h @@ -0,0 +1,34 @@ +// Library configuration. Modify this file as necessary. + +// File_Extractor 1.0.0 +#ifndef BLARGG_CONFIG_H +#define BLARGG_CONFIG_H + +// Uncomment a #define line below to have effect described. + +// Enable RAR archive support. Doing so adds extra licensing restrictions +// to this library (see unrar/readme.txt for more information). +#define FEX_ENABLE_RAR 1 + +// Accept file paths encoded as UTF-8. Currently only affects Windows, +// as Unix/Linux/Mac OS X already use UTF-8 paths. +#define BLARGG_UTF8_PATHS 1 + +// Enable support for as building DLL on Windows. +//#define BLARGG_BUILD_DLL 1 + +// Support only the listed archive types. Remove any you don't need. +/* +#define FEX_TYPE_LIST \ + fex_7z_type,\ + fex_gz_type,\ + fex_rar_type,\ + fex_zip_type, +*/ + +// Use standard config.h if present +#ifdef HAVE_CONFIG_H + #include "config.h" +#endif + +#endif diff --git a/snesreader/fex/blargg_endian.h b/snesreader/fex/blargg_endian.h new file mode 100644 index 00000000..c32c12f5 --- /dev/null +++ b/snesreader/fex/blargg_endian.h @@ -0,0 +1,185 @@ +// CPU Byte Order Utilities + +// File_Extractor 1.0.0 +#ifndef BLARGG_ENDIAN_H +#define BLARGG_ENDIAN_H + +#include "blargg_common.h" + +// BLARGG_CPU_CISC: Defined if CPU has very few general-purpose registers (< 16) +#if defined (__i386__) || defined (__x86_64__) || defined (_M_IX86) || defined (_M_X64) + #define BLARGG_CPU_X86 1 + #define BLARGG_CPU_CISC 1 +#endif + +#if defined (__powerpc__) || defined (__ppc__) || defined (__ppc64__) || \ + defined (__POWERPC__) || defined (__powerc) + #define BLARGG_CPU_POWERPC 1 + #define BLARGG_CPU_RISC 1 +#endif + +// BLARGG_BIG_ENDIAN, BLARGG_LITTLE_ENDIAN: Determined automatically, otherwise only +// one may be #defined to 1. Only needed if something actually depends on byte order. +#if !defined (BLARGG_BIG_ENDIAN) && !defined (BLARGG_LITTLE_ENDIAN) +#ifdef __GLIBC__ + // GCC handles this for us + #include + #if __BYTE_ORDER == __LITTLE_ENDIAN + #define BLARGG_LITTLE_ENDIAN 1 + #elif __BYTE_ORDER == __BIG_ENDIAN + #define BLARGG_BIG_ENDIAN 1 + #endif +#else + +#if defined (LSB_FIRST) || defined (__LITTLE_ENDIAN__) || BLARGG_CPU_X86 || \ + (defined (LITTLE_ENDIAN) && LITTLE_ENDIAN+0 != 1234) + #define BLARGG_LITTLE_ENDIAN 1 +#endif + +#if defined (MSB_FIRST) || defined (__BIG_ENDIAN__) || defined (WORDS_BIGENDIAN) || \ + defined (__sparc__) || BLARGG_CPU_POWERPC || \ + (defined (BIG_ENDIAN) && BIG_ENDIAN+0 != 4321) + #define BLARGG_BIG_ENDIAN 1 +#elif !defined (__mips__) + // No endian specified; assume little-endian, since it's most common + #define BLARGG_LITTLE_ENDIAN 1 +#endif +#endif +#endif + +#if BLARGG_LITTLE_ENDIAN && BLARGG_BIG_ENDIAN + #undef BLARGG_LITTLE_ENDIAN + #undef BLARGG_BIG_ENDIAN +#endif + +inline void blargg_verify_byte_order() +{ + #ifndef NDEBUG + #if BLARGG_BIG_ENDIAN + volatile int i = 1; + assert( *(volatile char*) &i == 0 ); + #elif BLARGG_LITTLE_ENDIAN + volatile int i = 1; + assert( *(volatile char*) &i != 0 ); + #endif + #endif +} + +inline unsigned get_le16( void const* p ) +{ + return (unsigned) ((unsigned char const*) p) [1] << 8 | + (unsigned) ((unsigned char const*) p) [0]; +} + +inline unsigned get_be16( void const* p ) +{ + return (unsigned) ((unsigned char const*) p) [0] << 8 | + (unsigned) ((unsigned char const*) p) [1]; +} + +inline unsigned get_le32( void const* p ) +{ + return (unsigned) ((unsigned char const*) p) [3] << 24 | + (unsigned) ((unsigned char const*) p) [2] << 16 | + (unsigned) ((unsigned char const*) p) [1] << 8 | + (unsigned) ((unsigned char const*) p) [0]; +} + +inline unsigned get_be32( void const* p ) +{ + return (unsigned) ((unsigned char const*) p) [0] << 24 | + (unsigned) ((unsigned char const*) p) [1] << 16 | + (unsigned) ((unsigned char const*) p) [2] << 8 | + (unsigned) ((unsigned char const*) p) [3]; +} + +inline void set_le16( void* p, unsigned n ) +{ + ((unsigned char*) p) [1] = (unsigned char) (n >> 8); + ((unsigned char*) p) [0] = (unsigned char) n; +} + +inline void set_be16( void* p, unsigned n ) +{ + ((unsigned char*) p) [0] = (unsigned char) (n >> 8); + ((unsigned char*) p) [1] = (unsigned char) n; +} + +inline void set_le32( void* p, unsigned n ) +{ + ((unsigned char*) p) [0] = (unsigned char) n; + ((unsigned char*) p) [1] = (unsigned char) (n >> 8); + ((unsigned char*) p) [2] = (unsigned char) (n >> 16); + ((unsigned char*) p) [3] = (unsigned char) (n >> 24); +} + +inline void set_be32( void* p, unsigned n ) +{ + ((unsigned char*) p) [3] = (unsigned char) n; + ((unsigned char*) p) [2] = (unsigned char) (n >> 8); + ((unsigned char*) p) [1] = (unsigned char) (n >> 16); + ((unsigned char*) p) [0] = (unsigned char) (n >> 24); +} + +#if BLARGG_NONPORTABLE + // Optimized implementation if byte order is known + #if BLARGG_LITTLE_ENDIAN + #define GET_LE16( addr ) (*(BOOST::uint16_t const*) (addr)) + #define GET_LE32( addr ) (*(BOOST::uint32_t const*) (addr)) + #define SET_LE16( addr, data ) (void) (*(BOOST::uint16_t*) (addr) = (data)) + #define SET_LE32( addr, data ) (void) (*(BOOST::uint32_t*) (addr) = (data)) + #elif BLARGG_BIG_ENDIAN + #define GET_BE16( addr ) (*(BOOST::uint16_t const*) (addr)) + #define GET_BE32( addr ) (*(BOOST::uint32_t const*) (addr)) + #define SET_BE16( addr, data ) (void) (*(BOOST::uint16_t*) (addr) = (data)) + #define SET_BE32( addr, data ) (void) (*(BOOST::uint32_t*) (addr) = (data)) + + #if BLARGG_CPU_POWERPC + // PowerPC has special byte-reversed instructions + #if defined (__MWERKS__) + #define GET_LE16( addr ) (__lhbrx( addr, 0 )) + #define GET_LE32( addr ) (__lwbrx( addr, 0 )) + #define SET_LE16( addr, in ) (__sthbrx( in, addr, 0 )) + #define SET_LE32( addr, in ) (__stwbrx( in, addr, 0 )) + #elif defined (__GNUC__) + #define GET_LE16( addr ) ({unsigned short ppc_lhbrx_; __asm__ volatile( "lhbrx %0,0,%1" : "=r" (ppc_lhbrx_) : "r" (addr) : "memory" ); ppc_lhbrx_;}) + #define GET_LE32( addr ) ({unsigned short ppc_lwbrx_; __asm__ volatile( "lwbrx %0,0,%1" : "=r" (ppc_lwbrx_) : "r" (addr) : "memory" ); ppc_lwbrx_;}) + #define SET_LE16( addr, in ) ({__asm__ volatile( "sthbrx %0,0,%1" : : "r" (in), "r" (addr) : "memory" );}) + #define SET_LE32( addr, in ) ({__asm__ volatile( "stwbrx %0,0,%1" : : "r" (in), "r" (addr) : "memory" );}) + #endif + #endif + #endif +#endif + +#ifndef GET_LE16 + #define GET_LE16( addr ) get_le16( addr ) + #define SET_LE16( addr, data ) set_le16( addr, data ) +#endif + +#ifndef GET_LE32 + #define GET_LE32( addr ) get_le32( addr ) + #define SET_LE32( addr, data ) set_le32( addr, data ) +#endif + +#ifndef GET_BE16 + #define GET_BE16( addr ) get_be16( addr ) + #define SET_BE16( addr, data ) set_be16( addr, data ) +#endif + +#ifndef GET_BE32 + #define GET_BE32( addr ) get_be32( addr ) + #define SET_BE32( addr, data ) set_be32( addr, data ) +#endif + +// auto-selecting versions + +inline void set_le( BOOST::uint16_t* p, unsigned n ) { SET_LE16( p, n ); } +inline void set_le( BOOST::uint32_t* p, unsigned n ) { SET_LE32( p, n ); } +inline void set_be( BOOST::uint16_t* p, unsigned n ) { SET_BE16( p, n ); } +inline void set_be( BOOST::uint32_t* p, unsigned n ) { SET_BE32( p, n ); } +inline unsigned get_le( BOOST::uint16_t const* p ) { return GET_LE16( p ); } +inline unsigned get_le( BOOST::uint32_t const* p ) { return GET_LE32( p ); } +inline unsigned get_be( BOOST::uint16_t const* p ) { return GET_BE16( p ); } +inline unsigned get_be( BOOST::uint32_t const* p ) { return GET_BE32( p ); } + +#endif diff --git a/snesreader/fex/blargg_errors.cpp b/snesreader/fex/blargg_errors.cpp new file mode 100644 index 00000000..14076cdb --- /dev/null +++ b/snesreader/fex/blargg_errors.cpp @@ -0,0 +1,113 @@ +// File_Extractor 1.0.0. http://www.slack.net/~ant/ + +#include "blargg_errors.h" + +/* Copyright (C) 2009 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + +blargg_err_def_t blargg_err_generic = BLARGG_ERR_GENERIC; +blargg_err_def_t blargg_err_memory = BLARGG_ERR_MEMORY; +blargg_err_def_t blargg_err_caller = BLARGG_ERR_CALLER; +blargg_err_def_t blargg_err_internal = BLARGG_ERR_INTERNAL; +blargg_err_def_t blargg_err_limitation = BLARGG_ERR_LIMITATION; + +blargg_err_def_t blargg_err_file_missing = BLARGG_ERR_FILE_MISSING; +blargg_err_def_t blargg_err_file_read = BLARGG_ERR_FILE_READ; +blargg_err_def_t blargg_err_file_write = BLARGG_ERR_FILE_WRITE; +blargg_err_def_t blargg_err_file_io = BLARGG_ERR_FILE_IO; +blargg_err_def_t blargg_err_file_full = BLARGG_ERR_FILE_FULL; +blargg_err_def_t blargg_err_file_eof = BLARGG_ERR_FILE_EOF; + +blargg_err_def_t blargg_err_file_type = BLARGG_ERR_FILE_TYPE; +blargg_err_def_t blargg_err_file_feature = BLARGG_ERR_FILE_FEATURE; +blargg_err_def_t blargg_err_file_corrupt = BLARGG_ERR_FILE_CORRUPT; + +const char* blargg_err_str( blargg_err_t err ) +{ + if ( !err ) + return ""; + + if ( *err == BLARGG_ERR_TYPE("")[0] ) + return err + 1; + + return err; +} + +bool blargg_is_err_type( blargg_err_t err, const char type [] ) +{ + if ( err ) + { + // True if first strlen(type) characters of err match type + char const* p = err; + while ( *type && *type == *p ) + { + type++; + p++; + } + + if ( !*type ) + return true; + } + + return false; +} + +const char* blargg_err_details( blargg_err_t err ) +{ + const char* p = err; + if ( !p ) + { + p = ""; + } + else if ( *p == BLARGG_ERR_TYPE("")[0] ) + { + while ( *p && *p != ';' ) + p++; + + // Skip ; and space after it + if ( *p ) + { + p++; + + check( *p == ' ' ); + if ( *p ) + p++; + } + } + return p; +} + +int blargg_err_to_code( blargg_err_t err, blargg_err_to_code_t const codes [] ) +{ + if ( !err ) + return 0; + + while ( codes->str && !blargg_is_err_type( err, codes->str ) ) + codes++; + + return codes->code; +} + +blargg_err_t blargg_code_to_err( int code, blargg_err_to_code_t const codes [] ) +{ + if ( !code ) + return blargg_ok; + + while ( codes->str && codes->code != code ) + codes++; + + if ( !codes->str ) + return blargg_err_generic; + + return codes->str; +} diff --git a/snesreader/fex/blargg_errors.h b/snesreader/fex/blargg_errors.h new file mode 100644 index 00000000..9c5206d5 --- /dev/null +++ b/snesreader/fex/blargg_errors.h @@ -0,0 +1,80 @@ +// Error strings and conversion functions + +// File_Extractor 1.0.0 +#ifndef BLARGG_ERRORS_H +#define BLARGG_ERRORS_H + +#ifndef BLARGG_COMMON_H + #include "blargg_common.h" +#endif + +typedef const char blargg_err_def_t []; + +// Basic errors +extern blargg_err_def_t blargg_err_generic; +extern blargg_err_def_t blargg_err_memory; +extern blargg_err_def_t blargg_err_caller; +extern blargg_err_def_t blargg_err_internal; +extern blargg_err_def_t blargg_err_limitation; + +// File low-level +extern blargg_err_def_t blargg_err_file_missing; // not found +extern blargg_err_def_t blargg_err_file_read; +extern blargg_err_def_t blargg_err_file_write; +extern blargg_err_def_t blargg_err_file_io; +extern blargg_err_def_t blargg_err_file_full; +extern blargg_err_def_t blargg_err_file_eof; + +// File high-level +extern blargg_err_def_t blargg_err_file_type; // wrong file type +extern blargg_err_def_t blargg_err_file_feature; +extern blargg_err_def_t blargg_err_file_corrupt; + +// C string describing error, or "" if err == NULL +const char* blargg_err_str( blargg_err_t err ); + +// True iff error is of given type, or false if err == NULL +bool blargg_is_err_type( blargg_err_t, const char type [] ); + +// Details of error without describing main cause, or "" if err == NULL +const char* blargg_err_details( blargg_err_t err ); + +// Converts error string to integer code using mapping table. Calls blargg_is_err_type() +// for each str and returns code on first match. Returns 0 if err == NULL. +struct blargg_err_to_code_t { + const char* str; + int code; +}; +int blargg_err_to_code( blargg_err_t err, blargg_err_to_code_t const [] ); + +// Converts error code back to string. If code == 0, returns NULL. If not in table, +// returns blargg_err_generic. +blargg_err_t blargg_code_to_err( int code, blargg_err_to_code_t const [] ); + +// Generates error string literal with details of cause +#define BLARGG_ERR( type, str ) (type "; " str) + +// Extra space to make it clear when blargg_err_str() isn't called to get +// printable version of error. At some point, I might prefix error strings +// with a code, to speed conversion to a code. +#define BLARGG_ERR_TYPE( str ) " " str + +// Error types to pass to BLARGG_ERR macro +#define BLARGG_ERR_GENERIC BLARGG_ERR_TYPE( "operation failed" ) +#define BLARGG_ERR_MEMORY BLARGG_ERR_TYPE( "out of memory" ) +#define BLARGG_ERR_CALLER BLARGG_ERR_TYPE( "internal usage bug" ) +#define BLARGG_ERR_INTERNAL BLARGG_ERR_TYPE( "internal bug" ) +#define BLARGG_ERR_LIMITATION BLARGG_ERR_TYPE( "exceeded limitation" ) + +#define BLARGG_ERR_FILE_MISSING BLARGG_ERR_TYPE( "file not found" ) +#define BLARGG_ERR_FILE_READ BLARGG_ERR_TYPE( "couldn't open file" ) +#define BLARGG_ERR_FILE_WRITE BLARGG_ERR_TYPE( "couldn't modify file" ) +#define BLARGG_ERR_FILE_IO BLARGG_ERR_TYPE( "read/write error" ) +#define BLARGG_ERR_FILE_FULL BLARGG_ERR_TYPE( "disk full" ) +#define BLARGG_ERR_FILE_EOF BLARGG_ERR_TYPE( "truncated file" ) + +#define BLARGG_ERR_FILE_TYPE BLARGG_ERR_TYPE( "wrong file type" ) +#define BLARGG_ERR_FILE_FEATURE BLARGG_ERR_TYPE( "unsupported file feature" ) +#define BLARGG_ERR_FILE_CORRUPT BLARGG_ERR_TYPE( "corrupt file" ) + +#endif diff --git a/snesreader/fex/blargg_source.h b/snesreader/fex/blargg_source.h new file mode 100644 index 00000000..659f34c5 --- /dev/null +++ b/snesreader/fex/blargg_source.h @@ -0,0 +1,125 @@ +/* Included at the beginning of library source files, AFTER all other #include +lines. Sets up helpful macros and services used in my source code. Since this +is only "active" in my source code, I don't have to worry about polluting the +global namespace with unprefixed names. */ + +// File_Extractor 1.0.0 +#ifndef BLARGG_SOURCE_H +#define BLARGG_SOURCE_H + +#ifndef BLARGG_COMMON_H // optimization only + #include "blargg_common.h" +#endif +#include "blargg_errors.h" + +#include /* memcpy(), memset(), memmove() */ +#include /* offsetof() */ + +/* The following four macros are for debugging only. Some or all might be +defined to do nothing, depending on the circumstances. Described is what +happens when a particular macro is defined to do something. When defined to +do nothing, the macros do NOT evaluate their argument(s). */ + +/* If expr is false, prints file and line number, then aborts program. Meant +for checking internal state and consistency. A failed assertion indicates a bug +in MY code. + +void assert( bool expr ); */ +#include + +/* If expr is false, prints file and line number, then aborts program. Meant +for checking caller-supplied parameters and operations that are outside the +control of the module. A failed requirement probably indicates a bug in YOUR +code. + +void require( bool expr ); */ +#undef require +#define require( expr ) assert( expr ) + +/* Like printf() except output goes to debugging console/file. + +void dprintf( const char format [], ... ); */ +static inline void blargg_dprintf_( const char [], ... ) { } +#undef dprintf +#define dprintf (1) ? (void) 0 : blargg_dprintf_ + +/* If expr is false, prints file and line number to debug console/log, then +continues execution normally. Meant for flagging potential problems or things +that should be looked into, but that aren't serious problems. + +void check( bool expr ); */ +#undef check +#define check( expr ) ((void) 0) + +/* If expr yields non-NULL error string, returns it from current function, +otherwise continues normally. */ +#undef RETURN_ERR +#define RETURN_ERR( expr ) \ + do {\ + blargg_err_t blargg_return_err_ = (expr);\ + if ( blargg_return_err_ )\ + return blargg_return_err_;\ + } while ( 0 ) + +/* If ptr is NULL, returns out-of-memory error, otherwise continues normally. */ +#undef CHECK_ALLOC +#define CHECK_ALLOC( ptr ) \ + do {\ + if ( !(ptr) )\ + return blargg_err_memory;\ + } while ( 0 ) + +/* The usual min/max functions for built-in types. + +template T min( T x, T y ) { return x < y ? x : y; } +template T max( T x, T y ) { return x > y ? x : y; } */ +#define BLARGG_DEF_MIN_MAX( type ) \ + static inline type blargg_min( type x, type y ) { if ( y < x ) x = y; return x; }\ + static inline type blargg_max( type x, type y ) { if ( x < y ) x = y; return x; } + +BLARGG_DEF_MIN_MAX( int ) +BLARGG_DEF_MIN_MAX( unsigned ) +BLARGG_DEF_MIN_MAX( long ) +BLARGG_DEF_MIN_MAX( unsigned long ) +BLARGG_DEF_MIN_MAX( float ) +BLARGG_DEF_MIN_MAX( double ) + +#undef min +#define min blargg_min + +#undef max +#define max blargg_max + +// typedef unsigned char byte; +typedef unsigned char blargg_byte; +#undef byte +#define byte blargg_byte + +#ifndef BLARGG_EXPORT + #if defined (_WIN32) && BLARGG_BUILD_DLL + #define BLARGG_EXPORT __declspec(dllexport) + #elif defined (__GNUC__) + // can always set visibility, even when not building DLL + #define BLARGG_EXPORT __attribute__ ((visibility ("default"))) + #else + #define BLARGG_EXPORT + #endif +#endif + +#if BLARGG_LEGACY + #define BLARGG_CHECK_ALLOC CHECK_ALLOC + #define BLARGG_RETURN_ERR RETURN_ERR +#endif + +// Called after failed operation when overall operation may still complete OK. +// Only used by unit testing framework. +#undef ACK_FAILURE +#define ACK_FAILURE() ((void)0) + +/* BLARGG_SOURCE_BEGIN: If defined, #included, allowing redefition of dprintf etc. +and check */ +#ifdef BLARGG_SOURCE_BEGIN + #include BLARGG_SOURCE_BEGIN +#endif + +#endif diff --git a/snesreader/fex/fex.cpp b/snesreader/fex/fex.cpp new file mode 100644 index 00000000..d0946dd9 --- /dev/null +++ b/snesreader/fex/fex.cpp @@ -0,0 +1,323 @@ +// File_Extractor 1.0.0. http://www.slack.net/~ant/ + +#include "fex.h" + +#include "File_Extractor.h" +#include "blargg_endian.h" +#include +#include + +/* Copyright (C) 2005-2009 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +details. You should have received a copy of the GNU Lesser General Public +License along with this module; if not, write to the Free Software Foundation, +Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ + +#include "blargg_source.h" + + +//// Types + +BLARGG_EXPORT const fex_type_t* fex_type_list( void ) +{ + static fex_type_t const fex_type_list_ [] = + { + #ifdef FEX_TYPE_LIST + FEX_TYPE_LIST + #else + // Modify blargg_config.h to change type list, NOT this file + fex_7z_type, + fex_gz_type, + #if FEX_ENABLE_RAR + fex_rar_type, + #endif + fex_zip_type, + #endif + fex_bin_type, + NULL + }; + + return fex_type_list_; +} + +BLARGG_EXPORT fex_err_t fex_init( void ) +{ + static bool inited; + if ( !inited ) + { + for ( fex_type_t const* t = fex_type_list(); *t != NULL; ++t ) + { + if ( (*t)->init ) + RETURN_ERR( (*t)->init() ); + } + inited = true; + } + return blargg_ok; +} + +BLARGG_EXPORT const char* fex_identify_header( void const* header ) +{ + unsigned four = get_be32( header ); + switch ( four ) + { + case 0x52457E5E: + case 0x52617221: return ".rar"; + + case 0x377ABCAF: return ".7z"; + + case 0x504B0304: + case 0x504B0506: return ".zip"; + + case 0x53495421: return ".sit"; + case 0x41724301: return ".arc"; + case 0x4D534346: return ".cab"; + case 0x5A4F4F20: return ".zoo"; + } + + unsigned three = four >> 8; + switch ( three ) + { + case 0x425A68: return ".bz2"; + } + + unsigned two = four >> 16; + switch ( two ) + { + case 0x1F8B: return ".gz"; + case 0x60EA: return ".arj"; + } + + unsigned skip_first_two = four & 0xFFFF; + if ( skip_first_two == 0x2D6C ) + return ".lha"; + + return ""; +} + +static int fex_has_extension_( const char str [], const char suffix [], size_t str_len ) +{ + size_t suffix_len = strlen( suffix ); + if ( str_len >= suffix_len ) + { + str += str_len - suffix_len; + while ( *str && tolower( (unsigned char) *str ) == *suffix ) + { + str++; + suffix++; + } + } + return *suffix == 0; +} + +BLARGG_EXPORT int fex_has_extension( const char str [], const char suffix [] ) +{ + return fex_has_extension_( str, suffix, strlen( str ) ); +} + +static int is_archive_extension( const char str [] ) +{ + static const char exts [] [6] = { + ".7z", + ".arc", + ".arj", + ".bz2", + ".cab", + ".dmg", + ".gz", + ".lha", + ".lz", + ".lzh", + ".lzma", + ".lzo", + ".lzx", + ".pea", + ".rar", + ".sit", + ".sitx", + ".tgz", + ".tlz", + ".z", + ".zip", + ".zoo", + "" + }; + + size_t str_len = strlen( str ); + const char (*ext) [6] = exts; + for ( ; **ext; ext++ ) + { + if ( fex_has_extension_( str, *ext, str_len ) ) + return 1; + } + return 0; +} + +BLARGG_EXPORT fex_type_t fex_identify_extension( const char str [] ) +{ + size_t str_len = strlen( str ); + for ( fex_type_t const* types = fex_type_list(); *types; types++ ) + { + if ( fex_has_extension_( str, (*types)->extension, str_len ) ) + { + // Avoid treating known archive type as binary + if ( *(*types)->extension || !is_archive_extension( str ) ) + return *types; + } + } + return NULL; +} + +BLARGG_EXPORT fex_err_t fex_identify_file( fex_type_t* type_out, const char path [] ) +{ + *type_out = NULL; + + fex_type_t type = fex_identify_extension( path ); + + // Unsupported extension? + if ( !type ) + return blargg_ok; // reject + + // Unknown/no extension? + if ( !*(type->extension) ) + { + // Examine header + FEX_FILE_READER in; + RETURN_ERR( in.open( path ) ); + if ( in.remain() >= fex_identify_header_size ) + { + char h [fex_identify_header_size]; + RETURN_ERR( in.read( h, sizeof h ) ); + + type = fex_identify_extension( fex_identify_header( h ) ); + } + } + + *type_out = type; + return blargg_ok; +} + +BLARGG_EXPORT fex_err_t fex_open_type( fex_t** fe_out, const char path [], fex_type_t type ) +{ + *fe_out = NULL; + + if ( !type ) + return blargg_err_file_type; + + fex_t* fe = type->new_fex(); + CHECK_ALLOC( fe ); + + fex_err_t err = fe->open( path ); + if ( err ) + { + delete fe; + return err; + } + + *fe_out = fe; + return blargg_ok; +} + +BLARGG_EXPORT fex_err_t fex_open( fex_t** fe_out, const char path [] ) +{ + *fe_out = NULL; + + fex_type_t type; + RETURN_ERR( fex_identify_file( &type, path ) ); + + return fex_open_type( fe_out, path, type ); +} + + +//// Wide paths + +#if BLARGG_UTF8_PATHS +char* fex_wide_to_path( const wchar_t* wide ) +{ + return blargg_to_utf8( wide ); +} + +void fex_free_path( char* path ) +{ + free( path ); +} +#endif + + +//// Errors + +#define ENTRY( name ) { blargg_err_##name, fex_err_##name } +static blargg_err_to_code_t const fex_codes [] = +{ + ENTRY( generic ), + ENTRY( memory ), + ENTRY( caller ), + ENTRY( internal ), + ENTRY( limitation ), + + ENTRY( file_missing ), + ENTRY( file_read ), + ENTRY( file_io ), + ENTRY( file_eof ), + + ENTRY( file_type ), + ENTRY( file_feature ), + ENTRY( file_corrupt ), + + { 0, -1 } +}; +#undef ENTRY + +static int err_code( fex_err_t err ) +{ + return blargg_err_to_code( err, fex_codes ); +} + +BLARGG_EXPORT int fex_err_code( fex_err_t err ) +{ + int code = err_code( err ); + return (code >= 0 ? code : fex_err_generic); +} + +BLARGG_EXPORT fex_err_t fex_code_to_err( int code ) +{ + return blargg_code_to_err( code, fex_codes ); +} + +BLARGG_EXPORT const char* fex_err_details( fex_err_t err ) +{ + // If we don't have error code assigned, return entire string + return (err_code( err ) >= 0 ? blargg_err_details( err ) : blargg_err_str( err )); +} + + +//// Wrappers + +BLARGG_EXPORT fex_err_t fex_read( fex_t* fe, void* out, int count ) +{ + RETURN_ERR( fe->stat() ); + return fe->reader().read( out, count ); +} + +BLARGG_EXPORT void fex_close ( fex_t* fe ) { delete fe; } +BLARGG_EXPORT fex_type_t fex_type ( const fex_t* fe ) { return fe->type(); } +BLARGG_EXPORT int fex_done ( const fex_t* fe ) { return fe->done(); } +BLARGG_EXPORT const char* fex_name ( const fex_t* fe ) { return fe->name(); } +BLARGG_EXPORT const wchar_t* fex_wname ( const fex_t* fe ) { return fe->wname(); } +BLARGG_EXPORT int fex_size ( const fex_t* fe ) { return fe->size(); } +BLARGG_EXPORT unsigned fex_dos_date ( const fex_t* fe ) { return fe->dos_date(); } +BLARGG_EXPORT unsigned fex_crc32 ( const fex_t* fe ) { return fe->crc32(); } +BLARGG_EXPORT fex_err_t fex_stat ( fex_t* fe ) { return fe->stat(); } +BLARGG_EXPORT fex_err_t fex_next ( fex_t* fe ) { return fe->next(); } +BLARGG_EXPORT fex_err_t fex_rewind ( fex_t* fe ) { return fe->rewind(); } +BLARGG_EXPORT int fex_tell ( const fex_t* fe ) { return fe->tell(); } +BLARGG_EXPORT fex_pos_t fex_tell_arc ( const fex_t* fe ) { return fe->tell_arc(); } +BLARGG_EXPORT fex_err_t fex_seek_arc ( fex_t* fe, fex_pos_t pos ) { return fe->seek_arc( pos ); } +BLARGG_EXPORT const char* fex_type_extension ( fex_type_t t ) { return t->extension; } +BLARGG_EXPORT const char* fex_type_name ( fex_type_t t ) { return t->name; } +BLARGG_EXPORT fex_err_t fex_data ( fex_t* fe, const void** data_out ) { return fe->data( data_out ); } +BLARGG_EXPORT const char* fex_err_str ( fex_err_t err ) { return blargg_err_str( err ); } diff --git a/snesreader/fex/fex.h b/snesreader/fex/fex.h new file mode 100644 index 00000000..f9452771 --- /dev/null +++ b/snesreader/fex/fex.h @@ -0,0 +1,206 @@ +/** Uniform access to zip, gzip, 7-zip, and RAR compressed archives \file */ + +/* File_Extractor 1.0.0 */ +#ifndef FEX_H +#define FEX_H + +#include + +#ifdef __cplusplus + extern "C" { +#endif + + +/** First parameter of most functions is fex_t*, or const fex_t* if nothing is +changed. Once one of these functions returns an error, the archive should not +be used any further, other than to close it. One exception is +fex_error_file_eof; the archive may still be used after this. */ +typedef struct fex_t fex_t; + +/** Pointer to error, or NULL if function was successful. See error functions +below. */ +#ifndef fex_err_t /* (#ifndef allows better testing of library) */ + typedef const char* fex_err_t; +#endif + + +/**** File types ****/ + +/** Archive file type identifier. Can also hold NULL. */ +typedef const struct fex_type_t_* fex_type_t; + +/** Array of supported types, with NULL at end */ +const fex_type_t* fex_type_list( void ); + +/** Name of this archive type, e.g. "ZIP archive", "file" */ +const char* fex_type_name( fex_type_t ); + +/** Usual file extension for type, e.g. ".zip", ".7z". For binary file type, +returns "", since it can open any file. */ +const char* fex_type_extension( fex_type_t ); + + +/**** Wide-character file paths (Windows only) ****/ + +/** Converts wide-character path to form suitable for use with fex functions. +Only supported when BLARGG_UTF8_PATHS is defined and building on Windows. */ +char* fex_wide_to_path( const wchar_t* wide ); + +/** Frees converted path. OK to pass NULL. Only supported when BLARGG_UTF8_PATHS +is defined and building on Windows */ +void fex_free_path( char* ); + + +/**** Identification ****/ + +/** True if str ends in extension. If extension is "", always returns true. +Converts str to lowercase before comparison, so extension should ALREADY be +lowercase (i.e. pass ".zip", NOT ".ZIP"). */ +int fex_has_extension( const char str [], const char extension [] ); + +/** Determines type based on first fex_identify_header_size bytes of file. +Returns usual file extension this should have (e.g. ".zip", ".gz", etc.). +Returns "" if file header is not recognized. */ +const char* fex_identify_header( const void* header ); +enum { fex_identify_header_size = 16 }; + +/** Determines type based on extension of a file path, or just a lone extension +(must include '.', e.g. ".zip", NOT just "zip"). Returns NULL if extension is +for an unsupported type (e.g. ".lzh"). */ +fex_type_t fex_identify_extension( const char path_or_extension [] ); + +/** Determines type based on filename extension and/or file header. Sets *out +to determined type, or NULL if type is not supported. */ +fex_err_t fex_identify_file( fex_type_t* out, const char path [] ); + +/** Type of an already-opened archive */ +fex_type_t fex_type( const fex_t* ); + + +/**** Open/close ****/ + +/** Initializes static tables used by library. Automatically called by +fex_open(). OK to call more than once. */ +fex_err_t fex_init( void ); + +/** Opens archive and points *out at it. If error, sets *out to NULL. */ +fex_err_t fex_open( fex_t** out, const char path [] ); + +/** Opens archive of specified type and sets *out. Returns error if file is not +of that archive type. If error, sets *out to NULL. */ +fex_err_t fex_open_type( fex_t** out, const char path [], fex_type_t ); + +/** Closes archive and frees memory. OK to pass NULL. */ +void fex_close( fex_t* ); + + +/**** Scanning ****/ + +/** True if at end of archive. Must be called after fex_open() or fex_rewind(), +as an archive might contain no files. */ +int fex_done( const fex_t* ); + +/** Goes to next file in archive. If there are no more files, fex_done() will +now return true. */ +fex_err_t fex_next( fex_t* ); + +/** Goes back to first file in archive, as if it were just opened with +fex_open() */ +fex_err_t fex_rewind( fex_t* ); + +/** Saved position in archive. Can also store zero. */ +typedef int fex_pos_t; + +/** Position of current file in archive. Never returns zero. */ +fex_pos_t fex_tell_arc( const fex_t* ); + +/** Returns to file at previously-saved position */ +fex_err_t fex_seek_arc( fex_t*, fex_pos_t ); + + +/**** Info ****/ + +/** Name of current file */ +const char* fex_name( const fex_t* ); + +/** Wide-character name of current file, or NULL if unavailable */ +const wchar_t* fex_wname( const fex_t* ); + +/** Makes further information available for file */ +fex_err_t fex_stat( fex_t* ); + +/** Size of current file. fex_stat() or fex_data() must have been called. */ +int fex_size( const fex_t* ); + +/** Modification date of current file (MS-DOS format), or 0 if unavailable. +fex_stat() must have been called. */ +unsigned int fex_dos_date( const fex_t* ); + +/** CRC-32 checksum of current file's contents, or 0 if unavailable. Doesn't +require calculation; simply gets it from file's header. fex_stat() must have +been called. */ +unsigned int fex_crc32( const fex_t* ); + + +/**** Extraction ****/ + +/** Reads n bytes from current file. Reading past end of file results in +fex_err_file_eof. */ +fex_err_t fex_read( fex_t*, void* out, int n ); + +/** Number of bytes read from current file */ +int fex_tell( const fex_t* ); + +/** Points *out at current file's data in memory. Pointer is valid until +fex_next(), fex_rewind(), fex_seek_arc(), or fex_close() is called. Pointer +must NOT be freed(); library frees it automatically. If error, sets *out to +NULL. */ +fex_err_t fex_data( fex_t*, const void** out ); + + +/**** Errors ****/ + +/** Error string associated with err. Returns "" if err is NULL. Returns err +unchanged if it isn't a fex_err_t returned by library. */ +const char* fex_err_str( fex_err_t err ); + +/** Details of error beyond main cause, or "" if none or err is NULL. Returns +err unchanged if it isn't a fex_err_t returned by library. */ +const char* fex_err_details( fex_err_t err ); + +/** Numeric code corresponding to err. Returns fex_ok if err is NULL. Returns +fex_err_generic if err isn't a fex_err_t returned by library. */ +int fex_err_code( fex_err_t err ); + +enum { + fex_ok = 0,/**< Successful call. Guaranteed to be zero. */ + fex_err_generic = 0x01,/**< Error of unspecified type */ + fex_err_memory = 0x02,/**< Out of memory */ + fex_err_caller = 0x03,/**< Caller called function with bad args */ + fex_err_internal = 0x04,/**< Internal problem, bug, etc. */ + fex_err_limitation = 0x05,/**< Exceeded program limit */ + + fex_err_file_missing = 0x20,/**< File not found at specified path */ + fex_err_file_read = 0x21,/**< Couldn't open file for reading */ + fex_err_file_io = 0x23,/**< Read/write error */ + fex_err_file_eof = 0x25,/**< Tried to read past end of file */ + + fex_err_file_type = 0x30,/**< File is of wrong type */ + fex_err_file_feature = 0x32,/**< File requires unsupported feature */ + fex_err_file_corrupt = 0x33 /**< File is corrupt */ +}; + +/** fex_err_t corresponding to numeric code. Note that this might not recover +the original fex_err_t before it was converted to a numeric code; in +particular, fex_err_details(fex_code_to_err(code)) will be "" in most cases. */ +fex_err_t fex_code_to_err( int code ); + + +/* Deprecated */ +typedef fex_t File_Extractor; + +#ifdef __cplusplus + } +#endif + +#endif diff --git a/snesreader/filechooser.cpp b/snesreader/filechooser.cpp new file mode 100644 index 00000000..e1f9d8b0 --- /dev/null +++ b/snesreader/filechooser.cpp @@ -0,0 +1,57 @@ +#include "filechooser.moc.hpp" +#include "filechooser.moc" + +//FileChooser is implemented as a modal QWidget instead of a QDialog +//due to a bug in Qt 4.6.0 (QTBUG-7188); which causes the FileChooser +//to not refresh when a QTimer is active from the main application. +string FileChooser::exec() { + if(list.size() == 0) return ""; + if(list.size() == 1) return list[0]; + + listWidget->clear(); + for(unsigned i = 0; i < list.size(); i++) { + listWidget->addItem(list[i]); + } + listWidget->sortItems(Qt::AscendingOrder); + listWidget->setCurrentRow(0); + listWidget->setFocus(); + + name = ""; + setWindowModality(Qt::ApplicationModal); + show(); + while(isVisible()) QApplication::processEvents(); + setWindowModality(Qt::NonModal); + return name; +} + +void FileChooser::load() { + QListWidgetItem *item = listWidget->currentItem(); + if(item) name = item->text().toUtf8().constData(); + close(); +} + +FileChooser::FileChooser() { + setWindowTitle("Select Cartridge To Load"); + setMinimumWidth(480); + setMinimumHeight(320); + + layout = new QVBoxLayout; + setLayout(layout); + + listWidget = new QListWidget; + layout->addWidget(listWidget); + + controlLayout = new QHBoxLayout; + controlLayout->setAlignment(Qt::AlignRight); + layout->addLayout(controlLayout); + + okButton = new QPushButton("Ok"); + controlLayout->addWidget(okButton); + + cancelButton = new QPushButton("Cancel"); + controlLayout->addWidget(cancelButton); + + connect(listWidget, SIGNAL(itemActivated(QListWidgetItem*)), this, SLOT(load())); + connect(okButton, SIGNAL(released()), this, SLOT(load())); + connect(cancelButton, SIGNAL(released()), this, SLOT(close())); +} diff --git a/snesreader/filechooser.moc.hpp b/snesreader/filechooser.moc.hpp new file mode 100644 index 00000000..e024cab0 --- /dev/null +++ b/snesreader/filechooser.moc.hpp @@ -0,0 +1,20 @@ +class FileChooser : public QWidget { + Q_OBJECT + +public: + lstring list; + string name; + string exec(); + + FileChooser(); + +private slots: + void load(); + +private: + QVBoxLayout *layout; + QListWidget *listWidget; + QHBoxLayout *controlLayout; + QPushButton *okButton; + QPushButton *cancelButton; +} *fileChooser; diff --git a/snesreader/libjma/7z.h b/snesreader/libjma/7z.h new file mode 100644 index 00000000..50e1f242 --- /dev/null +++ b/snesreader/libjma/7z.h @@ -0,0 +1,28 @@ +/* +Copyright (C) 2005-2007 NSRT Team ( http://nsrt.edgeemu.com ) +Copyright (C) 2002 Andrea Mazzoleni ( http://advancemame.sf.net ) + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License version 2.1 as published by the Free Software Foundation. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef __7Z_H +#define __7Z_H + +#include "iiostrm.h" + +bool decompress_lzma_7z(ISequentialInStream& in, unsigned in_size, ISequentialOutStream& out, unsigned out_size) throw (); +bool decompress_lzma_7z(const unsigned char* in_data, unsigned in_size, unsigned char* out_data, unsigned out_size) throw (); + +#endif + diff --git a/snesreader/libjma/7zlzma.cpp b/snesreader/libjma/7zlzma.cpp new file mode 100644 index 00000000..b849d8df --- /dev/null +++ b/snesreader/libjma/7zlzma.cpp @@ -0,0 +1,50 @@ +/* +Copyright (C) 2005-2007 NSRT Team ( http://nsrt.edgeemu.com ) +Copyright (C) 2002 Andrea Mazzoleni ( http://advancemame.sf.net ) + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License version 2.1 as published by the Free Software Foundation. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "7z.h" + +#include "lzmadec.h" + +bool decompress_lzma_7z(ISequentialInStream& in, unsigned in_size, ISequentialOutStream& out, unsigned out_size) throw () +{ + try + { + NCompress::NLZMA::CDecoder cc; + + UINT64 in_size_l = in_size; + UINT64 out_size_l = out_size; + + if (cc.ReadCoderProperties(&in) != S_OK) { return(false); } + if (cc.Code(&in, &out, &in_size_l, &out_size_l) != S_OK) { return(false); } + if (out.size_get() != out_size || out.overflow_get()) { return(false); } + + return(true); + } + catch (...) + { + return(false); + } +} + +bool decompress_lzma_7z(const unsigned char* in_data, unsigned int in_size, unsigned char* out_data, unsigned int out_size) throw () +{ + ISequentialInStream_Array in(reinterpret_cast(in_data), in_size); + ISequentialOutStream_Array out(reinterpret_cast(out_data), out_size); + + return(decompress_lzma_7z(in, in_size, out, out_size)); +} diff --git a/snesreader/libjma/aribitcd.h b/snesreader/libjma/aribitcd.h new file mode 100644 index 00000000..1fb421ba --- /dev/null +++ b/snesreader/libjma/aribitcd.h @@ -0,0 +1,73 @@ +#ifndef __COMPRESSION_BITCODER_H +#define __COMPRESSION_BITCODER_H + +#include "rngcoder.h" + +namespace NCompression { +namespace NArithmetic { + +const int kNumBitModelTotalBits = 11; +const UINT32 kBitModelTotal = (1 << kNumBitModelTotalBits); + +const int kNumMoveReducingBits = 2; + +///////////////////////////// +// CBitModel + +template +class CBitModel +{ +public: + UINT32 m_Probability; + void UpdateModel(UINT32 aSymbol) + { + /* + m_Probability -= (m_Probability + ((aSymbol - 1) & ((1 << aNumMoveBits) - 1))) >> aNumMoveBits; + m_Probability += (1 - aSymbol) << (kNumBitModelTotalBits - aNumMoveBits); + */ + if (aSymbol == 0) + m_Probability += (kBitModelTotal - m_Probability) >> aNumMoveBits; + else + m_Probability -= (m_Probability) >> aNumMoveBits; + } +public: + void Init() { m_Probability = kBitModelTotal / 2; } +}; + +template +class CBitDecoder: public CBitModel +{ +public: + UINT32 Decode(CRangeDecoder *aRangeDecoder) + { + UINT32 aNewBound = (aRangeDecoder->m_Range >> kNumBitModelTotalBits) * CBitModel::m_Probability; + if (aRangeDecoder->m_Code < aNewBound) + { + aRangeDecoder->m_Range = aNewBound; + CBitModel::m_Probability += (kBitModelTotal - CBitModel::m_Probability) >> aNumMoveBits; + if (aRangeDecoder->m_Range < kTopValue) + { + aRangeDecoder->m_Code = (aRangeDecoder->m_Code << 8) | aRangeDecoder->m_Stream.ReadByte(); + aRangeDecoder->m_Range <<= 8; + } + return 0; + } + else + { + aRangeDecoder->m_Range -= aNewBound; + aRangeDecoder->m_Code -= aNewBound; + CBitModel::m_Probability -= (CBitModel::m_Probability) >> aNumMoveBits; + if (aRangeDecoder->m_Range < kTopValue) + { + aRangeDecoder->m_Code = (aRangeDecoder->m_Code << 8) | aRangeDecoder->m_Stream.ReadByte(); + aRangeDecoder->m_Range <<= 8; + } + return 1; + } + } +}; + +}} + + +#endif diff --git a/snesreader/libjma/ariconst.h b/snesreader/libjma/ariconst.h new file mode 100644 index 00000000..751b2b7c --- /dev/null +++ b/snesreader/libjma/ariconst.h @@ -0,0 +1,29 @@ +/* +Copyright (C) 2002 Andrea Mazzoleni ( http://advancemame.sf.net ) +Copyright (C) 2001-4 Igor Pavlov ( http://www.7-zip.org ) + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License version 2.1 as published by the Free Software Foundation. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef __ARICONST_H +#define __ARICONST_H + +#include "aribitcd.h" + + +typedef NCompression::NArithmetic::CRangeDecoder CMyRangeDecoder; +template class CMyBitDecoder: + public NCompression::NArithmetic::CBitDecoder {}; + +#endif diff --git a/snesreader/libjma/ariprice.h b/snesreader/libjma/ariprice.h new file mode 100644 index 00000000..ccc398e1 --- /dev/null +++ b/snesreader/libjma/ariprice.h @@ -0,0 +1,12 @@ +#ifndef __COMPRESSION_ARIPRICE_H +#define __COMPRESSION_ARIPRICE_H + +namespace NCompression { +namespace NArithmetic { + +const UINT32 kNumBitPriceShiftBits = 6; +const UINT32 kBitPrice = 1 << kNumBitPriceShiftBits; + +}} + +#endif diff --git a/snesreader/libjma/btreecd.h b/snesreader/libjma/btreecd.h new file mode 100644 index 00000000..acce3664 --- /dev/null +++ b/snesreader/libjma/btreecd.h @@ -0,0 +1,126 @@ +/* +Copyright (C) 2002 Andrea Mazzoleni ( http://advancemame.sf.net ) +Copyright (C) 2001-4 Igor Pavlov ( http://www.7-zip.org ) + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License version 2.1 as published by the Free Software Foundation. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef __BITTREECODER_H +#define __BITTREECODER_H + +#include "aribitcd.h" +#include "rcdefs.h" + + +////////////////////////// +// CBitTreeDecoder + +template +class CBitTreeDecoder +{ + CMyBitDecoder m_Models[1 << m_NumBitLevels]; +public: + void Init() + { + for(UINT32 i = 1; i < (1 << m_NumBitLevels); i++) + m_Models[i].Init(); + } + UINT32 Decode(CMyRangeDecoder *aRangeDecoder) + { + UINT32 aModelIndex = 1; + RC_INIT_VAR + for(UINT32 aBitIndex = m_NumBitLevels; aBitIndex > 0; aBitIndex--) + { + // aModelIndex = (aModelIndex << 1) + m_Models[aModelIndex].Decode(aRangeDecoder); + RC_GETBIT(aNumMoveBits, m_Models[aModelIndex].m_Probability, aModelIndex) + } + RC_FLUSH_VAR + return aModelIndex - (1 << m_NumBitLevels); + }; +}; + +//////////////////////////////// +// CReverseBitTreeDecoder + +template +class CReverseBitTreeDecoder2 +{ + CMyBitDecoder *m_Models; + UINT32 m_NumBitLevels; +public: + CReverseBitTreeDecoder2(): m_Models(0) { } + ~CReverseBitTreeDecoder2() { delete []m_Models; } + bool Create(UINT32 aNumBitLevels) + { + m_NumBitLevels = aNumBitLevels; + m_Models = new CMyBitDecoder[1 << aNumBitLevels]; + return (m_Models != 0); + } + void Init() + { + UINT32 aNumModels = 1 << m_NumBitLevels; + for(UINT32 i = 1; i < aNumModels; i++) + m_Models[i].Init(); + } + UINT32 Decode(CMyRangeDecoder *aRangeDecoder) + { + UINT32 aModelIndex = 1; + UINT32 aSymbol = 0; + RC_INIT_VAR + for(UINT32 aBitIndex = 0; aBitIndex < m_NumBitLevels; aBitIndex++) + { + // UINT32 aBit = m_Models[aModelIndex].Decode(aRangeDecoder); + // aModelIndex <<= 1; + // aModelIndex += aBit; + // aSymbol |= (aBit << aBitIndex); + RC_GETBIT2(aNumMoveBits, m_Models[aModelIndex].m_Probability, aModelIndex, ; , aSymbol |= (1 << aBitIndex)) + } + RC_FLUSH_VAR + return aSymbol; + }; +}; +//////////////////////////// +// CReverseBitTreeDecoder2 + +template +class CReverseBitTreeDecoder +{ + CMyBitDecoder m_Models[1 << m_NumBitLevels]; +public: + void Init() + { + for(UINT32 i = 1; i < (1 << m_NumBitLevels); i++) + m_Models[i].Init(); + } + UINT32 Decode(CMyRangeDecoder *aRangeDecoder) + { + UINT32 aModelIndex = 1; + UINT32 aSymbol = 0; + RC_INIT_VAR + for(UINT32 aBitIndex = 0; aBitIndex < m_NumBitLevels; aBitIndex++) + { + // UINT32 aBit = m_Models[aModelIndex].Decode(aRangeDecoder); + // aModelIndex <<= 1; + // aModelIndex += aBit; + // aSymbol |= (aBit << aBitIndex); + RC_GETBIT2(aNumMoveBits, m_Models[aModelIndex].m_Probability, aModelIndex, ; , aSymbol |= (1 << aBitIndex)) + } + RC_FLUSH_VAR + return aSymbol; + } +}; + + + +#endif diff --git a/snesreader/libjma/crc32.h b/snesreader/libjma/crc32.h new file mode 100644 index 00000000..876a7d3d --- /dev/null +++ b/snesreader/libjma/crc32.h @@ -0,0 +1,26 @@ +/* +Copyright (C) 2004-2007 NSRT Team ( http://nsrt.edgeemu.com ) + +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. + +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., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#ifndef CRC32_H +#define CRC32_H + +namespace CRC32lib +{ + unsigned int CRC32(const unsigned char *, size_t, register unsigned int crc32 = 0xFFFFFFFF); +} + +#endif diff --git a/snesreader/libjma/iiostrm.cpp b/snesreader/libjma/iiostrm.cpp new file mode 100644 index 00000000..f2719969 --- /dev/null +++ b/snesreader/libjma/iiostrm.cpp @@ -0,0 +1,132 @@ +/* +Copyright (C) 2005-2007 NSRT Team ( http://nsrt.edgeemu.com ) +Copyright (C) 2002 Andrea Mazzoleni ( http://advancemame.sf.net ) +Copyright (C) 2001-4 Igor Pavlov ( http://www.7-zip.org ) + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License version 2.1 as published by the Free Software Foundation. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "portable.h" +#include "iiostrm.h" +#include "crc32.h" + +HRESULT ISequentialInStream_Array::Read(void *aData, UINT32 aSize, UINT32 *aProcessedSize) +{ + if (aSize > size) + { + aSize = size; + } + + *aProcessedSize = aSize; + memcpy(aData, data, aSize); + size -= aSize; + data += aSize; + return(S_OK); +} + +HRESULT ISequentialOutStream_Array::Write(const void *aData, UINT32 aSize, UINT32 *aProcessedSize) +{ + if (aSize > size) + { + overflow = true; + aSize = size; + } + + *aProcessedSize = aSize; + memcpy(data, aData, aSize); + size -= aSize; + data += aSize; + total += aSize; + return(S_OK); +} + +HRESULT ISequentialInStream_String::Read(void *aData, UINT32 aSize, UINT32 *aProcessedSize) +{ + if (aSize > data.size()) + { + aSize = data.size(); + } + + *aProcessedSize = aSize; + memcpy(aData, data.c_str(), aSize); + data.erase(0, aSize); + return(S_OK); +} + +HRESULT ISequentialOutStream_String::Write(const void *aData, UINT32 aSize, UINT32 *aProcessedSize) +{ + *aProcessedSize = aSize; + data.append((const char *)aData, aSize); + total += aSize; + return(S_OK); +} + +HRESULT ISequentialInStream_Istream::Read(void *aData, UINT32 aSize, UINT32 *aProcessedSize) +{ + data.read((char *)aData, aSize); + *aProcessedSize = data.gcount(); + return(S_OK); +} + +HRESULT ISequentialOutStream_Ostream::Write(const void *aData, UINT32 aSize, UINT32 *aProcessedSize) +{ + *aProcessedSize = aSize; + data.write((char *)aData, aSize); + total += aSize; + return(S_OK); +} + + + +HRESULT ISequentialInStreamCRC32_Array::Read(void *aData, UINT32 aSize, UINT32 *aProcessedSize) +{ + ISequentialInStream_Array::Read(aData, aSize, aProcessedSize); + crc32 = CRC32lib::CRC32((const unsigned char *)aData, *aProcessedSize, ~crc32); + return(S_OK); +} + +HRESULT ISequentialOutStreamCRC32_Array::Write(const void *aData, UINT32 aSize, UINT32 *aProcessedSize) +{ + ISequentialOutStream_Array::Write(aData, aSize, aProcessedSize); + crc32 = CRC32lib::CRC32((const unsigned char *)aData, *aProcessedSize, ~crc32); + return(S_OK); +} + +HRESULT ISequentialInStreamCRC32_String::Read(void *aData, UINT32 aSize, UINT32 *aProcessedSize) +{ + ISequentialInStream_String::Read(aData, aSize, aProcessedSize); + crc32 = CRC32lib::CRC32((const unsigned char *)aData, *aProcessedSize, ~crc32); + return(S_OK); +} + +HRESULT ISequentialOutStreamCRC32_String::Write(const void *aData, UINT32 aSize, UINT32 *aProcessedSize) +{ + ISequentialOutStream_String::Write(aData, aSize, aProcessedSize); + crc32 = CRC32lib::CRC32((const unsigned char *)aData, *aProcessedSize, ~crc32); + return(S_OK); +} + +HRESULT ISequentialInStreamCRC32_Istream::Read(void *aData, UINT32 aSize, UINT32 *aProcessedSize) +{ + ISequentialInStream_Istream::Read(aData, aSize, aProcessedSize); + crc32 = CRC32lib::CRC32((const unsigned char *)aData, *aProcessedSize, ~crc32); + return(S_OK); +} + +HRESULT ISequentialOutStreamCRC32_Ostream::Write(const void *aData, UINT32 aSize, UINT32 *aProcessedSize) +{ + ISequentialOutStream_Ostream::Write(aData, aSize, aProcessedSize); + crc32 = CRC32lib::CRC32((const unsigned char *)aData, *aProcessedSize, ~crc32); + return(S_OK); +} diff --git a/snesreader/libjma/iiostrm.h b/snesreader/libjma/iiostrm.h new file mode 100644 index 00000000..a5b2ab20 --- /dev/null +++ b/snesreader/libjma/iiostrm.h @@ -0,0 +1,210 @@ +/* +Copyright (C) 2005-2007 NSRT Team ( http://nsrt.edgeemu.com ) +Copyright (C) 2002 Andrea Mazzoleni ( http://advancemame.sf.net ) +Copyright (C) 2001-4 Igor Pavlov ( http://www.7-zip.org ) + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License version 2.1 as published by the Free Software Foundation. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef __IINOUTSTREAMS_H +#define __IINOUTSTREAMS_H + +#include +#include + +#include "portable.h" + + +class ISequentialInStream +{ +public: + virtual HRESULT Read(void *, UINT32, UINT32 *) = 0; + + virtual ~ISequentialInStream() {} +}; + + +class ISequentialInStream_Array : public ISequentialInStream +{ + const char *data; + unsigned int size; +public: + ISequentialInStream_Array(const char *Adata, unsigned Asize) : data(Adata), size(Asize) { } + + HRESULT Read(void *aData, UINT32 aSize, UINT32 *aProcessedSize); + + virtual ~ISequentialInStream_Array() {} +}; + +class ISequentialInStream_String : public ISequentialInStream +{ + std::string& data; +public: + ISequentialInStream_String(std::string& Adata) : data(Adata) { } + + HRESULT Read(void *aData, UINT32 aSize, UINT32 *aProcessedSize); + + virtual ~ISequentialInStream_String() {} +}; + +class ISequentialInStream_Istream : public ISequentialInStream +{ + std::istream& data; +public: + ISequentialInStream_Istream(std::istream& Adata) : data(Adata) { } + + HRESULT Read(void *aData, UINT32 aSize, UINT32 *aProcessedSize); + + virtual ~ISequentialInStream_Istream() {} +}; + + + +class ISequentialOutStream +{ +public: + virtual bool overflow_get() const = 0; + virtual unsigned int size_get() const = 0; + + virtual HRESULT Write(const void *, UINT32, UINT32 *) = 0; + + virtual ~ISequentialOutStream() {} +}; + + +class ISequentialOutStream_Array : public ISequentialOutStream +{ + char *data; + unsigned int size; + bool overflow; + unsigned int total; +public: + ISequentialOutStream_Array(char *Adata, unsigned Asize) : data(Adata), size(Asize), overflow(false), total(0) { } + + bool overflow_get() const { return(overflow); } + unsigned int size_get() const { return(total); } + + HRESULT Write(const void *aData, UINT32 aSize, UINT32 *aProcessedSize); + + virtual ~ISequentialOutStream_Array() {} +}; + +class ISequentialOutStream_String : public ISequentialOutStream +{ + std::string& data; + unsigned int total; +public: + ISequentialOutStream_String(std::string& Adata) : data(Adata), total(0) { } + + bool overflow_get() const { return(false); } + unsigned int size_get() const { return(total); } + + HRESULT Write(const void *aData, UINT32 aSize, UINT32 *aProcessedSize); + + virtual ~ISequentialOutStream_String() {} +}; + + +class ISequentialOutStream_Ostream : public ISequentialOutStream +{ + std::ostream& data; + unsigned int total; +public: + ISequentialOutStream_Ostream(std::ostream& Adata) : data(Adata), total(0) { } + + bool overflow_get() const { return(false); } + unsigned int size_get() const { return(total); } + + HRESULT Write(const void *aData, UINT32 aSize, UINT32 *aProcessedSize); + + virtual ~ISequentialOutStream_Ostream() {} +}; + + + +class ISequentialStreamCRC32 +{ +protected: + unsigned int crc32; +public: + ISequentialStreamCRC32() : crc32(0) {} + unsigned int crc32_get() const { return(crc32); } + + virtual ~ISequentialStreamCRC32() {} +}; + + +class ISequentialInStreamCRC32_Array : public ISequentialInStream_Array, public ISequentialStreamCRC32 +{ +public: + ISequentialInStreamCRC32_Array(const char *Adata, unsigned Asize) : ISequentialInStream_Array(Adata, Asize) { } + + HRESULT Read(void *aData, UINT32 aSize, UINT32 *aProcessedSize); + + virtual ~ISequentialInStreamCRC32_Array() {} +}; + +class ISequentialInStreamCRC32_String : public ISequentialInStream_String, public ISequentialStreamCRC32 +{ +public: + ISequentialInStreamCRC32_String(std::string& Adata) : ISequentialInStream_String(Adata) { } + + HRESULT Read(void *aData, UINT32 aSize, UINT32 *aProcessedSize); + + virtual ~ISequentialInStreamCRC32_String() {} +}; + +class ISequentialInStreamCRC32_Istream : public ISequentialInStream_Istream, public ISequentialStreamCRC32 +{ +public: + ISequentialInStreamCRC32_Istream(std::istream& Adata) : ISequentialInStream_Istream(Adata) { } + + HRESULT Read(void *aData, UINT32 aSize, UINT32 *aProcessedSize); + + virtual ~ISequentialInStreamCRC32_Istream() {} +}; + + +class ISequentialOutStreamCRC32_Array : public ISequentialOutStream_Array, public ISequentialStreamCRC32 +{ +public: + ISequentialOutStreamCRC32_Array(char *Adata, unsigned Asize) : ISequentialOutStream_Array(Adata, Asize) { } + + HRESULT Write(const void *aData, UINT32 aSize, UINT32 *aProcessedSize); + + virtual ~ISequentialOutStreamCRC32_Array() {} +}; + +class ISequentialOutStreamCRC32_String : public ISequentialOutStream_String, public ISequentialStreamCRC32 +{ +public: + ISequentialOutStreamCRC32_String(std::string& Adata) : ISequentialOutStream_String(Adata) { } + + HRESULT Write(const void *aData, UINT32 aSize, UINT32 *aProcessedSize); + + virtual ~ISequentialOutStreamCRC32_String() {} +}; + + +class ISequentialOutStreamCRC32_Ostream : public ISequentialOutStream_Ostream, public ISequentialStreamCRC32 +{ +public: + ISequentialOutStreamCRC32_Ostream(std::ostream& Adata) : ISequentialOutStream_Ostream(Adata) { } + + HRESULT Write(const void *aData, UINT32 aSize, UINT32 *aProcessedSize); + + virtual ~ISequentialOutStreamCRC32_Ostream() {} +}; + +#endif diff --git a/snesreader/libjma/inbyte.cpp b/snesreader/libjma/inbyte.cpp new file mode 100644 index 00000000..c727a4b2 --- /dev/null +++ b/snesreader/libjma/inbyte.cpp @@ -0,0 +1,60 @@ +/* +Copyright (C) 2005-2007 NSRT Team ( http://nsrt.edgeemu.com ) +Copyright (C) 2002 Andrea Mazzoleni ( http://advancemame.sf.net ) +Copyright (C) 2001-4 Igor Pavlov ( http://www.7-zip.org ) + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License version 2.1 as published by the Free Software Foundation. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "inbyte.h" + +namespace NStream{ + +CInByte::CInByte(UINT32 aBufferSize): + m_BufferBase(0), + m_BufferSize(aBufferSize) +{ + m_BufferBase = new BYTE[m_BufferSize]; +} + +CInByte::~CInByte() +{ + delete []m_BufferBase; +} + +void CInByte::Init(ISequentialInStream *aStream) +{ + m_Stream = aStream; + m_ProcessedSize = 0; + m_Buffer = m_BufferBase; + m_BufferLimit = m_Buffer; + m_StreamWasExhausted = false; +} + +bool CInByte::ReadBlock() +{ + if (m_StreamWasExhausted) + return false; + m_ProcessedSize += (m_Buffer - m_BufferBase); + UINT32 aNumProcessedBytes; + HRESULT aResult = m_Stream->Read(m_BufferBase, m_BufferSize, &aNumProcessedBytes); + if (aResult != S_OK) + throw aResult; + m_Buffer = m_BufferBase; + m_BufferLimit = m_Buffer + aNumProcessedBytes; + m_StreamWasExhausted = (aNumProcessedBytes == 0); + return (!m_StreamWasExhausted); +} + +} diff --git a/snesreader/libjma/inbyte.h b/snesreader/libjma/inbyte.h new file mode 100644 index 00000000..53afa171 --- /dev/null +++ b/snesreader/libjma/inbyte.h @@ -0,0 +1,76 @@ +/* +Copyright (C) 2002 Andrea Mazzoleni ( http://advancemame.sf.net ) +Copyright (C) 2001-4 Igor Pavlov ( http://www.7-zip.org ) + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License version 2.1 as published by the Free Software Foundation. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef __STREAM_INBYTE_H +#define __STREAM_INBYTE_H + +#include "iiostrm.h" + +namespace NStream { + +class CInByte +{ + UINT64 m_ProcessedSize; + BYTE *m_BufferBase; + UINT32 m_BufferSize; + BYTE *m_Buffer; + BYTE *m_BufferLimit; + ISequentialInStream* m_Stream; + bool m_StreamWasExhausted; + + bool ReadBlock(); + +public: + CInByte(UINT32 aBufferSize = 0x100000); + ~CInByte(); + + void Init(ISequentialInStream *aStream); + + bool ReadByte(BYTE &aByte) + { + if(m_Buffer >= m_BufferLimit) + if(!ReadBlock()) + return false; + aByte = *m_Buffer++; + return true; + } + BYTE ReadByte() + { + if(m_Buffer >= m_BufferLimit) + if(!ReadBlock()) + return 0x0; + return *m_Buffer++; + } + void ReadBytes(void *aData, UINT32 aSize, UINT32 &aProcessedSize) + { + for(aProcessedSize = 0; aProcessedSize < aSize; aProcessedSize++) + if (!ReadByte(((BYTE *)aData)[aProcessedSize])) + return; + } + bool ReadBytes(void *aData, UINT32 aSize) + { + UINT32 aProcessedSize; + ReadBytes(aData, aSize, aProcessedSize); + return (aProcessedSize == aSize); + } + UINT64 GetProcessedSize() const { return m_ProcessedSize + (m_Buffer - m_BufferBase); } +}; + +} + +#endif diff --git a/snesreader/libjma/jcrc32.cpp b/snesreader/libjma/jcrc32.cpp new file mode 100644 index 00000000..e3377d58 --- /dev/null +++ b/snesreader/libjma/jcrc32.cpp @@ -0,0 +1,80 @@ +/* +Copyright (C) 2004-2007 NSRT Team ( http://nsrt.edgeemu.com ) + +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. + +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., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include + +namespace CRC32lib +{ + //Don't ask questions, this is the PKZip CRC32 table + const unsigned int crc32Table[256] = { + 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, + 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, + 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2, + 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, + 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, + 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, + 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c, + 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, + 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, + 0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, + 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106, + 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, + 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, + 0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, + 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, + 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, + 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, + 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, + 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, + 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, + 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, + 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, + 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84, + 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, + 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, + 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, + 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e, + 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, + 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, + 0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, + 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28, + 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, + 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, + 0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, + 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242, + 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, + 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, + 0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, + 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, + 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, + 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, + 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, + 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d }; + + + //CRC32 for char arrays + unsigned int CRC32(const unsigned char *array, size_t size, register unsigned int crc32) + { + const unsigned char *end_p = array+size; + for (register const unsigned char *p = array; p < end_p; p++) + { + crc32 = ((crc32 >> 8) & 0x00FFFFFF) ^ crc32Table[(crc32 ^ *p) & 0xFF]; + } + + return(~crc32); + } +} diff --git a/snesreader/libjma/jma.cpp b/snesreader/libjma/jma.cpp new file mode 100644 index 00000000..87e03228 --- /dev/null +++ b/snesreader/libjma/jma.cpp @@ -0,0 +1,550 @@ +/* +Copyright (C) 2005-2007 NSRT Team ( http://nsrt.edgeemu.com ) + +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. + +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., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#include +#include "jma.h" +using namespace std; + +#include "portable.h" +#include "7z.h" +#include "crc32.h" + +namespace JMA +{ + const char jma_magic[] = { 'J', 'M', 'A', 0, 'N' }; + const unsigned int jma_header_length = 5; + const unsigned char jma_version = 1; + const unsigned int jma_version_length = 1; + const unsigned int jma_total_header_length = jma_header_length + jma_version_length + UINT_SIZE; + + //Convert DOS/zip/JMA integer time to to time_t + time_t uint_to_time(unsigned short date, unsigned short time) + { + tm formatted_time; + + formatted_time.tm_mday = date & 0x1F; + formatted_time.tm_mon = ((date >> 5) & 0xF) - 1; + formatted_time.tm_year = ((date >> 9) & 0x7f) + 80; + formatted_time.tm_sec = (time & 0x1F) * 2; + formatted_time.tm_min = (time >> 5) & 0x3F; + formatted_time.tm_hour = (time >> 11) & 0x1F; + + return(mktime(&formatted_time)); + } + + + //Retreive the file block, what else? + void jma_open::retrieve_file_block() throw(jma_errors) + { + unsigned char uint_buffer[UINT_SIZE]; + unsigned char ushort_buffer[USHORT_SIZE]; + + //File block size is the last UINT in the file + stream.seekg(-UINT_SIZE,ios::end); + stream.read((char *)uint_buffer, UINT_SIZE); + size_t file_block_size = charp_to_uint(uint_buffer); + + //Currently at the end of the file, so that's the file size + size_t jma_file_size = stream.tellg(); + + //The file block can't be larger than the JMA file without it's header. + //This if can probably be improved + if (file_block_size >= jma_file_size-jma_total_header_length) + { + throw(JMA_BAD_FILE); + } + + //Seek to before file block so we can read the file block + stream.seekg(-((int)file_block_size+UINT_SIZE),ios::end); + + //This is needed if the file block is compressed + stringstream decompressed_file_block; + //Pointer to where to read file block from (file or decompressed buffer) + istream *file_block_stream; + + //Setup file info buffer and byte to read with + jma_file_info file_info; + char byte; + + stream.get(byte); + if (!byte) //If file block is compressed + { + //Compressed size isn't counting the byte we just read or the UINT for compressed size + size_t compressed_size = file_block_size - (1+UINT_SIZE); + + //Read decompressed size / true file block size + stream.read((char *)uint_buffer, UINT_SIZE); + file_block_size = charp_to_uint(uint_buffer); + + //Setup access methods for decompression + ISequentialInStream_Istream compressed_data(stream); + ISequentialOutStream_Ostream decompressed_data(decompressed_file_block); + + //Decompress the data + if (!decompress_lzma_7z(compressed_data, compressed_size, decompressed_data, file_block_size)) + { + throw(JMA_DECOMPRESS_FAILED); + } + + //Go to beginning, setup pointer to buffer + decompressed_file_block.seekg(0, ios::beg); + file_block_stream = &decompressed_file_block; + } + else + { + stream.putback(byte); //Putback byte, byte is part of filename, not compressed indicator + file_block_stream = &stream; + } + + + //Minimum file name length is 2 bytes, a char and a null + //Minimum comment length is 1 byte, a null + //There are currently 2 UINTs and 2 USHORTs per file + while (file_block_size >= 2+1+UINT_SIZE*2+USHORT_SIZE*2) //This does allow for a gap, but that's okay + { + //First stored in the file block is the file name null terminated + file_info.name = ""; + + file_block_stream->get(byte); + while (byte) + { + file_info.name += byte; + file_block_stream->get(byte); + } + + //There must be a file name or the file is bad + if (!file_info.name.length()) + { + throw(JMA_BAD_FILE); + } + + //Same trick as above for the comment + file_info.comment = ""; + + file_block_stream->get(byte); + while (byte) + { + file_info.comment += byte; + file_block_stream->get(byte); + } + + //Next is a UINT representing the file's size + file_block_stream->read((char *)uint_buffer, UINT_SIZE); + file_info.size = charp_to_uint(uint_buffer); + + //Followed by CRC32 + file_block_stream->read((char *)uint_buffer, UINT_SIZE); + file_info.crc32 = charp_to_uint(uint_buffer); + + //Special USHORT representation of file's date + file_block_stream->read((char *)ushort_buffer, USHORT_SIZE); + file_info.date = charp_to_ushort(ushort_buffer); + + //Special USHORT representation of file's time + file_block_stream->read((char *)ushort_buffer, USHORT_SIZE); + file_info.time = charp_to_ushort(ushort_buffer); + + file_info.buffer = 0; //Pointing to null till we decompress files + + files.push_back(file_info); //Put file info into our structure + + //Subtract size of the file info we just read + file_block_size -= file_info.name.length()+file_info.comment.length()+2+UINT_SIZE*2+USHORT_SIZE*2; + } + } + + //Constructor for opening JMA files for reading + jma_open::jma_open(const char *compressed_file_name) throw (jma_errors) + { + decompressed_buffer = 0; + compressed_buffer = 0; + + stream.open(compressed_file_name, ios::in | ios::binary); + if (!stream.is_open()) + { + throw(JMA_NO_OPEN); + } + + //Header is "JMA\0N" + unsigned char header[jma_header_length]; + stream.read((char *)header, jma_header_length); + if (memcmp(jma_magic, header, jma_header_length)) + { + throw(JMA_BAD_FILE); + } + + //Not the cleanest code but logical + stream.read((char *)header, 5); + if (*header <= jma_version) + { + chunk_size = charp_to_uint(header+1); //Chunk size is a UINT that follows version # + retrieve_file_block(); + } + else + { + throw(JMA_UNSUPPORTED_VERSION); + } + } + + //Destructor only has to close the stream if neccesary + jma_open::~jma_open() + { + if (stream.is_open()) + { + stream.close(); + } + } + + //Return a vector containing useful info about the files in the JMA + vector jma_open::get_files_info() + { + vector file_info_vector; + jma_public_file_info file_info; + + for (vector::iterator i = files.begin(); i != files.end(); i++) + { + file_info.name = i->name; + file_info.comment = i->comment; + file_info.size = i->size; + file_info.datetime = uint_to_time(i->date, i->time); + file_info.crc32 = i->crc32; + file_info_vector.push_back(file_info); + } + + return(file_info_vector); + } + + //Skip forward a given number of chunks + void jma_open::chunk_seek(unsigned int chunk_num) throw(jma_errors) + { + //Check the stream is open + if (!stream.is_open()) + { + throw(JMA_NO_OPEN); + } + + //Clear possible errors so the seek will work + stream.clear(); + + //Move forward over header + stream.seekg(jma_total_header_length, ios::beg); + + unsigned char int4_buffer[UINT_SIZE]; + + while (chunk_num--) + { + //Read in size of chunk + stream.read((char *)int4_buffer, UINT_SIZE); + + //Skip chunk plus it's CRC32 + stream.seekg(charp_to_uint(int4_buffer)+UINT_SIZE, ios::cur); + } + } + + //Return a vector of pointers to each file in the JMA, the buffer to hold all the files + //must be initilized outside. + vector jma_open::get_all_files(unsigned char *buffer) throw(jma_errors) + { + //If there's no stream we can't read from it, so exit + if (!stream.is_open()) + { + throw(JMA_NO_OPEN); + } + + //Seek to the first chunk + chunk_seek(0); + + //Set the buffer that decompressed data goes to + decompressed_buffer = buffer; + + //If the JMA is not solid + if (chunk_size) + { + unsigned char int4_buffer[UINT_SIZE]; + size_t size = get_total_size(files); + + //For each chunk in the file... + for (size_t remaining_size = size; remaining_size; remaining_size -= chunk_size) + { + //Read the compressed size + stream.read((char *)int4_buffer, UINT_SIZE); + size_t compressed_size = charp_to_uint(int4_buffer); + + //Allocate memory of the correct size to hold the compressed data in the JMA + //Throw error on failure as that is unrecoverable from + try + { + compressed_buffer = new unsigned char[compressed_size]; + } + catch (bad_alloc xa) + { + throw(JMA_NO_MEM_ALLOC); + } + + //Read all the compressed data in + stream.read((char *)compressed_buffer, compressed_size); + + //Read the expected CRC of compressed data from the file + stream.read((char *)int4_buffer, UINT_SIZE); + + //If it doesn't match, throw error and cleanup memory + if (CRC32lib::CRC32(compressed_buffer, compressed_size) != charp_to_uint(int4_buffer)) + { + delete[] compressed_buffer; + throw(JMA_BAD_FILE); + } + + //Decompress the data, cleanup memory on failure + if (!decompress_lzma_7z(compressed_buffer, compressed_size, + decompressed_buffer+size-remaining_size, + (remaining_size > chunk_size) ? chunk_size : remaining_size)) + { + delete[] compressed_buffer; + throw(JMA_DECOMPRESS_FAILED); + } + delete[] compressed_buffer; + + if (remaining_size <= chunk_size) //If we just decompressed the remainder + { + break; + } + } + } + else //Solidly compressed JMA + { + unsigned char int4_buffer[UINT_SIZE]; + + //Read the size of the compressed data + stream.read((char *)int4_buffer, UINT_SIZE); + size_t compressed_size = charp_to_uint(int4_buffer); + + //Get decompressed size + size_t size = get_total_size(files); + + //Setup access methods for decompression + ISequentialInStream_Istream compressed_data(stream); + ISequentialOutStream_Array decompressed_data(reinterpret_cast(decompressed_buffer), size); + + //Decompress the data + if (!decompress_lzma_7z(compressed_data, compressed_size, decompressed_data, size)) + { + throw(JMA_DECOMPRESS_FAILED); + } + + /* + //Allocate memory of the right size to hold the compressed data in the JMA + try + { + compressed_buffer = new unsigned char[compressed_size]; + } + catch (bad_alloc xa) + { + throw(JMA_NO_MEM_ALLOC); + } + + //Copy the compressed data into memory + stream.read((char *)compressed_buffer, compressed_size); + size_t size = get_total_size(files); + + //Read the CRC of the compressed data + stream.read((char *)int4_buffer, UINT_SIZE); + + //If it doesn't match, complain + if (CRC32lib::CRC32(compressed_buffer, compressed_size) != charp_to_uint(int4_buffer)) + { + delete[] compressed_buffer; + throw(JMA_BAD_FILE); + } + + //Decompress the data + if (!decompress_lzma_7z(compressed_buffer, compressed_size, decompressed_buffer, size)) + { + delete[] compressed_buffer; + throw(JMA_DECOMPRESS_FAILED); + } + delete[] compressed_buffer; + */ + } + + vector file_pointers; + size_t size = 0; + + //For each file, add it's pointer to the vector, size is pointer offset in the buffer + for (vector::iterator i = files.begin(); i != files.end(); i++) + { + i->buffer = decompressed_buffer+size; + file_pointers.push_back(decompressed_buffer+size); + size += i->size; + } + + //Return the vector of pointers + return(file_pointers); + } + + //Extracts the file with a given name found in the archive to the given buffer + void jma_open::extract_file(string& name, unsigned char *buffer) throw(jma_errors) + { + if (!stream.is_open()) + { + throw(JMA_NO_OPEN); + } + + size_t size_to_skip = 0; + size_t our_file_size = 0; + + //Search through the vector of file information + for (vector::iterator i = files.begin(); i != files.end(); i++) + { + if (i->name == name) + { + //Set the variable so we can tell we found it + our_file_size = i->size; + break; + } + + //Keep a running total of size + size_to_skip += i->size; + } + + if (!our_file_size) //File with the specified name was not found in the archive + { + throw(JMA_FILE_NOT_FOUND); + } + + //If the JMA only contains one file, we can skip a lot of overhead + if (files.size() == 1) + { + get_all_files(buffer); + return; + } + + if (chunk_size) //we are using non-solid archive.. + { + unsigned int chunks_to_skip = size_to_skip / chunk_size; + + //skip over requisite number of chunks + chunk_seek(chunks_to_skip); + + //Allocate memory for compressed and decompressed data + unsigned char *comp_buffer = 0, *decomp_buffer = 0; + try + { + //Compressed data size is <= non compressed size + unsigned char *combined_buffer = new unsigned char[chunk_size*2]; + comp_buffer = combined_buffer; + decomp_buffer = combined_buffer+chunk_size; + } + catch (bad_alloc xa) + { + throw(JMA_NO_MEM_ALLOC); + } + + size_t first_chunk_offset = size_to_skip % chunk_size; + unsigned char int4_buffer[UINT_SIZE]; + for (size_t i = 0; i < our_file_size;) + { + //Get size + stream.read((char *)int4_buffer, UINT_SIZE); + size_t compressed_size = charp_to_uint(int4_buffer); + + //Read all the compressed data in + stream.read((char *)comp_buffer, compressed_size); + + //Read the CRC of the compressed data + stream.read((char *)int4_buffer, UINT_SIZE); + + //If it doesn't match, complain + if (CRC32lib::CRC32(comp_buffer, compressed_size) != charp_to_uint(int4_buffer)) + { + delete[] comp_buffer; + throw(JMA_BAD_FILE); + } + + //Decompress chunk + if (!decompress_lzma_7z(comp_buffer, compressed_size, decomp_buffer, chunk_size)) + { + delete[] comp_buffer; + throw(JMA_DECOMPRESS_FAILED); + } + + size_t copy_amount = our_file_size-i > chunk_size-first_chunk_offset ? chunk_size-first_chunk_offset : our_file_size-i; + + memcpy(buffer+i, decomp_buffer+first_chunk_offset, copy_amount); + first_chunk_offset = 0; //Set to zero since this is only for the first iteration + i += copy_amount; + } + delete[] comp_buffer; + } + else //Solid JMA + { + unsigned char *decomp_buffer = 0; + try + { + decomp_buffer = new unsigned char[get_total_size(files)]; + } + catch (bad_alloc xa) + { + throw(JMA_NO_MEM_ALLOC); + } + + get_all_files(decomp_buffer); + + memcpy(buffer, decomp_buffer+size_to_skip, our_file_size); + + delete[] decomp_buffer; + } + } + + bool jma_open::is_solid() + { + return(chunk_size ? false : true); + } + + const char *jma_error_text(jma_errors error) + { + switch (error) + { + case JMA_NO_CREATE: + return("JMA could not be created"); + + case JMA_NO_MEM_ALLOC: + return("Memory for JMA could be allocated"); + + case JMA_NO_OPEN: + return("JMA could not be opened"); + + case JMA_BAD_FILE: + return("Invalid/Corrupt JMA"); + + case JMA_UNSUPPORTED_VERSION: + return("JMA version not supported"); + + case JMA_COMPRESS_FAILED: + return("JMA compression failed"); + + case JMA_DECOMPRESS_FAILED: + return("JMA decompression failed"); + + case JMA_FILE_NOT_FOUND: + return("File not found in JMA"); + } + return("Unknown error"); + } + +} + + diff --git a/snesreader/libjma/jma.h b/snesreader/libjma/jma.h new file mode 100644 index 00000000..2aaa5ca1 --- /dev/null +++ b/snesreader/libjma/jma.h @@ -0,0 +1,88 @@ +/* +Copyright (C) 2005-2007 NSRT Team ( http://nsrt.edgeemu.com ) + +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. + +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., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#ifndef JMA_H +#define JMA_H + +#include +#include +#include +#include + +namespace JMA +{ + enum jma_errors { JMA_NO_CREATE, JMA_NO_MEM_ALLOC, JMA_NO_OPEN, JMA_BAD_FILE, + JMA_UNSUPPORTED_VERSION, JMA_COMPRESS_FAILED, JMA_DECOMPRESS_FAILED, + JMA_FILE_NOT_FOUND }; + + struct jma_file_info_base + { + std::string name; + std::string comment; + size_t size; + unsigned int crc32; + }; + + struct jma_public_file_info : jma_file_info_base + { + time_t datetime; + }; + + struct jma_file_info : jma_file_info_base + { + unsigned short date; + unsigned short time; + const unsigned char *buffer; + }; + + template + inline size_t get_total_size(std::vector& files) + { + size_t size = 0; + for (typename std::vector::iterator i = files.begin(); i != files.end(); i++) + { + size += i->size; //We do have a problem if this wraps around + } + + return(size); + } + + class jma_open + { + public: + jma_open(const char *) throw(jma_errors); + ~jma_open(); + + std::vector get_files_info(); + std::vector get_all_files(unsigned char *) throw(jma_errors); + void extract_file(std::string& name, unsigned char *) throw(jma_errors); + bool is_solid(); + + private: + std::ifstream stream; + std::vector files; + size_t chunk_size; + unsigned char *decompressed_buffer; + unsigned char *compressed_buffer; + + void chunk_seek(unsigned int) throw(jma_errors); + void retrieve_file_block() throw(jma_errors); + }; + + const char *jma_error_text(jma_errors); +} +#endif diff --git a/snesreader/libjma/lencoder.h b/snesreader/libjma/lencoder.h new file mode 100644 index 00000000..6f30e478 --- /dev/null +++ b/snesreader/libjma/lencoder.h @@ -0,0 +1,93 @@ +/* +Copyright (C) 2005-2007 NSRT Team ( http://nsrt.edgeemu.com ) +Copyright (C) 2002 Andrea Mazzoleni ( http://advancemame.sf.net ) +Copyright (C) 2001-4 Igor Pavlov ( http://www.7-zip.org ) + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License version 2.1 as published by the Free Software Foundation. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef __LENCODER_H +#define __LENCODER_H + +#include "btreecd.h" + +namespace NLength { + +const UINT32 kNumPosStatesBitsMax = 4; +const int kNumPosStatesMax = (1 << kNumPosStatesBitsMax); + + +const int kNumPosStatesBitsEncodingMax = 4; +const int kNumPosStatesEncodingMax = (1 << kNumPosStatesBitsEncodingMax); + + +const int kNumMoveBits = 5; + +const int kNumLenBits = 3; +const int kNumLowSymbols = 1 << kNumLenBits; +const int kNumMidBits = 3; +const int kNumMidSymbols = 1 << kNumMidBits; + +const int kNumHighBits = 8; + +const int kNumSymbolsTotal = kNumLowSymbols + kNumMidSymbols + (1 << kNumHighBits); + +const int kNumSpecSymbols = kNumLowSymbols + kNumMidSymbols; + +class CDecoder +{ + CMyBitDecoder m_Choice; + CBitTreeDecoder m_LowCoder[kNumPosStatesMax]; + CMyBitDecoder m_Choice2; + CBitTreeDecoder m_MidCoder[kNumPosStatesMax]; + CBitTreeDecoder m_HighCoder; + UINT32 m_NumPosStates; +public: + void Create(UINT32 aNumPosStates) + { m_NumPosStates = aNumPosStates; } + void Init() + { + m_Choice.Init(); + for (UINT32 aPosState = 0; aPosState < m_NumPosStates; aPosState++) + { + m_LowCoder[aPosState].Init(); + m_MidCoder[aPosState].Init(); + } + m_Choice2.Init(); + m_HighCoder.Init(); + } + UINT32 Decode(CMyRangeDecoder *aRangeDecoder, UINT32 aPosState) + { + if(m_Choice.Decode(aRangeDecoder) == 0) + return m_LowCoder[aPosState].Decode(aRangeDecoder); + else + { + UINT32 aSymbol = kNumLowSymbols; + if(m_Choice2.Decode(aRangeDecoder) == 0) + aSymbol += m_MidCoder[aPosState].Decode(aRangeDecoder); + else + { + aSymbol += kNumMidSymbols; + aSymbol += m_HighCoder.Decode(aRangeDecoder); + } + return aSymbol; + } + } + +}; + +} + + +#endif diff --git a/snesreader/libjma/litcoder.h b/snesreader/libjma/litcoder.h new file mode 100644 index 00000000..639d6c55 --- /dev/null +++ b/snesreader/libjma/litcoder.h @@ -0,0 +1,122 @@ +/* +Copyright (C) 2002 Andrea Mazzoleni ( http://advancemame.sf.net ) +Copyright (C) 2001-4 Igor Pavlov ( http://www.7-zip.org ) + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License version 2.1 as published by the Free Software Foundation. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef __LITERALCODER_H +#define __LITERALCODER_H + +#include "aribitcd.h" +#include "rcdefs.h" + +namespace NLiteral { + +const int kNumMoveBits = 5; + +class CDecoder2 +{ + CMyBitDecoder m_Decoders[3][1 << 8]; +public: + void Init() + { + for (int i = 0; i < 3; i++) + for (int j = 1; j < (1 << 8); j++) + m_Decoders[i][j].Init(); + } + + BYTE DecodeNormal(CMyRangeDecoder *aRangeDecoder) + { + UINT32 aSymbol = 1; + RC_INIT_VAR + do + { + // aSymbol = (aSymbol << 1) | m_Decoders[0][aSymbol].Decode(aRangeDecoder); + RC_GETBIT(kNumMoveBits, m_Decoders[0][aSymbol].m_Probability, aSymbol) + } + while (aSymbol < 0x100); + RC_FLUSH_VAR + return aSymbol; + } + + BYTE DecodeWithMatchByte(CMyRangeDecoder *aRangeDecoder, BYTE aMatchByte) + { + UINT32 aSymbol = 1; + RC_INIT_VAR + do + { + UINT32 aMatchBit = (aMatchByte >> 7) & 1; + aMatchByte <<= 1; + // UINT32 aBit = m_Decoders[1 + aMatchBit][aSymbol].Decode(aRangeDecoder); + // aSymbol = (aSymbol << 1) | aBit; + UINT32 aBit; + RC_GETBIT2(kNumMoveBits, m_Decoders[1 + aMatchBit][aSymbol].m_Probability, aSymbol, + aBit = 0, aBit = 1) + if (aMatchBit != aBit) + { + while (aSymbol < 0x100) + { + // aSymbol = (aSymbol << 1) | m_Decoders[0][aSymbol].Decode(aRangeDecoder); + RC_GETBIT(kNumMoveBits, m_Decoders[0][aSymbol].m_Probability, aSymbol) + } + break; + } + } + while (aSymbol < 0x100); + RC_FLUSH_VAR + return aSymbol; + } +}; + +class CDecoder +{ + CDecoder2 *m_Coders; + UINT32 m_NumPrevBits; + UINT32 m_NumPosBits; + UINT32 m_PosMask; +public: + CDecoder(): m_Coders(0) {} + ~CDecoder() { Free(); } + void Free() + { + delete []m_Coders; + m_Coders = 0; + } + void Create(UINT32 aNumPosBits, UINT32 aNumPrevBits) + { + Free(); + m_NumPosBits = aNumPosBits; + m_PosMask = (1 << aNumPosBits) - 1; + m_NumPrevBits = aNumPrevBits; + UINT32 aNumStates = 1 << (m_NumPrevBits + m_NumPosBits); + m_Coders = new CDecoder2[aNumStates]; + } + void Init() + { + UINT32 aNumStates = 1 << (m_NumPrevBits + m_NumPosBits); + for (UINT32 i = 0; i < aNumStates; i++) + m_Coders[i].Init(); + } + UINT32 GetState(UINT32 aPos, BYTE aPrevByte) const + { return ((aPos & m_PosMask) << m_NumPrevBits) + (aPrevByte >> (8 - m_NumPrevBits)); } + BYTE DecodeNormal(CMyRangeDecoder *aRangeDecoder, UINT32 aPos, BYTE aPrevByte) + { return m_Coders[GetState(aPos, aPrevByte)].DecodeNormal(aRangeDecoder); } + BYTE DecodeWithMatchByte(CMyRangeDecoder *aRangeDecoder, UINT32 aPos, BYTE aPrevByte, BYTE aMatchByte) + { return m_Coders[GetState(aPos, aPrevByte)].DecodeWithMatchByte(aRangeDecoder, aMatchByte); } +}; + +} + +#endif diff --git a/snesreader/libjma/lzma.cpp b/snesreader/libjma/lzma.cpp new file mode 100644 index 00000000..d020ed27 --- /dev/null +++ b/snesreader/libjma/lzma.cpp @@ -0,0 +1,41 @@ +/* +Copyright (C) 2002 Andrea Mazzoleni ( http://advancemame.sf.net ) +Copyright (C) 2001-4 Igor Pavlov ( http://www.7-zip.org ) + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License version 2.1 as published by the Free Software Foundation. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "lzma.h" + +namespace NCompress { +namespace NLZMA { + +UINT32 kDistStart[kDistTableSizeMax]; + +static class CConstInit +{ +public: + CConstInit() + { + UINT32 aStartValue = 0; + int i; + for (i = 0; i < kDistTableSizeMax; i++) + { + kDistStart[i] = aStartValue; + aStartValue += (1 << kDistDirectBits[i]); + } + } +} g_ConstInit; + +}} diff --git a/snesreader/libjma/lzma.h b/snesreader/libjma/lzma.h new file mode 100644 index 00000000..949b70b3 --- /dev/null +++ b/snesreader/libjma/lzma.h @@ -0,0 +1,124 @@ +/* +Copyright (C) 2005-2007 NSRT Team ( http://nsrt.edgeemu.com ) +Copyright (C) 2002 Andrea Mazzoleni ( http://advancemame.sf.net ) +Copyright (C) 2001-4 Igor Pavlov ( http://www.7-zip.org ) + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License version 2.1 as published by the Free Software Foundation. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "lencoder.h" + +#ifndef __LZMA_H +#define __LZMA_H + +namespace NCompress { +namespace NLZMA { + +const UINT32 kNumRepDistances = 4; + +const BYTE kNumStates = 12; + +const BYTE kLiteralNextStates[kNumStates] = {0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 4, 5}; +const BYTE kMatchNextStates[kNumStates] = {7, 7, 7, 7, 7, 7, 7, 10, 10, 10, 10, 10}; +const BYTE kRepNextStates[kNumStates] = {8, 8, 8, 8, 8, 8, 8, 11, 11, 11, 11, 11}; +const BYTE kShortRepNextStates[kNumStates]= {9, 9, 9, 9, 9, 9, 9, 11, 11, 11, 11, 11}; + +class CState +{ +public: + BYTE m_Index; + void Init() + { m_Index = 0; } + void UpdateChar() + { m_Index = kLiteralNextStates[m_Index]; } + void UpdateMatch() + { m_Index = kMatchNextStates[m_Index]; } + void UpdateRep() + { m_Index = kRepNextStates[m_Index]; } + void UpdateShortRep() + { m_Index = kShortRepNextStates[m_Index]; } +}; + +class CBaseCoder +{ +protected: + CState m_State; + BYTE m_PreviousByte; + bool m_PeviousIsMatch; + UINT32 m_RepDistances[kNumRepDistances]; + void Init() + { + m_State.Init(); + m_PreviousByte = 0; + m_PeviousIsMatch = false; + for(UINT32 i = 0 ; i < kNumRepDistances; i++) + m_RepDistances[i] = 0; + } +}; + +const int kNumPosSlotBits = 6; +const int kDicLogSizeMax = 28; +const int kDistTableSizeMax = kDicLogSizeMax * 2; + +extern UINT32 kDistStart[kDistTableSizeMax]; +const BYTE kDistDirectBits[kDistTableSizeMax] = +{ + 0, 0, 0, 0, 1, 1, 2, 2, 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, 16, 17, 17, 18, 18, 19, 19, + 20, 20, 21, 21, 22, 22, 23, 23, 24, 24, 25, 25, 26, 26 +}; + +const UINT32 kNumLenToPosStates = 4; +inline UINT32 GetLenToPosState(UINT32 aLen) +{ + aLen -= 2; + if (aLen < kNumLenToPosStates) + return aLen; + return kNumLenToPosStates - 1; +} + +const int kMatchMinLen = 2; + +const int kMatchMaxLen = kMatchMinLen + NLength::kNumSymbolsTotal - 1; + +const int kNumAlignBits = 4; +const int kAlignTableSize = 1 << kNumAlignBits; +const UINT32 kAlignMask = (kAlignTableSize - 1); + +const int kStartPosModelIndex = 4; +const int kEndPosModelIndex = 14; +const int kNumPosModels = kEndPosModelIndex - kStartPosModelIndex; + +const int kNumFullDistances = 1 << (kEndPosModelIndex / 2); + + +const int kMainChoiceLiteralIndex = 0; +const int kMainChoiceMatchIndex = 1; + +const int kMatchChoiceDistanceIndex= 0; +const int kMatchChoiceRepetitionIndex = 1; + +const int kNumMoveBitsForMainChoice = 5; +const int kNumMoveBitsForPosCoders = 5; + +const int kNumMoveBitsForAlignCoders = 5; + +const int kNumMoveBitsForPosSlotCoder = 5; + +const int kNumLitPosStatesBitsEncodingMax = 4; +const int kNumLitContextBitsMax = 8; + +}} + +#endif diff --git a/snesreader/libjma/lzmadec.h b/snesreader/libjma/lzmadec.h new file mode 100644 index 00000000..bb91912e --- /dev/null +++ b/snesreader/libjma/lzmadec.h @@ -0,0 +1,82 @@ +/* +Copyright (C) 2002 Andrea Mazzoleni ( http://advancemame.sf.net ) +Copyright (C) 2001-4 Igor Pavlov ( http://www.7-zip.org ) + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License version 2.1 as published by the Free Software Foundation. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef __LZARITHMETIC_DECODER_H +#define __LZARITHMETIC_DECODER_H + +#include "winout.h" +#include "lzma.h" +#include "lencoder.h" +#include "litcoder.h" + +namespace NCompress { +namespace NLZMA { + +typedef CMyBitDecoder CMyBitDecoder2; + +class CDecoder +{ + NStream::NWindow::COut m_OutWindowStream; + CMyRangeDecoder m_RangeDecoder; + + CMyBitDecoder2 m_MainChoiceDecoders[kNumStates][NLength::kNumPosStatesMax]; + CMyBitDecoder2 m_MatchChoiceDecoders[kNumStates]; + CMyBitDecoder2 m_MatchRepChoiceDecoders[kNumStates]; + CMyBitDecoder2 m_MatchRep1ChoiceDecoders[kNumStates]; + CMyBitDecoder2 m_MatchRep2ChoiceDecoders[kNumStates]; + CMyBitDecoder2 m_MatchRepShortChoiceDecoders[kNumStates][NLength::kNumPosStatesMax]; + + CBitTreeDecoder m_PosSlotDecoder[kNumLenToPosStates]; + + CReverseBitTreeDecoder2 m_PosDecoders[kNumPosModels]; + CReverseBitTreeDecoder m_PosAlignDecoder; + // CBitTreeDecoder2 m_PosDecoders[kNumPosModels]; + // CBitTreeDecoder m_PosAlignDecoder; + + NLength::CDecoder m_LenDecoder; + NLength::CDecoder m_RepMatchLenDecoder; + + NLiteral::CDecoder m_LiteralDecoder; + + UINT32 m_DictionarySize; + + UINT32 m_PosStateMask; + + HRESULT Create(); + + HRESULT Init(ISequentialInStream *anInStream, ISequentialOutStream *anOutStream); + + HRESULT Flush() { return m_OutWindowStream.Flush(); } + + HRESULT CodeReal(ISequentialInStream *anInStream, ISequentialOutStream *anOutStream, const UINT64 *anInSize, const UINT64 *anOutSize); + +public: + + CDecoder(); + + HRESULT Code(ISequentialInStream *anInStream, ISequentialOutStream *anOutStream, const UINT64 *anInSize, const UINT64 *anOutSize); + HRESULT ReadCoderProperties(ISequentialInStream *anInStream); + + HRESULT SetDictionarySize(UINT32 aDictionarySize); + HRESULT SetLiteralProperties(UINT32 aLiteralPosStateBits, UINT32 aLiteralContextBits); + HRESULT SetPosBitsProperties(UINT32 aNumPosStateBits); +}; + +}} + +#endif diff --git a/snesreader/libjma/lzmadecode.cpp b/snesreader/libjma/lzmadecode.cpp new file mode 100644 index 00000000..ad6b5709 --- /dev/null +++ b/snesreader/libjma/lzmadecode.cpp @@ -0,0 +1,298 @@ +/* +Copyright (C) 2005-2007 NSRT Team ( http://nsrt.edgeemu.com ) +Copyright (C) 2002 Andrea Mazzoleni ( http://advancemame.sf.net ) +Copyright (C) 2001-4 Igor Pavlov ( http://www.7-zip.org ) + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License version 2.1 as published by the Free Software Foundation. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "portable.h" +#include "lzmadec.h" + +#define RETURN_E_OUTOFMEMORY_IF_FALSE(x) { if (!(x)) return E_OUTOFMEMORY; } + +namespace NCompress { +namespace NLZMA { + +HRESULT CDecoder::SetDictionarySize(UINT32 aDictionarySize) +{ + if (aDictionarySize > (1 << kDicLogSizeMax)) + return E_INVALIDARG; + + UINT32 aWindowReservSize = MyMax(aDictionarySize, UINT32(1 << 21)); + + if (m_DictionarySize != aDictionarySize) + { + m_OutWindowStream.Create(aDictionarySize, kMatchMaxLen, aWindowReservSize); + m_DictionarySize = aDictionarySize; + } + return S_OK; +} + +HRESULT CDecoder::SetLiteralProperties( + UINT32 aLiteralPosStateBits, UINT32 aLiteralContextBits) +{ + if (aLiteralPosStateBits > 8) + return E_INVALIDARG; + if (aLiteralContextBits > 8) + return E_INVALIDARG; + m_LiteralDecoder.Create(aLiteralPosStateBits, aLiteralContextBits); + return S_OK; +} + +HRESULT CDecoder::SetPosBitsProperties(UINT32 aNumPosStateBits) +{ + if (aNumPosStateBits > NLength::kNumPosStatesBitsMax) + return E_INVALIDARG; + UINT32 aNumPosStates = 1 << aNumPosStateBits; + m_LenDecoder.Create(aNumPosStates); + m_RepMatchLenDecoder.Create(aNumPosStates); + m_PosStateMask = aNumPosStates - 1; + return S_OK; +} + +CDecoder::CDecoder(): + m_DictionarySize((UINT32)-1) +{ + Create(); +} + +HRESULT CDecoder::Create() +{ + for(int i = 0; i < kNumPosModels; i++) + { + RETURN_E_OUTOFMEMORY_IF_FALSE( + m_PosDecoders[i].Create(kDistDirectBits[kStartPosModelIndex + i])); + } + return S_OK; +} + + +HRESULT CDecoder::Init(ISequentialInStream *anInStream, + ISequentialOutStream *anOutStream) +{ + m_RangeDecoder.Init(anInStream); + + m_OutWindowStream.Init(anOutStream); + + int i; + for(i = 0; i < kNumStates; i++) + { + for (UINT32 j = 0; j <= m_PosStateMask; j++) + { + m_MainChoiceDecoders[i][j].Init(); + m_MatchRepShortChoiceDecoders[i][j].Init(); + } + m_MatchChoiceDecoders[i].Init(); + m_MatchRepChoiceDecoders[i].Init(); + m_MatchRep1ChoiceDecoders[i].Init(); + m_MatchRep2ChoiceDecoders[i].Init(); + } + + m_LiteralDecoder.Init(); + + // m_RepMatchLenDecoder.Init(); + + for (i = 0; (UINT32) i < kNumLenToPosStates; i++) + m_PosSlotDecoder[i].Init(); + + for(i = 0; i < kNumPosModels; i++) + m_PosDecoders[i].Init(); + + m_LenDecoder.Init(); + m_RepMatchLenDecoder.Init(); + + m_PosAlignDecoder.Init(); + return S_OK; + +} + +HRESULT CDecoder::CodeReal(ISequentialInStream *anInStream, + ISequentialOutStream *anOutStream, + const UINT64 *anInSize, const UINT64 *anOutSize) +{ + if (anOutSize == NULL) + return E_INVALIDARG; + + Init(anInStream, anOutStream); + + CState aState; + aState.Init(); + bool aPeviousIsMatch = false; + BYTE aPreviousByte = 0; + UINT32 aRepDistances[kNumRepDistances]; + for(UINT32 i = 0 ; i < kNumRepDistances; i++) + aRepDistances[i] = 0; + + UINT64 aNowPos64 = 0; + UINT64 aSize = *anOutSize; + while(aNowPos64 < aSize) + { + UINT64 aNext = MyMin(aNowPos64 + (1 << 18), aSize); + while(aNowPos64 < aNext) + { + UINT32 aPosState = UINT32(aNowPos64) & m_PosStateMask; + if (m_MainChoiceDecoders[aState.m_Index][aPosState].Decode(&m_RangeDecoder) == (UINT32) kMainChoiceLiteralIndex) + { + // aCounts[0]++; + aState.UpdateChar(); + if(aPeviousIsMatch) + { + BYTE aMatchByte = m_OutWindowStream.GetOneByte(0 - aRepDistances[0] - 1); + aPreviousByte = m_LiteralDecoder.DecodeWithMatchByte(&m_RangeDecoder, + UINT32(aNowPos64), aPreviousByte, aMatchByte); + aPeviousIsMatch = false; + } + else + aPreviousByte = m_LiteralDecoder.DecodeNormal(&m_RangeDecoder, + UINT32(aNowPos64), aPreviousByte); + m_OutWindowStream.PutOneByte(aPreviousByte); + aNowPos64++; + } + else + { + aPeviousIsMatch = true; + UINT32 aDistance, aLen; + if(m_MatchChoiceDecoders[aState.m_Index].Decode(&m_RangeDecoder) == + (UINT32) kMatchChoiceRepetitionIndex) + { + if(m_MatchRepChoiceDecoders[aState.m_Index].Decode(&m_RangeDecoder) == 0) + { + if(m_MatchRepShortChoiceDecoders[aState.m_Index][aPosState].Decode(&m_RangeDecoder) == 0) + { + aState.UpdateShortRep(); + aPreviousByte = m_OutWindowStream.GetOneByte(0 - aRepDistances[0] - 1); + m_OutWindowStream.PutOneByte(aPreviousByte); + aNowPos64++; + // aCounts[3 + 4]++; + continue; + } + // aCounts[3 + 0]++; + aDistance = aRepDistances[0]; + } + else + { + if(m_MatchRep1ChoiceDecoders[aState.m_Index].Decode(&m_RangeDecoder) == 0) + { + aDistance = aRepDistances[1]; + aRepDistances[1] = aRepDistances[0]; + // aCounts[3 + 1]++; + } + else + { + if (m_MatchRep2ChoiceDecoders[aState.m_Index].Decode(&m_RangeDecoder) == 0) + { + // aCounts[3 + 2]++; + aDistance = aRepDistances[2]; + } + else + { + // aCounts[3 + 3]++; + aDistance = aRepDistances[3]; + aRepDistances[3] = aRepDistances[2]; + } + aRepDistances[2] = aRepDistances[1]; + aRepDistances[1] = aRepDistances[0]; + } + aRepDistances[0] = aDistance; + } + aLen = m_RepMatchLenDecoder.Decode(&m_RangeDecoder, aPosState) + kMatchMinLen; + // aCounts[aLen]++; + aState.UpdateRep(); + } + else + { + aLen = kMatchMinLen + m_LenDecoder.Decode(&m_RangeDecoder, aPosState); + aState.UpdateMatch(); + UINT32 aPosSlot = m_PosSlotDecoder[GetLenToPosState(aLen)].Decode(&m_RangeDecoder); + // aCounts[aPosSlot]++; + if (aPosSlot >= (UINT32) kStartPosModelIndex) + { + aDistance = kDistStart[aPosSlot]; + if (aPosSlot < (UINT32) kEndPosModelIndex) + aDistance += m_PosDecoders[aPosSlot - kStartPosModelIndex].Decode(&m_RangeDecoder); + else + { + aDistance += (m_RangeDecoder.DecodeDirectBits(kDistDirectBits[aPosSlot] - + kNumAlignBits) << kNumAlignBits); + aDistance += m_PosAlignDecoder.Decode(&m_RangeDecoder); + } + } + else + aDistance = aPosSlot; + + + aRepDistances[3] = aRepDistances[2]; + aRepDistances[2] = aRepDistances[1]; + aRepDistances[1] = aRepDistances[0]; + + aRepDistances[0] = aDistance; + // UpdateStat(aLen, aPosSlot); + } + if (aDistance >= aNowPos64) + throw E_INVALIDDATA; + m_OutWindowStream.CopyBackBlock(aDistance, aLen); + aNowPos64 += aLen; + aPreviousByte = m_OutWindowStream.GetOneByte(0 - 1); + } + } + } + return Flush(); +} + +HRESULT CDecoder::Code(ISequentialInStream *anInStream, ISequentialOutStream *anOutStream, const UINT64 *anInSize, const UINT64 *anOutSize) +{ + try { + return CodeReal(anInStream, anOutStream, anInSize, anOutSize); + } catch (HRESULT& e) { + return e; + } catch (...) { + return E_FAIL; + } +} + +HRESULT CDecoder::ReadCoderProperties(ISequentialInStream *anInStream) +{ + UINT32 aNumPosStateBits; + UINT32 aLiteralPosStateBits; + UINT32 aLiteralContextBits; + UINT32 aDictionarySize; + + UINT32 aProcessesedSize; + + BYTE aByte; + RETURN_IF_NOT_S_OK(anInStream->Read(&aByte, sizeof(aByte), &aProcessesedSize)); + if (aProcessesedSize != sizeof(aByte)) + return E_INVALIDARG; + + aLiteralContextBits = aByte % 9; + BYTE aRemainder = aByte / 9; + aLiteralPosStateBits = aRemainder % 5; + aNumPosStateBits = aRemainder / 5; + + UINT8 uint_buffer[UINT_SIZE]; + RETURN_IF_NOT_S_OK(anInStream->Read(uint_buffer, sizeof(aDictionarySize), &aProcessesedSize)); + aDictionarySize = charp_to_uint(uint_buffer); + + if (aProcessesedSize != sizeof(aDictionarySize)) + return E_INVALIDARG; + + RETURN_IF_NOT_S_OK(SetDictionarySize(aDictionarySize)); + RETURN_IF_NOT_S_OK(SetLiteralProperties(aLiteralPosStateBits, aLiteralContextBits)); + RETURN_IF_NOT_S_OK(SetPosBitsProperties(aNumPosStateBits)); + + return S_OK; +} + +}} diff --git a/snesreader/libjma/portable.h b/snesreader/libjma/portable.h new file mode 100644 index 00000000..d95f81ab --- /dev/null +++ b/snesreader/libjma/portable.h @@ -0,0 +1,84 @@ +/* +Copyright (C) 2004-2007 NSRT Team ( http://nsrt.edgeemu.com ) +Copyright (C) 2002 Andrea Mazzoleni ( http://advancemame.sf.net ) + +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. + +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., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#ifndef __PORTABLE_H +#define __PORTABLE_H + +#include +#include + +typedef int8_t INT8; +typedef uint8_t UINT8; +typedef int16_t INT16; +typedef uint16_t UINT16; +typedef int32_t INT32; +typedef uint32_t UINT32; +typedef int64_t INT64; +typedef uint64_t UINT64; + +typedef UINT8 BYTE; +typedef UINT16 WORD; +typedef UINT32 DWORD; + +typedef unsigned UINT_PTR; + +typedef int BOOL; +#define FALSE 0 +#define TRUE 1 + +#define HRESULT int +#define S_OK 0 +#define E_INVALIDARG -1 +#define E_OUTOFMEMORY -2 +#define E_FAIL -3 +#define E_INTERNAL_ERROR -4 +#define E_INVALIDDATA -5 + +template inline T MyMin(T a, T b) { + return a < b ? a : b; +} + +template inline T MyMax(T a, T b) { + return a > b ? a : b; +} + +#define RETURN_IF_NOT_S_OK(x) { HRESULT __aResult_ = (x); if(__aResult_ != S_OK) return __aResult_; } + + +#define UINT_SIZE (4) +#define USHORT_SIZE (2) + +//Convert an array of 4 bytes back into an integer +inline unsigned int charp_to_uint(const unsigned char buffer[UINT_SIZE]) +{ + unsigned int num = (unsigned int)buffer[3]; + num |= ((unsigned int)buffer[2]) << 8; + num |= ((unsigned int)buffer[1]) << 16; + num |= ((unsigned int)buffer[0]) << 24; + return(num); +} + +//Convert an array of 2 bytes back into a short integer +inline unsigned short charp_to_ushort(const unsigned char buffer[USHORT_SIZE]) +{ + unsigned short num = (unsigned short)buffer[1]; + num |= ((unsigned short)buffer[0]) << 8; + return(num); +} + +#endif diff --git a/snesreader/libjma/rcdefs.h b/snesreader/libjma/rcdefs.h new file mode 100644 index 00000000..6106b57a --- /dev/null +++ b/snesreader/libjma/rcdefs.h @@ -0,0 +1,60 @@ +/* +Copyright (C) 2002 Andrea Mazzoleni ( http://advancemame.sf.net ) +Copyright (C) 2001-4 Igor Pavlov ( http://www.7-zip.org ) + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License version 2.1 as published by the Free Software Foundation. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef __RCDEFS_H +#define __RCDEFS_H + +#include "aribitcd.h" +#include "ariconst.h" + +#define RC_INIT_VAR \ + UINT32 aRange = aRangeDecoder->m_Range; \ + UINT32 aCode = aRangeDecoder->m_Code; + +#define RC_FLUSH_VAR \ + aRangeDecoder->m_Range = aRange; \ + aRangeDecoder->m_Code = aCode; + +#define RC_NORMALIZE \ + if (aRange < NCompression::NArithmetic::kTopValue) \ + { \ + aCode = (aCode << 8) | aRangeDecoder->m_Stream.ReadByte(); \ + aRange <<= 8; } + +#define RC_GETBIT2(aNumMoveBits, aProb, aModelIndex, Action0, Action1) \ + {UINT32 aNewBound = (aRange >> NCompression::NArithmetic::kNumBitModelTotalBits) * aProb; \ + if (aCode < aNewBound) \ + { \ + Action0; \ + aRange = aNewBound; \ + aProb += (NCompression::NArithmetic::kBitModelTotal - aProb) >> aNumMoveBits; \ + aModelIndex <<= 1; \ + } \ + else \ + { \ + Action1; \ + aRange -= aNewBound; \ + aCode -= aNewBound; \ + aProb -= (aProb) >> aNumMoveBits; \ + aModelIndex = (aModelIndex << 1) + 1; \ + }} \ + RC_NORMALIZE + +#define RC_GETBIT(aNumMoveBits, aProb, aModelIndex) RC_GETBIT2(aNumMoveBits, aProb, aModelIndex, ; , ;) + +#endif diff --git a/snesreader/libjma/rngcoder.h b/snesreader/libjma/rngcoder.h new file mode 100644 index 00000000..711c2de8 --- /dev/null +++ b/snesreader/libjma/rngcoder.h @@ -0,0 +1,143 @@ +/* +Copyright (C) 2002 Andrea Mazzoleni ( http://advancemame.sf.net ) +Copyright (C) 2001-4 Igor Pavlov ( http://www.7-zip.org ) + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License version 2.1 as published by the Free Software Foundation. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef __COMPRESSION_RANGECODER_H +#define __COMPRESSION_RANGECODER_H + +#include "inbyte.h" + +namespace NCompression { +namespace NArithmetic { + +const UINT32 kNumTopBits = 24; +const UINT32 kTopValue = (1 << kNumTopBits); + +class CRangeDecoder +{ +public: + NStream::CInByte m_Stream; + UINT32 m_Range; + UINT32 m_Code; + UINT32 m_Word; + void Normalize() + { + while (m_Range < kTopValue) + { + m_Code = (m_Code << 8) | m_Stream.ReadByte(); + m_Range <<= 8; + } + } + + void Init(ISequentialInStream *aStream) + { + m_Stream.Init(aStream); + m_Code = 0; + m_Range = UINT32(-1); + for(int i = 0; i < 5; i++) + m_Code = (m_Code << 8) | m_Stream.ReadByte(); + } + + UINT32 GetThreshold(UINT32 aTotal) + { + return (m_Code) / ( m_Range /= aTotal); + } + + void Decode(UINT32 aStart, UINT32 aSize, UINT32 aTotal) + { + m_Code -= aStart * m_Range; + m_Range *= aSize; + Normalize(); + } + + /* + UINT32 DecodeDirectBitsDiv(UINT32 aNumTotalBits) + { + m_Range >>= aNumTotalBits; + UINT32 aThreshold = m_Code / m_Range; + m_Code -= aThreshold * m_Range; + + Normalize(); + return aThreshold; + } + + UINT32 DecodeDirectBitsDiv2(UINT32 aNumTotalBits) + { + if (aNumTotalBits <= kNumBottomBits) + return DecodeDirectBitsDiv(aNumTotalBits); + UINT32 aResult = DecodeDirectBitsDiv(aNumTotalBits - kNumBottomBits) << kNumBottomBits; + return (aResult | DecodeDirectBitsDiv(kNumBottomBits)); + } + */ + + UINT32 DecodeDirectBits(UINT32 aNumTotalBits) + { + UINT32 aRange = m_Range; + UINT32 aCode = m_Code; + UINT32 aResult = 0; + for (UINT32 i = aNumTotalBits; i > 0; i--) + { + aRange >>= 1; + /* + aResult <<= 1; + if (aCode >= aRange) + { + aCode -= aRange; + aResult |= 1; + } + */ + UINT32 t = (aCode - aRange) >> 31; + aCode -= aRange & (t - 1); + // aRange = aRangeTmp + ((aRange & 1) & (1 - t)); + aResult = (aResult << 1) | (1 - t); + + if (aRange < kTopValue) + { + aCode = (aCode << 8) | m_Stream.ReadByte(); + aRange <<= 8; + } + } + m_Range = aRange; + m_Code = aCode; + return aResult; + } + + UINT32 DecodeBit(UINT32 aSize0, UINT32 aNumTotalBits) + { + UINT32 aNewBound = (m_Range >> aNumTotalBits) * aSize0; + UINT32 aSymbol; + if (m_Code < aNewBound) + { + aSymbol = 0; + m_Range = aNewBound; + } + else + { + aSymbol = 1; + m_Code -= aNewBound; + m_Range -= aNewBound; + } + Normalize(); + return aSymbol; + } + + UINT64 GetProcessedSize() {return m_Stream.GetProcessedSize(); } +}; + +}} + +#endif diff --git a/snesreader/libjma/winout.cpp b/snesreader/libjma/winout.cpp new file mode 100644 index 00000000..1f33885c --- /dev/null +++ b/snesreader/libjma/winout.cpp @@ -0,0 +1,89 @@ +/* +Copyright (C) 2002 Andrea Mazzoleni ( http://advancemame.sf.net ) +Copyright (C) 2001-4 Igor Pavlov ( http://www.7-zip.org ) + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License version 2.1 as published by the Free Software Foundation. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "winout.h" + +namespace NStream { +namespace NWindow { + +void COut::Create(UINT32 aKeepSizeBefore, UINT32 aKeepSizeAfter, UINT32 aKeepSizeReserv) +{ + m_Pos = 0; + m_PosLimit = aKeepSizeReserv + aKeepSizeBefore; + m_KeepSizeBefore = aKeepSizeBefore; + m_KeepSizeAfter = aKeepSizeAfter; + m_KeepSizeReserv = aKeepSizeReserv; + m_StreamPos = 0; + m_MoveFrom = m_KeepSizeReserv; + m_WindowSize = aKeepSizeBefore; + UINT32 aBlockSize = m_KeepSizeBefore + m_KeepSizeAfter + m_KeepSizeReserv; + delete []m_Buffer; + m_Buffer = new BYTE[aBlockSize]; +} + +COut::~COut() +{ + delete []m_Buffer; +} + +void COut::SetWindowSize(UINT32 aWindowSize) +{ + m_WindowSize = aWindowSize; + m_MoveFrom = m_KeepSizeReserv + m_KeepSizeBefore - aWindowSize; +} + +void COut::Init(ISequentialOutStream *aStream, bool aSolid) +{ + m_Stream = aStream; + + if(aSolid) + m_StreamPos = m_Pos; + else + { + m_Pos = 0; + m_PosLimit = m_KeepSizeReserv + m_KeepSizeBefore; + m_StreamPos = 0; + } +} + +HRESULT COut::Flush() +{ + UINT32 aSize = m_Pos - m_StreamPos; + if(aSize == 0) + return S_OK; + UINT32 aProcessedSize; + HRESULT aResult = m_Stream->Write(m_Buffer + m_StreamPos, aSize, &aProcessedSize); + if (aResult != S_OK) + return aResult; + if (aSize != aProcessedSize) + return E_FAIL; + m_StreamPos = m_Pos; + return S_OK; +} + +void COut::MoveBlockBackward() +{ + HRESULT aResult = Flush(); + if (aResult != S_OK) + throw aResult; + memmove(m_Buffer, m_Buffer + m_MoveFrom, m_WindowSize + m_KeepSizeAfter); + m_Pos -= m_MoveFrom; + m_StreamPos -= m_MoveFrom; +} + +}} diff --git a/snesreader/libjma/winout.h b/snesreader/libjma/winout.h new file mode 100644 index 00000000..1f6d7e33 --- /dev/null +++ b/snesreader/libjma/winout.h @@ -0,0 +1,90 @@ +/* +Copyright (C) 2002 Andrea Mazzoleni ( http://advancemame.sf.net ) +Copyright (C) 2001-4 Igor Pavlov ( http://www.7-zip.org ) + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License version 2.1 as published by the Free Software Foundation. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef __STREAM_WINDOWOUT_H +#define __STREAM_WINDOWOUT_H + +#include "iiostrm.h" + +namespace NStream { +namespace NWindow { + +// m_KeepSizeBefore: how mach BYTEs must be in buffer before m_Pos; +// m_KeepSizeAfter: how mach BYTEs must be in buffer after m_Pos; +// m_KeepSizeReserv: how mach BYTEs must be in buffer for Moving Reserv; +// must be >= aKeepSizeAfter; // test it + +class COut +{ + BYTE *m_Buffer; + UINT32 m_Pos; + UINT32 m_PosLimit; + UINT32 m_KeepSizeBefore; + UINT32 m_KeepSizeAfter; + UINT32 m_KeepSizeReserv; + UINT32 m_StreamPos; + + UINT32 m_WindowSize; + UINT32 m_MoveFrom; + + ISequentialOutStream *m_Stream; + + virtual void MoveBlockBackward(); +public: + COut(): m_Buffer(0), m_Stream(0) {} + virtual ~COut(); + void Create(UINT32 aKeepSizeBefore, + UINT32 aKeepSizeAfter, UINT32 aKeepSizeReserv = (1<<17)); + void SetWindowSize(UINT32 aWindowSize); + + void Init(ISequentialOutStream *aStream, bool aSolid = false); + HRESULT Flush(); + + UINT32 GetCurPos() const { return m_Pos; } + const BYTE *GetPointerToCurrentPos() const { return m_Buffer + m_Pos;}; + + void CopyBackBlock(UINT32 aDistance, UINT32 aLen) + { + if (m_Pos >= m_PosLimit) + MoveBlockBackward(); + BYTE *p = m_Buffer + m_Pos; + aDistance++; + BYTE *p2 = p - aDistance; + for(UINT32 i = 0; i < aLen; i++) + p[i] = p2[i]; + m_Pos += aLen; + } + + void PutOneByte(BYTE aByte) + { + if (m_Pos >= m_PosLimit) + MoveBlockBackward(); + m_Buffer[m_Pos++] = aByte; + } + + BYTE GetOneByte(UINT32 anIndex) const + { + return m_Buffer[m_Pos + anIndex]; + } + + BYTE *GetBuffer() const { return m_Buffer; } +}; + +}} + +#endif diff --git a/snesreader/micro-bunzip/micro-bunzip.c b/snesreader/micro-bunzip/micro-bunzip.c new file mode 100644 index 00000000..e7f6f7dc --- /dev/null +++ b/snesreader/micro-bunzip/micro-bunzip.c @@ -0,0 +1,515 @@ +/* vi: set sw=4 ts=4: */ +/* micro-bunzip, a small, simple bzip2 decompression implementation. + Copyright 2003 by Rob Landley (rob@landley.net). + + Based on bzip2 decompression code by Julian R Seward (jseward@acm.org), + which also acknowledges contributions by Mike Burrows, David Wheeler, + Peter Fenwick, Alistair Moffat, Radford Neal, Ian H. Witten, + Robert Sedgewick, and Jon L. Bentley. + + I hereby release this code under the GNU Library General Public License + (LGPL) version 2, available at http://www.gnu.org/copyleft/lgpl.html +*/ + +#include +#include +#include +#include +#include + +/* Constants for huffman coding */ +#define MAX_GROUPS 6 +#define GROUP_SIZE 50 /* 64 would have been more efficient */ +#define MAX_HUFCODE_BITS 20 /* Longest huffman code allowed */ +#define MAX_SYMBOLS 258 /* 256 literals + RUNA + RUNB */ +#define SYMBOL_RUNA 0 +#define SYMBOL_RUNB 1 + +/* Status return values */ +#define RETVAL_OK 0 +#define RETVAL_LAST_BLOCK (-1) +#define RETVAL_NOT_BZIP_DATA (-2) +#define RETVAL_UNEXPECTED_INPUT_EOF (-3) +#define RETVAL_UNEXPECTED_OUTPUT_EOF (-4) +#define RETVAL_DATA_ERROR (-5) +#define RETVAL_OUT_OF_MEMORY (-6) +#define RETVAL_OBSOLETE_INPUT (-7) + +/* Other housekeeping constants */ +#define IOBUF_SIZE 4096 + +char *bunzip_errors[]={NULL,"Bad file checksum","Not bzip data", + "Unexpected input EOF","Unexpected output EOF","Data error", + "Out of memory","Obsolete (pre 0.9.5) bzip format not supported."}; + +/* This is what we know about each huffman coding group */ +struct group_data { + int limit[MAX_HUFCODE_BITS],base[MAX_HUFCODE_BITS],permute[MAX_SYMBOLS]; + char minLen, maxLen; +}; + +/* Structure holding all the housekeeping data, including IO buffers and + memory that persists between calls to bunzip */ +typedef struct { + /* For I/O error handling */ + jmp_buf jmpbuf; + /* Input stream, input buffer, input bit buffer */ + int in_fd,inbufCount,inbufPos; + unsigned char *inbuf; + unsigned int inbufBitCount, inbufBits; + /* Output buffer */ + char outbuf[IOBUF_SIZE]; + int outbufPos; + /* The CRC values stored in the block header and calculated from the data */ + unsigned int crc32Table[256],headerCRC, dataCRC, totalCRC; + /* Intermediate buffer and its size (in bytes) */ + unsigned int *dbuf, dbufSize; + /* State for interrupting output loop */ + int writePos,writeRun,writeCount,writeCurrent; + + /* These things are a bit too big to go on the stack */ + unsigned char selectors[32768]; /* nSelectors=15 bits */ + struct group_data groups[MAX_GROUPS]; /* huffman coding tables */ +} bunzip_data; + +/* Return the next nnn bits of input. All reads from the compressed input + are done through this function. All reads are big endian */ +static unsigned int get_bits(bunzip_data *bd, char bits_wanted) +{ + unsigned int bits=0; + + /* If we need to get more data from the byte buffer, do so. (Loop getting + one byte at a time to enforce endianness and avoid unaligned access.) */ + while (bd->inbufBitCountinbufPos==bd->inbufCount) { + if(!(bd->inbufCount = read(bd->in_fd, bd->inbuf, IOBUF_SIZE))) + longjmp(bd->jmpbuf,RETVAL_UNEXPECTED_INPUT_EOF); + bd->inbufPos=0; + } + /* Avoid 32-bit overflow (dump bit buffer to top of output) */ + if(bd->inbufBitCount>=24) { + bits=bd->inbufBits&((1<inbufBitCount)-1); + bits_wanted-=bd->inbufBitCount; + bits<<=bits_wanted; + bd->inbufBitCount=0; + } + /* Grab next 8 bits of input from buffer. */ + bd->inbufBits=(bd->inbufBits<<8)|bd->inbuf[bd->inbufPos++]; + bd->inbufBitCount+=8; + } + /* Calculate result */ + bd->inbufBitCount-=bits_wanted; + bits|=(bd->inbufBits>>bd->inbufBitCount)&((1<headerCRC=get_bits(bd,32); + /* Is this the last block (with CRC for file)? */ + if(!strcmp(mtfSymbol,"\x17\x72\x45\x38\x50\x90")) + return RETVAL_LAST_BLOCK; + /* If it's not a valid data block, barf. */ + if(strcmp(mtfSymbol,"\x31\x41\x59\x26\x53\x59")) + return RETVAL_NOT_BZIP_DATA; + + dbuf=bd->dbuf; + dbufSize=bd->dbufSize; + selectors=bd->selectors; + /* We can add support for blockRandomised if anybody complains. There was + some code for this in busybox 1.0.0-pre3, but nobody ever noticed that + it didn't actually work. */ + if(get_bits(bd,1)) return RETVAL_OBSOLETE_INPUT; + if((origPtr=get_bits(bd,24)) > dbufSize) return RETVAL_DATA_ERROR; + /* mapping table: if some byte values are never used (encoding things + like ascii text), the compression code removes the gaps to have fewer + symbols to deal with, and writes a sparse bitfield indicating which + values were present. We make a translation table to convert the symbols + back to the corresponding bytes. */ + t=get_bits(bd, 16); + memset(symToByte,0,256); + symTotal=0; + for (i=0;i<16;i++) { + if(t&(1<<(15-i))) { + k=get_bits(bd,16); + for(j=0;j<16;j++) + if(k&(1<<(15-j))) symToByte[symTotal++]=(16*i)+j; + } + } + /* How many different huffman coding groups does this block use? */ + groupCount=get_bits(bd,3); + if (groupCount<2 || groupCount>MAX_GROUPS) return RETVAL_DATA_ERROR; + /* nSelectors: Every GROUP_SIZE many symbols we select a new huffman coding + group. Read in the group selector list, which is stored as MTF encoded + bit runs. */ + if(!(nSelectors=get_bits(bd, 15))) return RETVAL_DATA_ERROR; + for(i=0; i=groupCount) return RETVAL_DATA_ERROR; + /* Decode MTF to get the next selector */ + uc = mtfSymbol[j]; + memmove(mtfSymbol+1,mtfSymbol,j); + mtfSymbol[0]=selectors[i]=uc; + } + /* Read the huffman coding tables for each group, which code for symTotal + literal symbols, plus two run symbols (RUNA, RUNB) */ + symCount=symTotal+2; + for (j=0; j MAX_HUFCODE_BITS) return RETVAL_DATA_ERROR; + if(!get_bits(bd, 1)) break; + if(!get_bits(bd, 1)) t++; + else t--; + } + length[i] = t; + } + /* Find largest and smallest lengths in this group */ + minLen=maxLen=length[0]; + for(i = 1; i < symCount; i++) { + if(length[i] > maxLen) maxLen = length[i]; + else if(length[i] < minLen) minLen = length[i]; + } + /* Calculate permute[], base[], and limit[] tables from length[]. + * + * permute[] is the lookup table for converting huffman coded symbols + * into decoded symbols. base[] is the amount to subtract from the + * value of a huffman symbol of a given length when using permute[]. + * + * limit[] indicates the largest numerical value a symbol with a given + * number of bits can have. It lets us know when to stop reading. + * + * To use these, keep reading bits until value<=limit[bitcount] or + * you've read over 20 bits (error). Then the decoded symbol + * equals permute[hufcode_value-base[hufcode_bitcount]]. + */ + hufGroup=bd->groups+j; + hufGroup->minLen = minLen; + hufGroup->maxLen = maxLen; + /* Note that minLen can't be smaller than 1, so we adjust the base + and limit array pointers so we're not always wasting the first + entry. We do this again when using them (during symbol decoding).*/ + base=hufGroup->base-1; + limit=hufGroup->limit-1; + /* Calculate permute[] */ + pp = 0; + for(i=minLen;i<=maxLen;i++) + for(t=0;tpermute[pp++] = t; + /* Count cumulative symbols coded for at each bit length */ + for (i=minLen;i<=maxLen;i++) temp[i]=limit[i]=0; + for (i=0;i=nSelectors) return RETVAL_DATA_ERROR; + hufGroup=bd->groups+selectors[selector++]; + base=hufGroup->base-1; + limit=hufGroup->limit-1; + } + /* Read next huffman-coded symbol */ + i = hufGroup->minLen; + j=get_bits(bd, i); + for(;;) { + if (i > hufGroup->maxLen) return RETVAL_DATA_ERROR; + if (j <= limit[i]) break; + i++; + + j = (j << 1) | get_bits(bd,1); + } + /* Huffman decode nextSym (with bounds checking) */ + j-=base[i]; + if (j < 0 || j >= MAX_SYMBOLS) return RETVAL_DATA_ERROR; + nextSym = hufGroup->permute[j]; + /* If this is a repeated run, loop collecting data */ + if (nextSym == SYMBOL_RUNA || nextSym == SYMBOL_RUNB) { + /* If this is the start of a new run, zero out counter */ + if(!runPos) { + runPos = 1; + t = 0; + } + /* Neat trick that saves 1 symbol: instead of or-ing 0 or 1 at + each bit position, add 1 or 2 instead. For example, + 1011 is 1<<0 + 1<<1 + 2<<2. 1010 is 2<<0 + 2<<1 + 1<<2. + You can make any bit pattern that way using 1 less symbol than + the basic or 0/1 method (except all bits 0, which would use no + symbols, but a run of length 0 doesn't mean anything in this + context). Thus space is saved. */ + if (nextSym == SYMBOL_RUNA) t += runPos; + else t += 2*runPos; + runPos <<= 1; + continue; + } + /* When we hit the first non-run symbol after a run, we now know + how many times to repeat the last literal, so append that many + copies to our buffer of decoded symbols (dbuf) now. (The last + literal used is the one at the head of the mtfSymbol array.) */ + if(runPos) { + runPos=0; + if(dbufCount+t>=dbufSize) return RETVAL_DATA_ERROR; + + uc = symToByte[mtfSymbol[0]]; + byteCount[uc] += t; + while(t--) dbuf[dbufCount++]=uc; + } + /* Is this the terminating symbol? */ + if(nextSym>symTotal) break; + /* At this point, the symbol we just decoded indicates a new literal + character. Subtract one to get the position in the MTF array + at which this literal is currently to be found. (Note that the + result can't be -1 or 0, because 0 and 1 are RUNA and RUNB. + Another instance of the first symbol in the mtf array, position 0, + would have been handled as part of a run.) */ + if(dbufCount>=dbufSize) return RETVAL_DATA_ERROR; + i = nextSym - 1; + uc = mtfSymbol[i]; + memmove(mtfSymbol+1,mtfSymbol,i); + mtfSymbol[0] = uc; + uc=symToByte[uc]; + /* We have our literal byte. Save it into dbuf. */ + byteCount[uc]++; + dbuf[dbufCount++] = (unsigned int)uc; + } + /* At this point, we've finished reading huffman-coded symbols and + compressed runs from the input stream. There are dbufCount many of + them in dbuf[]. Now undo the Burrows-Wheeler transform on dbuf. + See http://dogma.net/markn/articles/bwt/bwt.htm + */ + + /* Now we know what dbufCount is, do a better sanity check on origPtr. */ + if (origPtr<0 || origPtr>=dbufCount) return RETVAL_DATA_ERROR; + /* Turn byteCount into cumulative occurrence counts of 0 to n-1. */ + j=0; + for(i=0;i<256;i++) { + k=j+byteCount[i]; + byteCount[i] = j; + j=k; + } + /* Figure out what order dbuf would be in if we sorted it. */ + for (i=0;idataCRC = 0xffffffffL; + /* Decode first byte by hand to initialize "previous" byte. Note that it + doesn't get output, and if the first three characters are identical + it doesn't qualify as a run (hence uc=255, which will either wrap + to 1 or get reset). */ + if(dbufCount) { + bd->writePos=dbuf[origPtr]; + bd->writeCurrent=(unsigned char)(bd->writePos&0xff); + bd->writePos>>=8; + bd->writeRun=-1; + } + bd->writeCount=dbufCount; + + return RETVAL_OK; +} + +/* Flush output buffer to disk */ +extern void flush_bunzip_outbuf(bunzip_data *bd, int out_fd) +{ + if(bd->outbufPos) { + if(write(out_fd, bd->outbuf, bd->outbufPos) != bd->outbufPos) + longjmp(bd->jmpbuf,RETVAL_UNEXPECTED_OUTPUT_EOF); + bd->outbufPos=0; + } +} + + +/* Undo burrows-wheeler transform on intermediate buffer to produce output. + If !len, write up to len bytes of data to buf. Otherwise write to out_fd. + Returns len ? bytes written : RETVAL_OK. Notice all errors negative #'s. */ +extern int write_bunzip_data(bunzip_data *bd, int out_fd, char *outbuf, int len) +{ + unsigned int *dbuf=bd->dbuf; + int count,pos,current, run,copies,outbyte,previous,gotcount=0; + + for(;;) { + /* If last read was short due to end of file, return last block now */ + if(bd->writeCount<0) return bd->writeCount; + /* If we need to refill dbuf, do it. */ + if(!bd->writeCount) { + int i=read_bunzip_data(bd); + if(i) { + if(i==RETVAL_LAST_BLOCK) { + bd->writeCount=i; + return gotcount; + } else return i; + } + } + /* Loop generating output */ + count=bd->writeCount; + pos=bd->writePos; + current=bd->writeCurrent; + run=bd->writeRun; + while(count) { + /* If somebody (like busybox tar) wants a certain number of bytes of + data from memory instead of written to a file, humor them */ + if(len && bd->outbufPos>=len) goto dataus_interruptus; + count--; + /* Follow sequence vector to undo Burrows-Wheeler transform */ + previous=current; + pos=dbuf[pos]; + current=pos&0xff; + pos>>=8; + /* Whenever we see 3 consecutive copies of the same byte, + the 4th is a repeat count */ + if(run++==3) { + copies=current; + outbyte=previous; + current=-1; + } else { + copies=1; + outbyte=current; + } + /* Output bytes to buffer, flushing to file if necessary */ + while(copies--) { + if(bd->outbufPos == IOBUF_SIZE) flush_bunzip_outbuf(bd,out_fd); + bd->outbuf[bd->outbufPos++] = outbyte; + bd->dataCRC = (bd->dataCRC << 8) + ^ bd->crc32Table[(bd->dataCRC >> 24) ^ outbyte]; + } + if(current!=previous) run=0; + } + /* Decompression of this block completed successfully */ + bd->dataCRC=~(bd->dataCRC); + bd->totalCRC=((bd->totalCRC << 1) | (bd->totalCRC >> 31)) ^ bd->dataCRC; + /* If this block had a CRC error, force file level CRC error. */ + if(bd->dataCRC!=bd->headerCRC) { + bd->totalCRC=bd->headerCRC+1; + return RETVAL_LAST_BLOCK; + } +dataus_interruptus: + bd->writeCount=count; + if(len) { + gotcount+=bd->outbufPos; + memcpy(outbuf,bd->outbuf,len); + /* If we got enough data, checkpoint loop state and return */ + if((len-=bd->outbufPos)<1) { + bd->outbufPos-=len; + if(bd->outbufPos) + memmove(bd->outbuf,bd->outbuf+len,bd->outbufPos); + bd->writePos=pos; + bd->writeCurrent=current; + bd->writeRun=run; + return gotcount; + } + } + } +} + +/* Allocate the structure, read file header. If !len, src_fd contains + filehandle to read from. Else inbuf contains data. */ +extern int start_bunzip(bunzip_data **bdp, int src_fd, char *inbuf, int len) +{ + bunzip_data *bd; + unsigned int i,j,c; + + /* Figure out how much data to allocate */ + i=sizeof(bunzip_data); + if(!len) i+=IOBUF_SIZE; + /* Allocate bunzip_data. Most fields initialize to zero. */ + if(!(bd=*bdp=malloc(i))) return RETVAL_OUT_OF_MEMORY; + memset(bd,0,sizeof(bunzip_data)); + if(len) { + bd->inbuf=inbuf; + bd->inbufCount=len; + bd->in_fd=-1; + } else { + bd->inbuf=(char *)(bd+1); + bd->in_fd=src_fd; + } + /* Init the CRC32 table (big endian) */ + for(i=0;i<256;i++) { + c=i<<24; + for(j=8;j;j--) + c=c&0x80000000 ? (c<<1)^0x04c11db7 : (c<<1); + bd->crc32Table[i]=c; + } + /* Setup for I/O error handling via longjmp */ + i=setjmp(bd->jmpbuf); + if(i) return i; + /* Ensure that file starts with "BZh" */ + for(i=0;i<3;i++) if(get_bits(bd,8)!="BZh"[i]) return RETVAL_NOT_BZIP_DATA; + /* Next byte ascii '1'-'9', indicates block size in units of 100k of + uncompressed data. Allocate intermediate buffer for block. */ + i=get_bits(bd,8); + if (i<'1' || i>'9') return RETVAL_NOT_BZIP_DATA; + bd->dbufSize=100000*(i-'0'); + if(!(bd->dbuf=malloc(bd->dbufSize * sizeof(int)))) + return RETVAL_OUT_OF_MEMORY; + return RETVAL_OK; +} + +/* Example usage: decompress src_fd to dst_fd. (Stops at end of bzip data, + not end of file.) */ +extern char *uncompressStream(int src_fd, int dst_fd) +{ + bunzip_data *bd; + int i; + + if(!(i=start_bunzip(&bd,src_fd,0,0))) { + i=write_bunzip_data(bd,dst_fd,0,0); + if(i==RETVAL_LAST_BLOCK && bd->headerCRC==bd->totalCRC) i=RETVAL_OK; + } + flush_bunzip_outbuf(bd,dst_fd); + if(bd->dbuf) free(bd->dbuf); + free(bd); + return bunzip_errors[-i]; +} + +/* Dumb little test thing, decompress stdin to stdout */ +/*int main(int argc, char *argv[]) +{ + char *c=uncompressStream(0,1); + fprintf(stderr,"\n%s\n", c ? c : "Completed OK"); +}*/ diff --git a/snesreader/nall/Makefile b/snesreader/nall/Makefile new file mode 100644 index 00000000..8149bf15 --- /dev/null +++ b/snesreader/nall/Makefile @@ -0,0 +1,107 @@ +# Makefile +# author: byuu +# license: public domain + +[A-Z] = A B C D E F G H I J K L M N O P Q R S T U V W X Y Z +[a-z] = a b c d e f g h i j k l m n o p q r s t u v w x y z +[0-9] = 0 1 2 3 4 5 6 7 8 9 +[markup] = ` ~ ! @ \# $$ % ^ & * ( ) - _ = + [ { ] } \ | ; : ' " , < . > / ? +[all] = $([A-Z]) $([a-z]) $([0-9]) $([markup]) +[space] := +[space] += + +##### +# platform detection +##### + +ifeq ($(platform),) + uname := $(shell uname -a) + ifeq ($(uname),) + platform := win + delete = del $(subst /,\,$1) + else ifneq ($(findstring Darwin,$(uname)),) + platform := osx + delete = rm -f $1 + else + platform := x + delete = rm -f $1 + endif +endif + +ifeq ($(compiler),) + ifeq ($(platform),osx) + compiler := gcc-4.2 + else + compiler := gcc + endif +endif + +ifeq ($(prefix),) + prefix := /usr/local +endif + +##### +# function rwildcard(directory, pattern) +##### +rwildcard = \ + $(strip \ + $(filter $(if $2,$2,%), \ + $(foreach f, \ + $(wildcard $1*), \ + $(eval t = $(call rwildcard,$f/)) \ + $(if $t,$t,$f) \ + ) \ + ) \ + ) + +##### +# function strtr(source, from, to) +##### +strtr = \ + $(eval __temp := $1) \ + $(strip \ + $(foreach c, \ + $(join $(addsuffix :,$2),$3), \ + $(eval __temp := \ + $(subst $(word 1,$(subst :, ,$c)),$(word 2,$(subst :, ,$c)),$(__temp)) \ + ) \ + ) \ + $(__temp) \ + ) + +##### +# function strupper(source) +##### +strupper = $(call strtr,$1,$([a-z]),$([A-Z])) + +##### +# function strlower(source) +##### +strlower = $(call strtr,$1,$([A-Z]),$([a-z])) + +##### +# function strlen(source) +##### +strlen = \ + $(eval __temp := $(subst $([space]),_,$1)) \ + $(words \ + $(strip \ + $(foreach c, \ + $([all]), \ + $(eval __temp := \ + $(subst $c,$c ,$(__temp)) \ + ) \ + ) \ + $(__temp) \ + ) \ + ) + +##### +# function streq(source) +##### +streq = $(if $(filter-out xx,x$(subst $1,,$2)$(subst $2,,$1)x),,1) + +##### +# function strne(source) +##### +strne = $(if $(filter-out xx,x$(subst $1,,$2)$(subst $2,,$1)x),1,) diff --git a/snesreader/nall/algorithm.hpp b/snesreader/nall/algorithm.hpp new file mode 100644 index 00000000..cdc48dcf --- /dev/null +++ b/snesreader/nall/algorithm.hpp @@ -0,0 +1,23 @@ +#ifndef NALL_ALGORITHM_HPP +#define NALL_ALGORITHM_HPP + +#undef min +#undef max + +namespace nall { + template T min(const T &t, const U &u) { + return t < u ? t : u; + } + + template T max(const T &t, const U &u) { + return t > u ? t : u; + } + + //pseudo-random number generator + inline unsigned prng() { + static unsigned n = 0; + return n = (n >> 1) ^ (((n & 1) - 1) & 0xedb88320); + } +} + +#endif diff --git a/snesreader/nall/any.hpp b/snesreader/nall/any.hpp new file mode 100644 index 00000000..b31cff3c --- /dev/null +++ b/snesreader/nall/any.hpp @@ -0,0 +1,74 @@ +#ifndef NALL_ANY_HPP +#define NALL_ANY_HPP + +#include +#include +#include + +namespace nall { + class any { + public: + bool empty() const { return container; } + const std::type_info& type() const { return container ? container->type() : typeid(void); } + + template any& operator=(const T& value_) { + typedef typename static_if< + std::is_array::value, + typename std::remove_extent::type>::type*, + T + >::type auto_t; + + if(type() == typeid(auto_t)) { + static_cast*>(container)->value = (auto_t)value_; + } else { + if(container) delete container; + container = new holder((auto_t)value_); + } + + return *this; + } + + any() : container(0) {} + template any(const T& value_) : container(0) { operator=(value_); } + + private: + struct placeholder { + virtual const std::type_info& type() const = 0; + } *container; + + template struct holder : placeholder { + T value; + const std::type_info& type() const { return typeid(T); } + holder(const T& value_) : value(value_) {} + }; + + template friend T any_cast(any&); + template friend T any_cast(const any&); + template friend T* any_cast(any*); + template friend const T* any_cast(const any*); + }; + + template T any_cast(any &value) { + typedef typename std::remove_reference::type nonref; + if(value.type() != typeid(nonref)) throw; + return static_cast*>(value.container)->value; + } + + template T any_cast(const any &value) { + typedef const typename std::remove_reference::type nonref; + if(value.type() != typeid(nonref)) throw; + return static_cast*>(value.container)->value; + } + + template T* any_cast(any *value) { + if(!value || value->type() != typeid(T)) return 0; + return &static_cast*>(value->container)->value; + } + + template const T* any_cast(const any *value) { + if(!value || value->type() != typeid(T)) return 0; + return &static_cast*>(value->container)->value; + } +} + +#endif diff --git a/snesreader/nall/array.hpp b/snesreader/nall/array.hpp new file mode 100644 index 00000000..c1d33fd1 --- /dev/null +++ b/snesreader/nall/array.hpp @@ -0,0 +1,120 @@ +#ifndef NALL_ARRAY_HPP +#define NALL_ARRAY_HPP + +#include +#include +#include +#include +#include +#include +#include + +namespace nall { + //dynamic vector array + //neither constructor nor destructor is ever invoked; + //thus, this should only be used for POD objects. + template class array { + protected: + T *pool; + unsigned poolsize, buffersize; + + public: + unsigned size() const { return buffersize; } + unsigned capacity() const { return poolsize; } + + void reset() { + if(pool) free(pool); + pool = 0; + poolsize = 0; + buffersize = 0; + } + + void reserve(unsigned newsize) { + if(newsize == poolsize) return; + + pool = (T*)realloc(pool, newsize * sizeof(T)); + poolsize = newsize; + buffersize = min(buffersize, newsize); + } + + void resize(unsigned newsize) { + if(newsize > poolsize) reserve(bit::round(newsize)); //round reserve size up to power of 2 + buffersize = newsize; + } + + T* get(unsigned minsize = 0) { + if(minsize > buffersize) resize(minsize); + if(minsize > buffersize) throw "array[] out of bounds"; + return pool; + } + + void add(const T data) { + operator[](buffersize) = data; + } + + signed find(const T data) { + for(unsigned i = 0; i < size(); i++) if(pool[i] == data) return i; + return -1; //not found + } + + void clear() { + memset(pool, 0, buffersize * sizeof(T)); + } + + array() : pool(0), poolsize(0), buffersize(0) { + } + + array(std::initializer_list list) : pool(0), poolsize(0), buffersize(0) { + for(const T *p = list.begin(); p != list.end(); ++p) add(*p); + } + + ~array() { + reset(); + } + + //copy + array& operator=(const array &source) { + if(pool) free(pool); + buffersize = source.buffersize; + poolsize = source.poolsize; + pool = (T*)malloc(sizeof(T) * poolsize); //allocate entire pool size, + memcpy(pool, source.pool, sizeof(T) * buffersize); //... but only copy used pool objects + return *this; + } + + array(const array &source) : pool(0), poolsize(0), buffersize(0) { + operator=(source); + } + + //move + array& operator=(array &&source) { + if(pool) free(pool); + pool = source.pool; + poolsize = source.poolsize; + buffersize = source.buffersize; + source.pool = 0; + source.reset(); + return *this; + } + + array(array &&source) : pool(0), poolsize(0), buffersize(0) { + operator=(std::move(source)); + } + + //index + inline T& operator[](unsigned index) { + if(index >= buffersize) resize(index + 1); + if(index >= buffersize) throw "array[] out of bounds"; + return pool[index]; + } + + inline const T& operator[](unsigned index) const { + if(index >= buffersize) throw "array[] out of bounds"; + return pool[index]; + } + }; + + template struct has_size> { enum { value = true }; }; +} + +#endif diff --git a/snesreader/nall/base64.hpp b/snesreader/nall/base64.hpp new file mode 100644 index 00000000..e41c87b7 --- /dev/null +++ b/snesreader/nall/base64.hpp @@ -0,0 +1,90 @@ +#ifndef NALL_BASE64_HPP +#define NALL_BASE64_HPP + +#include +#include + +namespace nall { + class base64 { + public: + static bool encode(char *&output, const uint8_t* input, unsigned inlength) { + output = new char[inlength * 8 / 6 + 6](); + + unsigned i = 0, o = 0; + while(i < inlength) { + switch(i % 3) { + case 0: { + output[o++] = enc(input[i] >> 2); + output[o] = enc((input[i] & 3) << 4); + } break; + + case 1: { + uint8_t prev = dec(output[o]); + output[o++] = enc(prev + (input[i] >> 4)); + output[o] = enc((input[i] & 15) << 2); + } break; + + case 2: { + uint8_t prev = dec(output[o]); + output[o++] = enc(prev + (input[i] >> 6)); + output[o++] = enc(input[i] & 63); + } break; + } + + i++; + } + + return true; + } + + static bool decode(uint8_t *&output, unsigned &outlength, const char *input) { + unsigned inlength = strlen(input), infix = 0; + output = new uint8_t[inlength](); + + unsigned i = 0, o = 0; + while(i < inlength) { + uint8_t x = dec(input[i]); + + switch(i++ & 3) { + case 0: { + output[o] = x << 2; + } break; + + case 1: { + output[o++] |= x >> 4; + output[o] = (x & 15) << 4; + } break; + + case 2: { + output[o++] |= x >> 2; + output[o] = (x & 3) << 6; + } break; + + case 3: { + output[o++] |= x; + } break; + } + } + + outlength = o; + return true; + } + + private: + static char enc(uint8_t n) { + static char lookup_table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; + return lookup_table[n & 63]; + } + + static uint8_t dec(char n) { + if(n >= 'A' && n <= 'Z') return n - 'A'; + if(n >= 'a' && n <= 'z') return n - 'a' + 26; + if(n >= '0' && n <= '9') return n - '0' + 52; + if(n == '-') return 62; + if(n == '_') return 63; + return 0; + } + }; +} + +#endif diff --git a/snesreader/nall/bit.hpp b/snesreader/nall/bit.hpp new file mode 100644 index 00000000..169fc144 --- /dev/null +++ b/snesreader/nall/bit.hpp @@ -0,0 +1,51 @@ +#ifndef NALL_BIT_HPP +#define NALL_BIT_HPP + +namespace nall { + template inline unsigned uclamp(const unsigned x) { + enum { y = (1U << bits) - 1 }; + return y + ((x - y) & -(x < y)); //min(x, y); + } + + template inline unsigned uclip(const unsigned x) { + enum { m = (1U << bits) - 1 }; + return (x & m); + } + + template inline signed sclamp(const signed x) { + enum { b = 1U << (bits - 1), m = (1U << (bits - 1)) - 1 }; + return (x > m) ? m : (x < -b) ? -b : x; + } + + template inline signed sclip(const signed x) { + enum { b = 1U << (bits - 1), m = (1U << bits) - 1 }; + return ((x & m) ^ b) - b; + } + + namespace bit { + //lowest(0b1110) == 0b0010 + template inline T lowest(const T x) { + return x & -x; + } + + //clear_lowest(0b1110) == 0b1100 + template inline T clear_lowest(const T x) { + return x & (x - 1); + } + + //set_lowest(0b0101) == 0b0111 + template inline T set_lowest(const T x) { + return x | (x + 1); + } + + //round up to next highest single bit: + //round(15) == 16, round(16) == 16, round(17) == 32 + inline unsigned round(unsigned x) { + if((x & (x - 1)) == 0) return x; + while(x & (x - 1)) x &= x - 1; + return x << 1; + } + } +} + +#endif diff --git a/snesreader/nall/concept.hpp b/snesreader/nall/concept.hpp new file mode 100644 index 00000000..2949cd5e --- /dev/null +++ b/snesreader/nall/concept.hpp @@ -0,0 +1,15 @@ +#ifndef NALL_CONCEPT_HPP +#define NALL_CONCEPT_HPP + +namespace nall { + //unsigned count() const; + template struct has_count { enum { value = false }; }; + + //unsigned length() const; + template struct has_length { enum { value = false }; }; + + //unsigned size() const; + template struct has_size { enum { value = false }; }; +} + +#endif diff --git a/snesreader/nall/config.hpp b/snesreader/nall/config.hpp new file mode 100644 index 00000000..c713d0b0 --- /dev/null +++ b/snesreader/nall/config.hpp @@ -0,0 +1,123 @@ +#ifndef NALL_CONFIG_HPP +#define NALL_CONFIG_HPP + +#include +#include +#include + +namespace nall { + namespace configuration_traits { + template struct is_boolean { enum { value = false }; }; + template<> struct is_boolean { enum { value = true }; }; + + template struct is_signed { enum { value = false }; }; + template<> struct is_signed { enum { value = true }; }; + + template struct is_unsigned { enum { value = false }; }; + template<> struct is_unsigned { enum { value = true }; }; + + template struct is_double { enum { value = false }; }; + template<> struct is_double { enum { value = true }; }; + + template struct is_string { enum { value = false }; }; + template<> struct is_string { enum { value = true }; }; + } + + class configuration { + public: + enum type_t { boolean_t, signed_t, unsigned_t, double_t, string_t, unknown_t }; + struct item_t { + uintptr_t data; + string name; + string desc; + type_t type; + + string get() const { + switch(type) { + case boolean_t: return string() << *(bool*)data; + case signed_t: return string() << *(signed*)data; + case unsigned_t: return string() << *(unsigned*)data; + case double_t: return string() << *(double*)data; + case string_t: return string() << "\"" << *(string*)data << "\""; + } + return "???"; + } + + void set(string s) { + switch(type) { + case boolean_t: *(bool*)data = (s == "true"); break; + case signed_t: *(signed*)data = strsigned(s); break; + case unsigned_t: *(unsigned*)data = strunsigned(s); break; + case double_t: *(double*)data = strdouble(s); break; + case string_t: trim(s, "\""); *(string*)data = s; break; + } + } + }; + linear_vector list; + + template + void attach(T &data, const char *name, const char *desc = "") { + unsigned n = list.size(); + list[n].data = (uintptr_t)&data; + list[n].name = name; + list[n].desc = desc; + + if(configuration_traits::is_boolean::value) list[n].type = boolean_t; + else if(configuration_traits::is_signed::value) list[n].type = signed_t; + else if(configuration_traits::is_unsigned::value) list[n].type = unsigned_t; + else if(configuration_traits::is_double::value) list[n].type = double_t; + else if(configuration_traits::is_string::value) list[n].type = string_t; + else list[n].type = unknown_t; + } + + virtual bool load(const char *filename) { + string data; + if(data.readfile(filename) == true) { + data.replace("\r", ""); + lstring line; + line.split("\n", data); + + for(unsigned i = 0; i < line.size(); i++) { + if(auto position = qstrpos(line[i], "#")) line[i][position()] = 0; + if(!qstrpos(line[i], " = ")) continue; + + lstring part; + part.qsplit(" = ", line[i]); + trim(part[0]); + trim(part[1]); + + for(unsigned n = 0; n < list.size(); n++) { + if(part[0] == list[n].name) { + list[n].set(part[1]); + break; + } + } + } + + return true; + } else { + return false; + } + } + + virtual bool save(const char *filename) const { + file fp; + if(fp.open(filename, file::mode_write)) { + for(unsigned i = 0; i < list.size(); i++) { + string output; + output << list[i].name << " = " << list[i].get(); + if(list[i].desc != "") output << " # " << list[i].desc; + output << "\r\n"; + fp.print(output); + } + + fp.close(); + return true; + } else { + return false; + } + } + }; +} + +#endif diff --git a/snesreader/nall/crc32.hpp b/snesreader/nall/crc32.hpp new file mode 100644 index 00000000..ad36fbf6 --- /dev/null +++ b/snesreader/nall/crc32.hpp @@ -0,0 +1,66 @@ +#ifndef NALL_CRC32_HPP +#define NALL_CRC32_HPP + +#include + +namespace nall { + const uint32_t crc32_table[256] = { + 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, + 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, + 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2, + 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, + 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, + 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, + 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c, + 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, + 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, + 0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, + 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106, + 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, + 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, + 0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, + 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, + 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, + 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, + 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, + 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, + 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, + 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, + 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, + 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84, + 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, + 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, + 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, + 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e, + 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, + 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, + 0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, + 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28, + 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, + 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, + 0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, + 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242, + 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, + 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, + 0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, + 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, + 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, + 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, + 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, + 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d + }; + + inline uint32_t crc32_adjust(uint32_t crc32, uint8_t input) { + return ((crc32 >> 8) & 0x00ffffff) ^ crc32_table[(crc32 ^ input) & 0xff]; + } + + inline uint32_t crc32_calculate(const uint8_t *data, unsigned length) { + uint32_t crc32 = ~0; + for(unsigned i = 0; i < length; i++) { + crc32 = crc32_adjust(crc32, data[i]); + } + return ~crc32; + } +} + +#endif diff --git a/snesreader/nall/detect.hpp b/snesreader/nall/detect.hpp new file mode 100644 index 00000000..b4991aaf --- /dev/null +++ b/snesreader/nall/detect.hpp @@ -0,0 +1,30 @@ +#ifndef NALL_DETECT_HPP +#define NALL_DETECT_HPP + +/* Compiler detection */ + +#if defined(__GNUC__) + #define COMPILER_GCC +#elif defined(_MSC_VER) + #define COMPILER_VISUALC +#endif + +/* Platform detection */ + +#if defined(_WIN32) + #define PLATFORM_WIN +#elif defined(__APPLE__) + #define PLATFORM_OSX +#elif defined(linux) || defined(__sun__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) + #define PLATFORM_X +#endif + +/* Endian detection */ + +#if defined(__i386__) || defined(__amd64__) || defined(_M_IX86) || defined(_M_AMD64) + #define ARCH_LSB +#elif defined(__powerpc__) || defined(_M_PPC) || defined(__BIG_ENDIAN__) + #define ARCH_MSB +#endif + +#endif diff --git a/snesreader/nall/dictionary.hpp b/snesreader/nall/dictionary.hpp new file mode 100644 index 00000000..9e0a1620 --- /dev/null +++ b/snesreader/nall/dictionary.hpp @@ -0,0 +1,75 @@ +#ifndef NALL_DICTIONARY_HPP +#define NALL_DICTIONARY_HPP + +#include +#include +#include + +namespace nall { + class dictionary { + public: + string operator[](const char *input) { + for(unsigned i = 0; i < index_input.size(); i++) { + if(index_input[i] == input) return index_output[i]; + } + + //no match, use input; remove input identifier, if one exists + if(strbegin(input, "{{")) { + if(auto pos = strpos(input, "}}")) { + string temp = substr(input, pos() + 2); + return temp; + } + } + + return input; + } + + bool import(const char *filename) { + string data; + if(data.readfile(filename) == false) return false; + ltrim_once(data, "\xef\xbb\xbf"); //remove UTF-8 marker, if it exists + data.replace("\r", ""); + + lstring line; + line.split("\n", data); + for(unsigned i = 0; i < line.size(); i++) { + lstring part; + //format: "Input" = "Output" + part.qsplit("=", line[i]); + if(part.size() != 2) continue; + + //remove whitespace + trim(part[0]); + trim(part[1]); + + //remove quotes + trim_once(part[0], "\""); + trim_once(part[1], "\""); + + unsigned n = index_input.size(); + index_input[n] = part[0]; + index_output[n] = part[1]; + } + + return true; + } + + void reset() { + index_input.reset(); + index_output.reset(); + } + + ~dictionary() { + reset(); + } + + dictionary& operator=(const dictionary&) = delete; + dictionary(const dictionary&) = delete; + + protected: + lstring index_input; + lstring index_output; + }; +} + +#endif diff --git a/snesreader/nall/dl.hpp b/snesreader/nall/dl.hpp new file mode 100644 index 00000000..22acf51f --- /dev/null +++ b/snesreader/nall/dl.hpp @@ -0,0 +1,119 @@ +#ifndef NALL_DL_HPP +#define NALL_DL_HPP + +//dynamic linking support + +#include +#include +#include +#include + +#if defined(PLATFORM_X) || defined(PLATFORM_OSX) + #include +#elif defined(PLATFORM_WIN) + #include + #include +#endif + +namespace nall { + struct library { + bool opened() const { return handle; } + bool open(const char*); + void* sym(const char*); + void close(); + + library() : handle(0) {} + ~library() { close(); } + + library& operator=(const library&) = delete; + library(const library&) = delete; + + private: + uintptr_t handle; + }; + + #if defined(PLATFORM_X) + inline bool library::open(const char *name) { + if(handle) close(); + char *t = new char[strlen(name) + 256]; + strcpy(t, "lib"); + strcat(t, name); + strcat(t, ".so"); + handle = (uintptr_t)dlopen(t, RTLD_LAZY); + if(!handle) { + strcpy(t, "/usr/local/lib/lib"); + strcat(t, name); + strcat(t, ".so"); + handle = (uintptr_t)dlopen(t, RTLD_LAZY); + } + delete[] t; + return handle; + } + + inline void* library::sym(const char *name) { + if(!handle) return 0; + return dlsym((void*)handle, name); + } + + inline void library::close() { + if(!handle) return; + dlclose((void*)handle); + handle = 0; + } + #elif defined(PLATFORM_OSX) + inline bool library::open(const char *name) { + if(handle) close(); + char *t = new char[strlen(name) + 256]; + strcpy(t, "lib"); + strcat(t, name); + strcat(t, ".dylib"); + handle = (uintptr_t)dlopen(t, RTLD_LAZY); + if(!handle) { + strcpy(t, "/usr/local/lib/lib"); + strcat(t, name); + strcat(t, ".dylib"); + handle = (uintptr_t)dlopen(t, RTLD_LAZY); + } + delete[] t; + return handle; + } + + inline void* library::sym(const char *name) { + if(!handle) return 0; + return dlsym((void*)handle, name); + } + + inline void library::close() { + if(!handle) return; + dlclose((void*)handle); + handle = 0; + } + #elif defined(PLATFORM_WIN) + inline bool library::open(const char *name) { + if(handle) close(); + char *t = new char[strlen(name) + 8]; + strcpy(t, name); + strcat(t, ".dll"); + handle = (uintptr_t)LoadLibraryW(utf16_t(t)); + delete[] t; + return handle; + } + + inline void* library::sym(const char *name) { + if(!handle) return 0; + return (void*)GetProcAddress((HMODULE)handle, name); + } + + inline void library::close() { + if(!handle) return; + FreeLibrary((HMODULE)handle); + handle = 0; + } + #else + inline bool library::open(const char*) { return false; } + inline void* library::sym(const char*) { return 0; } + inline void library::close() {} + #endif +}; + +#endif diff --git a/snesreader/nall/endian.hpp b/snesreader/nall/endian.hpp new file mode 100644 index 00000000..40d15633 --- /dev/null +++ b/snesreader/nall/endian.hpp @@ -0,0 +1,38 @@ +#ifndef NALL_ENDIAN_HPP +#define NALL_ENDIAN_HPP + +#if !defined(ARCH_MSB) + //little-endian: uint8_t[] { 0x01, 0x02, 0x03, 0x04 } == 0x04030201 + #define order_lsb2(a,b) a,b + #define order_lsb3(a,b,c) a,b,c + #define order_lsb4(a,b,c,d) a,b,c,d + #define order_lsb5(a,b,c,d,e) a,b,c,d,e + #define order_lsb6(a,b,c,d,e,f) a,b,c,d,e,f + #define order_lsb7(a,b,c,d,e,f,g) a,b,c,d,e,f,g + #define order_lsb8(a,b,c,d,e,f,g,h) a,b,c,d,e,f,g,h + #define order_msb2(a,b) b,a + #define order_msb3(a,b,c) c,b,a + #define order_msb4(a,b,c,d) d,c,b,a + #define order_msb5(a,b,c,d,e) e,d,c,b,a + #define order_msb6(a,b,c,d,e,f) f,e,d,c,b,a + #define order_msb7(a,b,c,d,e,f,g) g,f,e,d,c,b,a + #define order_msb8(a,b,c,d,e,f,g,h) h,g,f,e,d,c,b,a +#else + //big-endian: uint8_t[] { 0x01, 0x02, 0x03, 0x04 } == 0x01020304 + #define order_lsb2(a,b) b,a + #define order_lsb3(a,b,c) c,b,a + #define order_lsb4(a,b,c,d) d,c,b,a + #define order_lsb5(a,b,c,d,e) e,d,c,b,a + #define order_lsb6(a,b,c,d,e,f) f,e,d,c,b,a + #define order_lsb7(a,b,c,d,e,f,g) g,f,e,d,c,b,a + #define order_lsb8(a,b,c,d,e,f,g,h) h,g,f,e,d,c,b,a + #define order_msb2(a,b) a,b + #define order_msb3(a,b,c) a,b,c + #define order_msb4(a,b,c,d) a,b,c,d + #define order_msb5(a,b,c,d,e) a,b,c,d,e + #define order_msb6(a,b,c,d,e,f) a,b,c,d,e,f + #define order_msb7(a,b,c,d,e,f,g) a,b,c,d,e,f,g + #define order_msb8(a,b,c,d,e,f,g,h) a,b,c,d,e,f,g,h +#endif + +#endif diff --git a/snesreader/nall/file.hpp b/snesreader/nall/file.hpp new file mode 100644 index 00000000..4c8ca8ee --- /dev/null +++ b/snesreader/nall/file.hpp @@ -0,0 +1,259 @@ +#ifndef NALL_FILE_HPP +#define NALL_FILE_HPP + +#include +#include + +#if !defined(_WIN32) + #include +#else + #include +#endif + +#include +#include +#include + +namespace nall { + inline FILE* fopen_utf8(const char *utf8_filename, const char *mode) { + #if !defined(_WIN32) + return fopen(utf8_filename, mode); + #else + return _wfopen(utf16_t(utf8_filename), utf16_t(mode)); + #endif + } + + class file { + public: + enum FileMode { mode_read, mode_write, mode_readwrite, mode_writeread }; + enum SeekMode { seek_absolute, seek_relative }; + + uint8_t read() { + if(!fp) return 0xff; //file not open + if(file_mode == mode_write) return 0xff; //reads not permitted + if(file_offset >= file_size) return 0xff; //cannot read past end of file + buffer_sync(); + return buffer[(file_offset++) & buffer_mask]; + } + + uintmax_t readl(unsigned length = 1) { + uintmax_t data = 0; + for(int i = 0; i < length; i++) { + data |= (uintmax_t)read() << (i << 3); + } + return data; + } + + uintmax_t readm(unsigned length = 1) { + uintmax_t data = 0; + while(length--) { + data <<= 8; + data |= read(); + } + return data; + } + + void read(uint8_t *buffer, unsigned length) { + while(length--) *buffer++ = read(); + } + + void write(uint8_t data) { + if(!fp) return; //file not open + if(file_mode == mode_read) return; //writes not permitted + buffer_sync(); + buffer[(file_offset++) & buffer_mask] = data; + buffer_dirty = true; + if(file_offset > file_size) file_size = file_offset; + } + + void writel(uintmax_t data, unsigned length = 1) { + while(length--) { + write(data); + data >>= 8; + } + } + + void writem(uintmax_t data, unsigned length = 1) { + for(int i = length - 1; i >= 0; i--) { + write(data >> (i << 3)); + } + } + + void write(const uint8_t *buffer, unsigned length) { + while(length--) write(*buffer++); + } + + void print(const char *string) { + if(!string) return; + while(*string) write(*string++); + } + + void flush() { + buffer_flush(); + fflush(fp); + } + + void seek(int offset, SeekMode mode = seek_absolute) { + if(!fp) return; //file not open + buffer_flush(); + + uintmax_t req_offset = file_offset; + switch(mode) { + case seek_absolute: req_offset = offset; break; + case seek_relative: req_offset += offset; break; + } + + if(req_offset < 0) req_offset = 0; //cannot seek before start of file + if(req_offset > file_size) { + if(file_mode == mode_read) { //cannot seek past end of file + req_offset = file_size; + } else { //pad file to requested location + file_offset = file_size; + while(file_size < req_offset) write(0x00); + } + } + + file_offset = req_offset; + } + + int offset() { + if(!fp) return -1; //file not open + return file_offset; + } + + int size() { + if(!fp) return -1; //file not open + return file_size; + } + + bool truncate(unsigned size) { + if(!fp) return false; //file not open + #if !defined(_WIN32) + return ftruncate(fileno(fp), size) == 0; + #else + return _chsize(fileno(fp), size) == 0; + #endif + } + + bool end() { + if(!fp) return true; //file not open + return file_offset >= file_size; + } + + static bool exists(const char *fn) { + #if !defined(_WIN32) + FILE *fp = fopen(fn, "rb"); + #else + FILE *fp = _wfopen(utf16_t(fn), L"rb"); + #endif + if(fp) { + fclose(fp); + return true; + } + return false; + } + + static unsigned size(const char *fn) { + #if !defined(_WIN32) + FILE *fp = fopen(fn, "rb"); + #else + FILE *fp = _wfopen(utf16_t(fn), L"rb"); + #endif + unsigned filesize = 0; + if(fp) { + fseek(fp, 0, SEEK_END); + filesize = ftell(fp); + fclose(fp); + } + return filesize; + } + + bool open() { + return fp; + } + + bool open(const char *fn, FileMode mode) { + if(fp) return false; + + switch(file_mode = mode) { + #if !defined(_WIN32) + case mode_read: fp = fopen(fn, "rb"); break; + case mode_write: fp = fopen(fn, "wb+"); break; //need read permission for buffering + case mode_readwrite: fp = fopen(fn, "rb+"); break; + case mode_writeread: fp = fopen(fn, "wb+"); break; + #else + case mode_read: fp = _wfopen(utf16_t(fn), L"rb"); break; + case mode_write: fp = _wfopen(utf16_t(fn), L"wb+"); break; + case mode_readwrite: fp = _wfopen(utf16_t(fn), L"rb+"); break; + case mode_writeread: fp = _wfopen(utf16_t(fn), L"wb+"); break; + #endif + } + if(!fp) return false; + buffer_offset = -1; //invalidate buffer + file_offset = 0; + fseek(fp, 0, SEEK_END); + file_size = ftell(fp); + fseek(fp, 0, SEEK_SET); + return true; + } + + void close() { + if(!fp) return; + buffer_flush(); + fclose(fp); + fp = 0; + } + + file() { + memset(buffer, 0, sizeof buffer); + buffer_offset = -1; + buffer_dirty = false; + fp = 0; + file_offset = 0; + file_size = 0; + file_mode = mode_read; + } + + ~file() { + close(); + } + + file& operator=(const file&) = delete; + file(const file&) = delete; + + private: + enum { buffer_size = 1 << 12, buffer_mask = buffer_size - 1 }; + char buffer[buffer_size]; + int buffer_offset; + bool buffer_dirty; + FILE *fp; + unsigned file_offset; + unsigned file_size; + FileMode file_mode; + + void buffer_sync() { + if(!fp) return; //file not open + if(buffer_offset != (file_offset & ~buffer_mask)) { + buffer_flush(); + buffer_offset = file_offset & ~buffer_mask; + fseek(fp, buffer_offset, SEEK_SET); + unsigned length = (buffer_offset + buffer_size) <= file_size ? buffer_size : (file_size & buffer_mask); + if(length) unsigned unused = fread(buffer, 1, length, fp); + } + } + + void buffer_flush() { + if(!fp) return; //file not open + if(file_mode == mode_read) return; //buffer cannot be written to + if(buffer_offset < 0) return; //buffer unused + if(buffer_dirty == false) return; //buffer unmodified since read + fseek(fp, buffer_offset, SEEK_SET); + unsigned length = (buffer_offset + buffer_size) <= file_size ? buffer_size : (file_size & buffer_mask); + if(length) unsigned unused = fwrite(buffer, 1, length, fp); + buffer_offset = -1; //invalidate buffer + buffer_dirty = false; + } + }; +} + +#endif diff --git a/snesreader/nall/filemap.hpp b/snesreader/nall/filemap.hpp new file mode 100644 index 00000000..a05f0eb7 --- /dev/null +++ b/snesreader/nall/filemap.hpp @@ -0,0 +1,190 @@ +#ifndef NALL_FILEMAP_HPP +#define NALL_FILEMAP_HPP + +#include +#include + +#include +#include +#if defined(_WIN32) + #include +#else + #include + #include + #include + #include + #include +#endif + +namespace nall { + class filemap { + public: + enum filemode { mode_read, mode_write, mode_readwrite, mode_writeread }; + + bool open(const char *filename, filemode mode) { return p_open(filename, mode); } + void close() { return p_close(); } + unsigned size() const { return p_size; } + uint8_t* handle() { return p_handle; } + const uint8_t* handle() const { return p_handle; } + filemap() : p_size(0), p_handle(0) { p_ctor(); } + ~filemap() { p_dtor(); } + + private: + unsigned p_size; + uint8_t *p_handle; + + #if defined(_WIN32) + //============= + //MapViewOfFile + //============= + + HANDLE p_filehandle, p_maphandle; + + bool p_open(const char *filename, filemode mode) { + int desired_access, creation_disposition, flprotect, map_access; + + switch(mode) { + default: return false; + case mode_read: + desired_access = GENERIC_READ; + creation_disposition = OPEN_EXISTING; + flprotect = PAGE_READONLY; + map_access = FILE_MAP_READ; + break; + case mode_write: + //write access requires read access + desired_access = GENERIC_WRITE; + creation_disposition = CREATE_ALWAYS; + flprotect = PAGE_READWRITE; + map_access = FILE_MAP_ALL_ACCESS; + break; + case mode_readwrite: + desired_access = GENERIC_READ | GENERIC_WRITE; + creation_disposition = OPEN_EXISTING; + flprotect = PAGE_READWRITE; + map_access = FILE_MAP_ALL_ACCESS; + break; + case mode_writeread: + desired_access = GENERIC_READ | GENERIC_WRITE; + creation_disposition = CREATE_NEW; + flprotect = PAGE_READWRITE; + map_access = FILE_MAP_ALL_ACCESS; + break; + } + + p_filehandle = CreateFileW(utf16_t(filename), desired_access, FILE_SHARE_READ, NULL, + creation_disposition, FILE_ATTRIBUTE_NORMAL, NULL); + if(p_filehandle == INVALID_HANDLE_VALUE) return false; + + p_size = GetFileSize(p_filehandle, NULL); + + p_maphandle = CreateFileMapping(p_filehandle, NULL, flprotect, 0, p_size, NULL); + if(p_maphandle == INVALID_HANDLE_VALUE) { + CloseHandle(p_filehandle); + p_filehandle = INVALID_HANDLE_VALUE; + return false; + } + + p_handle = (uint8_t*)MapViewOfFile(p_maphandle, map_access, 0, 0, p_size); + return p_handle; + } + + void p_close() { + if(p_handle) { + UnmapViewOfFile(p_handle); + p_handle = 0; + } + + if(p_maphandle != INVALID_HANDLE_VALUE) { + CloseHandle(p_maphandle); + p_maphandle = INVALID_HANDLE_VALUE; + } + + if(p_filehandle != INVALID_HANDLE_VALUE) { + CloseHandle(p_filehandle); + p_filehandle = INVALID_HANDLE_VALUE; + } + } + + void p_ctor() { + p_filehandle = INVALID_HANDLE_VALUE; + p_maphandle = INVALID_HANDLE_VALUE; + } + + void p_dtor() { + close(); + } + + #else + //==== + //mmap + //==== + + int p_fd; + + bool p_open(const char *filename, filemode mode) { + int open_flags, mmap_flags; + + switch(mode) { + default: return false; + case mode_read: + open_flags = O_RDONLY; + mmap_flags = PROT_READ; + break; + case mode_write: + open_flags = O_RDWR | O_CREAT; //mmap() requires read access + mmap_flags = PROT_WRITE; + break; + case mode_readwrite: + open_flags = O_RDWR; + mmap_flags = PROT_READ | PROT_WRITE; + break; + case mode_writeread: + open_flags = O_RDWR | O_CREAT; + mmap_flags = PROT_READ | PROT_WRITE; + break; + } + + p_fd = ::open(filename, open_flags); + if(p_fd < 0) return false; + + struct stat p_stat; + fstat(p_fd, &p_stat); + p_size = p_stat.st_size; + + p_handle = (uint8_t*)mmap(0, p_size, mmap_flags, MAP_SHARED, p_fd, 0); + if(p_handle == MAP_FAILED) { + p_handle = 0; + ::close(p_fd); + p_fd = -1; + return false; + } + + return p_handle; + } + + void p_close() { + if(p_handle) { + munmap(p_handle, p_size); + p_handle = 0; + } + + if(p_fd >= 0) { + ::close(p_fd); + p_fd = -1; + } + } + + void p_ctor() { + p_fd = -1; + } + + void p_dtor() { + p_close(); + } + + #endif + }; +} + +#endif diff --git a/snesreader/nall/foreach.hpp b/snesreader/nall/foreach.hpp new file mode 100644 index 00000000..ea975b84 --- /dev/null +++ b/snesreader/nall/foreach.hpp @@ -0,0 +1,31 @@ +#ifndef NALL_FOREACH_HPP +#define NALL_FOREACH_HPP + +#undef foreach +#define foreach(iter, object) \ + for(unsigned foreach_counter = 0, foreach_limit = foreach_size(object), foreach_once = 0, foreach_broken = 0; foreach_counter < foreach_limit && foreach_broken == 0; foreach_counter++, foreach_once = 0) \ + for(auto &iter = object[foreach_counter]; foreach_once == 0 && (foreach_broken = 1); foreach_once++, foreach_broken = 0) + +#include +#include +#include + +namespace nall { + template unsigned foreach_size(const T& object, typename mp_enable_if>::type = 0) { + return object.count(); + } + + template unsigned foreach_size(const T& object, typename mp_enable_if>::type = 0) { + return object.length(); + } + + template unsigned foreach_size(const T& object, typename mp_enable_if>::type = 0) { + return object.size(); + } + + template unsigned foreach_size(const T& object, typename mp_enable_if>::type = 0) { + return sizeof(T) / sizeof(typename std::remove_extent::type); + } +} + +#endif diff --git a/snesreader/nall/function.hpp b/snesreader/nall/function.hpp new file mode 100644 index 00000000..3f0f704e --- /dev/null +++ b/snesreader/nall/function.hpp @@ -0,0 +1,102 @@ +#ifndef NALL_FUNCTION_HPP +#define NALL_FUNCTION_HPP + +#include +#include + +namespace nall { + template class function; + + template + class function { + private: + struct base1 { virtual void func1(P...) {} }; + struct base2 { virtual void func2(P...) {} }; + struct derived : base1, virtual base2 {}; + + struct data_t { + R (*callback)(const data_t&, P...); + union { + R (*callback_global)(P...); + struct { + R (derived::*callback_member)(P...); + void *object; + }; + }; + } data; + + static R callback_global(const data_t &data, P... p) { + return data.callback_global(p...); + } + + template + static R callback_member(const data_t &data, P... p) { + return (((C*)data.object)->*((R (C::*&)(P...))data.callback_member))(p...); + } + + public: + R operator()(P... p) const { return data.callback(data, p...); } + operator bool() const { return data.callback; } + void reset() { data.callback = 0; } + + function& operator=(const function &source) { memcpy(&data, &source.data, sizeof(data_t)); return *this; } + function(const function &source) { operator=(source); } + + //no pointer + function() { + data.callback = 0; + } + + //symbolic link pointer (nall/dl.hpp::sym, etc) + function(void *callback) { + data.callback = callback ? &callback_global : 0; + data.callback_global = (R (*)(P...))callback; + } + + //global function pointer + function(R (*callback)(P...)) { + data.callback = &callback_global; + data.callback_global = callback; + } + + //member function pointer + template + function(R (C::*callback)(P...), C *object) { + static_assert(sizeof data.callback_member >= sizeof callback, "callback_member is too small"); + data.callback = &callback_member; + (R (C::*&)(P...))data.callback_member = callback; + data.object = object; + } + + //const member function pointer + template + function(R (C::*callback)(P...) const, C *object) { + static_assert(sizeof data.callback_member >= sizeof callback, "callback_member is too small"); + data.callback = &callback_member; + (R (C::*&)(P...))data.callback_member = (R (C::*&)(P...))callback; + data.object = object; + } + + //lambda function pointer + template + function(T callback) { + static_assert(std::is_same::type>::value, "lambda mismatch"); + data.callback = &callback_global; + data.callback_global = (R (*)(P...))callback; + } + }; + + //bind functions to ease construction and assignment of function() with more than one argument + + template + function bind(R (C::*callback)(P...), C *object) { + return function(callback, object); + } + + template + function bind(R (C::*callback)(P...) const, C *object) { + return function(callback, object); + } +} + +#endif diff --git a/snesreader/nall/input.hpp b/snesreader/nall/input.hpp new file mode 100644 index 00000000..83c4a484 --- /dev/null +++ b/snesreader/nall/input.hpp @@ -0,0 +1,386 @@ +#ifndef NALL_INPUT_HPP +#define NALL_INPUT_HPP + +#include +#include +#include + +#include +#include + +namespace nall { + +struct Keyboard; +Keyboard& keyboard(unsigned = 0); + +static const char KeyboardScancodeName[][64] = { + "Escape", "F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12", + "PrintScreen", "ScrollLock", "Pause", "Tilde", + "Num1", "Num2", "Num3", "Num4", "Num5", "Num6", "Num7", "Num8", "Num9", "Num0", + "Dash", "Equal", "Backspace", + "Insert", "Delete", "Home", "End", "PageUp", "PageDown", + "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", + "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", + "LeftBracket", "RightBracket", "Backslash", "Semicolon", "Apostrophe", "Comma", "Period", "Slash", + "Keypad1", "Keypad2", "Keypad3", "Keypad4", "Keypad5", "Keypad6", "Keypad7", "Keypad8", "Keypad9", "Keypad0", + "Point", "Enter", "Add", "Subtract", "Multiply", "Divide", + "NumLock", "CapsLock", + "Up", "Down", "Left", "Right", + "Tab", "Return", "Spacebar", "Menu", + "Shift", "Control", "Alt", "Super", +}; + +struct Keyboard { + const unsigned ID; + enum { Base = 1 }; + enum { Count = 8, Size = 128 }; + + enum Scancode { + Escape, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, + PrintScreen, ScrollLock, Pause, Tilde, + Num1, Num2, Num3, Num4, Num5, Num6, Num7, Num8, Num9, Num0, + Dash, Equal, Backspace, + Insert, Delete, Home, End, PageUp, PageDown, + A, B, C, D, E, F, G, H, I, J, K, L, M, + N, O, P, Q, R, S, T, U, V, W, X, Y, Z, + LeftBracket, RightBracket, Backslash, Semicolon, Apostrophe, Comma, Period, Slash, + Keypad1, Keypad2, Keypad3, Keypad4, Keypad5, Keypad6, Keypad7, Keypad8, Keypad9, Keypad0, + Point, Enter, Add, Subtract, Multiply, Divide, + NumLock, CapsLock, + Up, Down, Left, Right, + Tab, Return, Spacebar, Menu, + Shift, Control, Alt, Super, + Limit, + }; + + static signed numberDecode(uint16_t scancode) { + for(unsigned i = 0; i < Count; i++) { + if(keyboard(i).belongsTo(scancode)) return i; + } + return -1; + } + + static signed keyDecode(uint16_t scancode) { + for(unsigned i = 0; i < Count; i++) { + if(keyboard(i).isKey(scancode)) return scancode - keyboard(i).key(Escape); + } + return -1; + } + + static signed modifierDecode(uint16_t scancode) { + for(unsigned i = 0; i < Count; i++) { + if(keyboard(i).isModifier(scancode)) return scancode - keyboard(i).key(Shift); + } + return -1; + } + + static bool isAnyKey(uint16_t scancode) { + for(unsigned i = 0; i < Count; i++) { + if(keyboard(i).isKey(scancode)) return true; + } + return false; + } + + static bool isAnyModifier(uint16_t scancode) { + for(unsigned i = 0; i < Count; i++) { + if(keyboard(i).isModifier(scancode)) return true; + } + return false; + } + + static uint16_t decode(const char *name) { + string s(name); + if(!strbegin(name, "KB")) return 0; + ltrim(s, "KB"); + unsigned id = strunsigned(s); + auto pos = strpos(s, "::"); + if(!pos) return 0; + s = substr(s, pos() + 2); + for(unsigned i = 0; i < Limit; i++) { + if(s == KeyboardScancodeName[i]) return Base + Size * id + i; + } + return 0; + } + + string encode(uint16_t code) const { + unsigned index = 0; + for(unsigned i = 0; i < Count; i++) { + if(code >= Base + Size * i && code < Base + Size * (i + 1)) { + index = code - (Base + Size * i); + break; + } + } + return string() << "KB" << ID << "::" << KeyboardScancodeName[index]; + } + + uint16_t operator[](Scancode code) const { return Base + ID * Size + code; } + uint16_t key(unsigned id) const { return Base + Size * ID + id; } + bool isKey(unsigned id) const { return id >= key(Escape) && id <= key(Menu); } + bool isModifier(unsigned id) const { return id >= key(Shift) && id <= key(Super); } + bool belongsTo(uint16_t scancode) const { return isKey(scancode) || isModifier(scancode); } + + Keyboard(unsigned ID_) : ID(ID_) {} +}; + +inline Keyboard& keyboard(unsigned id) { + static Keyboard kb0(0), kb1(1), kb2(2), kb3(3), kb4(4), kb5(5), kb6(6), kb7(7); + switch(id) { default: + case 0: return kb0; case 1: return kb1; case 2: return kb2; case 3: return kb3; + case 4: return kb4; case 5: return kb5; case 6: return kb6; case 7: return kb7; + } +} + +static const char MouseScancodeName[][64] = { + "Xaxis", "Yaxis", "Zaxis", + "Button0", "Button1", "Button2", "Button3", "Button4", "Button5", "Button6", "Button7", +}; + +struct Mouse; +Mouse& mouse(unsigned = 0); + +struct Mouse { + const unsigned ID; + enum { Base = Keyboard::Base + Keyboard::Size * Keyboard::Count }; + enum { Count = 8, Size = 16 }; + enum { Axes = 3, Buttons = 8 }; + + enum Scancode { + Xaxis, Yaxis, Zaxis, + Button0, Button1, Button2, Button3, Button4, Button5, Button6, Button7, + Limit, + }; + + static signed numberDecode(uint16_t scancode) { + for(unsigned i = 0; i < Count; i++) { + if(mouse(i).belongsTo(scancode)) return i; + } + return -1; + } + + static signed axisDecode(uint16_t scancode) { + for(unsigned i = 0; i < Count; i++) { + if(mouse(i).isAxis(scancode)) return scancode - mouse(i).axis(0); + } + return -1; + } + + static signed buttonDecode(uint16_t scancode) { + for(unsigned i = 0; i < Count; i++) { + if(mouse(i).isButton(scancode)) return scancode - mouse(i).button(0); + } + return -1; + } + + static bool isAnyAxis(uint16_t scancode) { + for(unsigned i = 0; i < Count; i++) { + if(mouse(i).isAxis(scancode)) return true; + } + return false; + } + + static bool isAnyButton(uint16_t scancode) { + for(unsigned i = 0; i < Count; i++) { + if(mouse(i).isButton(scancode)) return true; + } + return false; + } + + static uint16_t decode(const char *name) { + string s(name); + if(!strbegin(name, "MS")) return 0; + ltrim(s, "MS"); + unsigned id = strunsigned(s); + auto pos = strpos(s, "::"); + if(!pos) return 0; + s = substr(s, pos() + 2); + for(unsigned i = 0; i < Limit; i++) { + if(s == MouseScancodeName[i]) return Base + Size * id + i; + } + return 0; + } + + string encode(uint16_t code) const { + unsigned index = 0; + for(unsigned i = 0; i < Count; i++) { + if(code >= Base + Size * i && code < Base + Size * (i + 1)) { + index = code - (Base + Size * i); + break; + } + } + return string() << "MS" << ID << "::" << MouseScancodeName[index]; + } + + uint16_t operator[](Scancode code) const { return Base + ID * Size + code; } + uint16_t axis(unsigned id) const { return Base + Size * ID + Xaxis + id; } + uint16_t button(unsigned id) const { return Base + Size * ID + Button0 + id; } + bool isAxis(unsigned id) const { return id >= axis(0) && id <= axis(2); } + bool isButton(unsigned id) const { return id >= button(0) && id <= button(7); } + bool belongsTo(uint16_t scancode) const { return isAxis(scancode) || isButton(scancode); } + + Mouse(unsigned ID_) : ID(ID_) {} +}; + +inline Mouse& mouse(unsigned id) { + static Mouse ms0(0), ms1(1), ms2(2), ms3(3), ms4(4), ms5(5), ms6(6), ms7(7); + switch(id) { default: + case 0: return ms0; case 1: return ms1; case 2: return ms2; case 3: return ms3; + case 4: return ms4; case 5: return ms5; case 6: return ms6; case 7: return ms7; + } +} + +static const char JoypadScancodeName[][64] = { + "Hat0", "Hat1", "Hat2", "Hat3", "Hat4", "Hat5", "Hat6", "Hat7", + "Axis0", "Axis1", "Axis2", "Axis3", "Axis4", "Axis5", "Axis6", "Axis7", + "Axis8", "Axis9", "Axis10", "Axis11", "Axis12", "Axis13", "Axis14", "Axis15", + "Button0", "Button1", "Button2", "Button3", "Button4", "Button5", "Button6", "Button7", + "Button8", "Button9", "Button10", "Button11", "Button12", "Button13", "Button14", "Button15", + "Button16", "Button17", "Button18", "Button19", "Button20", "Button21", "Button22", "Button23", + "Button24", "Button25", "Button26", "Button27", "Button28", "Button29", "Button30", "Button31", +}; + +struct Joypad; +Joypad& joypad(unsigned = 0); + +struct Joypad { + const unsigned ID; + enum { Base = Mouse::Base + Mouse::Size * Mouse::Count }; + enum { Count = 8, Size = 64 }; + enum { Hats = 8, Axes = 16, Buttons = 32 }; + + enum Scancode { + Hat0, Hat1, Hat2, Hat3, Hat4, Hat5, Hat6, Hat7, + Axis0, Axis1, Axis2, Axis3, Axis4, Axis5, Axis6, Axis7, + Axis8, Axis9, Axis10, Axis11, Axis12, Axis13, Axis14, Axis15, + Button0, Button1, Button2, Button3, Button4, Button5, Button6, Button7, + Button8, Button9, Button10, Button11, Button12, Button13, Button14, Button15, + Button16, Button17, Button18, Button19, Button20, Button21, Button22, Button23, + Button24, Button25, Button26, Button27, Button28, Button29, Button30, Button31, + Limit, + }; + + enum Hat { HatCenter = 0, HatUp = 1, HatRight = 2, HatDown = 4, HatLeft = 8 }; + + static signed numberDecode(uint16_t scancode) { + for(unsigned i = 0; i < Count; i++) { + if(joypad(i).belongsTo(scancode)) return i; + } + return -1; + } + + static signed hatDecode(uint16_t scancode) { + for(unsigned i = 0; i < Count; i++) { + if(joypad(i).isHat(scancode)) return scancode - joypad(i).hat(0); + } + return -1; + } + + static signed axisDecode(uint16_t scancode) { + for(unsigned i = 0; i < Count; i++) { + if(joypad(i).isAxis(scancode)) return scancode - joypad(i).axis(0); + } + return -1; + } + + static signed buttonDecode(uint16_t scancode) { + for(unsigned i = 0; i < Count; i++) { + if(joypad(i).isButton(scancode)) return scancode - joypad(i).button(0); + } + return -1; + } + + static bool isAnyHat(uint16_t scancode) { + for(unsigned i = 0; i < Count; i++) { + if(joypad(i).isHat(scancode)) return true; + } + return false; + } + + static bool isAnyAxis(uint16_t scancode) { + for(unsigned i = 0; i < Count; i++) { + if(joypad(i).isAxis(scancode)) return true; + } + return false; + } + + static bool isAnyButton(uint16_t scancode) { + for(unsigned i = 0; i < Count; i++) { + if(joypad(i).isButton(scancode)) return true; + } + return false; + } + + static uint16_t decode(const char *name) { + string s(name); + if(!strbegin(name, "JP")) return 0; + ltrim(s, "JP"); + unsigned id = strunsigned(s); + auto pos = strpos(s, "::"); + if(!pos) return 0; + s = substr(s, pos() + 2); + for(unsigned i = 0; i < Limit; i++) { + if(s == JoypadScancodeName[i]) return Base + Size * id + i; + } + return 0; + } + + string encode(uint16_t code) const { + unsigned index = 0; + for(unsigned i = 0; i < Count; i++) { + if(code >= Base + Size * i && code < Base + Size * (i + 1)) { + index = code - (Base + Size * i); + } + } + return string() << "JP" << ID << "::" << JoypadScancodeName[index]; + } + + uint16_t operator[](Scancode code) const { return Base + ID * Size + code; } + uint16_t hat(unsigned id) const { return Base + Size * ID + Hat0 + id; } + uint16_t axis(unsigned id) const { return Base + Size * ID + Axis0 + id; } + uint16_t button(unsigned id) const { return Base + Size * ID + Button0 + id; } + bool isHat(unsigned id) const { return id >= hat(0) && id <= hat(7); } + bool isAxis(unsigned id) const { return id >= axis(0) && id <= axis(15); } + bool isButton(unsigned id) const { return id >= button(0) && id <= button(31); } + bool belongsTo(uint16_t scancode) const { return isHat(scancode) || isAxis(scancode) || isButton(scancode); } + + Joypad(unsigned ID_) : ID(ID_) {} +}; + +inline Joypad& joypad(unsigned id) { + static Joypad jp0(0), jp1(1), jp2(2), jp3(3), jp4(4), jp5(5), jp6(6), jp7(7); + switch(id) { default: + case 0: return jp0; case 1: return jp1; case 2: return jp2; case 3: return jp3; + case 4: return jp4; case 5: return jp5; case 6: return jp6; case 7: return jp7; + } +} + +struct Scancode { + enum { None = 0, Limit = Joypad::Base + Joypad::Size * Joypad::Count }; + + static uint16_t decode(const char *name) { + uint16_t code; + code = Keyboard::decode(name); + if(code) return code; + code = Mouse::decode(name); + if(code) return code; + code = Joypad::decode(name); + if(code) return code; + return None; + } + + static string encode(uint16_t code) { + for(unsigned i = 0; i < Keyboard::Count; i++) { + if(keyboard(i).belongsTo(code)) return keyboard(i).encode(code); + } + for(unsigned i = 0; i < Mouse::Count; i++) { + if(mouse(i).belongsTo(code)) return mouse(i).encode(code); + } + for(unsigned i = 0; i < Joypad::Count; i++) { + if(joypad(i).belongsTo(code)) return joypad(i).encode(code); + } + return "None"; + } +}; + +} + +#endif diff --git a/snesreader/nall/lzss.hpp b/snesreader/nall/lzss.hpp new file mode 100644 index 00000000..202bc814 --- /dev/null +++ b/snesreader/nall/lzss.hpp @@ -0,0 +1,81 @@ +#ifndef NALL_LZSS_HPP +#define NALL_LZSS_HPP + +#include +#include +#include + +namespace nall { + class lzss { + public: + static bool encode(uint8_t *&output, unsigned &outlength, const uint8_t *input, unsigned inlength) { + output = new(zeromemory) uint8_t[inlength * 9 / 8 + 9]; + + unsigned i = 0, o = 0; + while(i < inlength) { + unsigned flagoffset = o++; + uint8_t flag = 0x00; + + for(unsigned b = 0; b < 8 && i < inlength; b++) { + unsigned longest = 0, pointer; + for(unsigned index = 1; index < 4096; index++) { + unsigned count = 0; + while(true) { + if(count >= 15 + 3) break; //verify pattern match is not longer than max length + if(i + count >= inlength) break; //verify pattern match does not read past end of input + if(i + count < index) break; //verify read is not before start of input + if(input[i + count] != input[i + count - index]) break; //verify pattern still matches + count++; + } + + if(count > longest) { + longest = count; + pointer = index; + } + } + + if(longest < 3) output[o++] = input[i++]; + else { + flag |= 1 << b; + uint16_t x = ((longest - 3) << 12) + pointer; + output[o++] = x; + output[o++] = x >> 8; + i += longest; + } + } + + output[flagoffset] = flag; + } + + outlength = o; + return true; + } + + static bool decode(uint8_t *&output, const uint8_t *input, unsigned length) { + output = new(zeromemory) uint8_t[length]; + + unsigned i = 0, o = 0; + while(o < length) { + uint8_t flag = input[i++]; + + for(unsigned b = 0; b < 8 && o < length; b++) { + if(!(flag & (1 << b))) output[o++] = input[i++]; + else { + uint16_t offset = input[i++]; + offset += input[i++] << 8; + uint16_t lookuplength = (offset >> 12) + 3; + offset &= 4095; + for(unsigned index = 0; index < lookuplength && o + index < length; index++) { + output[o + index] = output[o + index - offset]; + } + o += lookuplength; + } + } + } + + return true; + } + }; +} + +#endif diff --git a/snesreader/nall/moduloarray.hpp b/snesreader/nall/moduloarray.hpp new file mode 100644 index 00000000..be549ae9 --- /dev/null +++ b/snesreader/nall/moduloarray.hpp @@ -0,0 +1,40 @@ +#ifndef NALL_MODULO_HPP +#define NALL_MODULO_HPP + +#include + +namespace nall { + template class modulo_array { + public: + inline T operator[](int index) const { + return buffer[size + index]; + } + + inline T read(int index) const { + return buffer[size + index]; + } + + inline void write(unsigned index, const T value) { + buffer[index] = + buffer[index + size] = + buffer[index + size + size] = value; + } + + void serialize(serializer &s) { + s.array(buffer, size * 3); + } + + modulo_array() { + buffer = new T[size * 3](); + } + + ~modulo_array() { + delete[] buffer; + } + + private: + T *buffer; + }; +} + +#endif diff --git a/snesreader/nall/platform.hpp b/snesreader/nall/platform.hpp new file mode 100644 index 00000000..68ed37ce --- /dev/null +++ b/snesreader/nall/platform.hpp @@ -0,0 +1,80 @@ +#ifndef NALL_PLATFORM_HPP +#define NALL_PLATFORM_HPP + +#include + +//========================= +//standard platform headers +//========================= + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(_WIN32) + #include + #include + #include + #undef interface +#else + #include + #include + #include +#endif + +//================== +//warning supression +//================== + +//Visual C++ +#if defined(_MSC_VER) + //disable libc "deprecation" warnings + #pragma warning(disable:4996) +#endif + +//================ +//POSIX compliance +//================ + +#if defined(_MSC_VER) + #define PATH_MAX _MAX_PATH + #define va_copy(dest, src) ((dest) = (src)) +#endif + +#if defined(_WIN32) + #define getcwd _getcwd + #define ftruncate _chsize + #define putenv _putenv + #define mkdir(n, m) _wmkdir(nall::utf16_t(n)) + #define rmdir _rmdir + #define vsnprintf _vsnprintf + #define usleep(n) Sleep(n / 1000) +#endif + +//================ +//inline expansion +//================ + +#if defined(__GNUC__) + #define noinline __attribute__((noinline)) + #define inline inline + #define alwaysinline inline __attribute__((always_inline)) +#elif defined(_MSC_VER) + #define noinline __declspec(noinline) + #define inline inline + #define alwaysinline inline __forceinline +#else + #define noinline + #define inline inline + #define alwaysinline inline +#endif + +#endif + diff --git a/snesreader/nall/priorityqueue.hpp b/snesreader/nall/priorityqueue.hpp new file mode 100644 index 00000000..7104e791 --- /dev/null +++ b/snesreader/nall/priorityqueue.hpp @@ -0,0 +1,109 @@ +#ifndef NALL_PRIORITYQUEUE_HPP +#define NALL_PRIORITYQUEUE_HPP + +#include +#include +#include +#include + +namespace nall { + template void priority_queue_nocallback(type_t) {} + + //priority queue implementation using binary min-heap array; + //does not require normalize() function. + //O(1) find (tick) + //O(log n) insert (enqueue) + //O(log n) remove (dequeue) + template class priority_queue { + public: + inline void tick(unsigned ticks) { + basecounter += ticks; + while(heapsize && gte(basecounter, heap[0].counter)) callback(dequeue()); + } + + //counter is relative to current time (eg enqueue(64, ...) fires in 64 ticks); + //counter cannot exceed std::numeric_limits::max() >> 1. + void enqueue(unsigned counter, type_t event) { + unsigned child = heapsize++; + counter += basecounter; + + while(child) { + unsigned parent = (child - 1) >> 1; + if(gte(counter, heap[parent].counter)) break; + + heap[child].counter = heap[parent].counter; + heap[child].event = heap[parent].event; + child = parent; + } + + heap[child].counter = counter; + heap[child].event = event; + } + + type_t dequeue() { + type_t event(heap[0].event); + unsigned parent = 0; + unsigned counter = heap[--heapsize].counter; + + while(true) { + unsigned child = (parent << 1) + 1; + if(child >= heapsize) break; + if(child + 1 < heapsize && gte(heap[child].counter, heap[child + 1].counter)) child++; + if(gte(heap[child].counter, counter)) break; + + heap[parent].counter = heap[child].counter; + heap[parent].event = heap[child].event; + parent = child; + } + + heap[parent].counter = counter; + heap[parent].event = heap[heapsize].event; + return event; + } + + void reset() { + basecounter = 0; + heapsize = 0; + } + + void serialize(serializer &s) { + s.integer(basecounter); + s.integer(heapsize); + for(unsigned n = 0; n < heapcapacity; n++) { + s.integer(heap[n].counter); + s.integer(heap[n].event); + } + } + + priority_queue(unsigned size, function callback_ = &priority_queue_nocallback) + : callback(callback_) { + heap = new heap_t[size]; + heapcapacity = size; + reset(); + } + + ~priority_queue() { + delete[] heap; + } + + priority_queue& operator=(const priority_queue&) = delete; + priority_queue(const priority_queue&) = delete; + + private: + function callback; + unsigned basecounter; + unsigned heapsize; + unsigned heapcapacity; + struct heap_t { + unsigned counter; + type_t event; + } *heap; + + //return true if x is greater than or equal to y + inline bool gte(unsigned x, unsigned y) { + return x - y < (std::numeric_limits::max() >> 1); + } + }; +} + +#endif diff --git a/snesreader/nall/property.hpp b/snesreader/nall/property.hpp new file mode 100644 index 00000000..6fd33acd --- /dev/null +++ b/snesreader/nall/property.hpp @@ -0,0 +1,91 @@ +#ifndef NALL_PROPERTY_HPP +#define NALL_PROPERTY_HPP + +//nall::property implements ownership semantics into container classes +//example: property::readonly implies that only owner has full +//access to type; and all other code has readonly access. +// +//this code relies on extended friend semantics from C++0x to work, as it +//declares a friend class via a template paramter. it also exploits a bug in +//G++ 4.x to work even in C++98 mode. +// +//if compiling elsewhere, simply remove the friend class and private semantics + +//property can be used either of two ways: +//struct foo { +// property::readonly x; +// property::readwrite y; +//}; +//-or- +//struct foo : property { +// readonly x; +// readwrite y; +//}; + +//return types are const T& (byref) instead fo T (byval) to avoid major speed +//penalties for objects with expensive copy constructors + +//operator-> provides access to underlying object type: +//readonly foo; +//foo->bar(); +//... will call Object::bar(); + +//operator='s reference is constant so as to avoid leaking a reference handle +//that could bypass access restrictions + +//both constant and non-constant operators are provided, though it may be +//necessary to cast first, for instance: +//struct foo : property { readonly bar; } object; +//int main() { int value = const_cast(object); } + +//writeonly is useful for objects that have non-const reads, but const writes. +//however, to avoid leaking handles, the interface is very restricted. the only +//way to write is via operator=, which requires conversion via eg copy +//constructor. example: +//struct foo { +// foo(bool value) { ... } +//}; +//writeonly bar; +//bar = true; + +namespace nall { + template struct property { + template struct traits { typedef T type; }; + + template struct readonly { + const T* operator->() const { return &value; } + const T& operator()() const { return value; } + operator const T&() const { return value; } + private: + T* operator->() { return &value; } + operator T&() { return value; } + const T& operator=(const T& value_) { return value = value_; } + T value; + friend class traits::type; + }; + + template struct writeonly { + void operator=(const T& value_) { value = value_; } + private: + const T* operator->() const { return &value; } + const T& operator()() const { return value; } + operator const T&() const { return value; } + T* operator->() { return &value; } + operator T&() { return value; } + T value; + friend class traits::type; + }; + + template struct readwrite { + const T* operator->() const { return &value; } + const T& operator()() const { return value; } + operator const T&() const { return value; } + T* operator->() { return &value; } + operator T&() { return value; } + const T& operator=(const T& value_) { return value = value_; } + T value; + }; + }; +} + +#endif diff --git a/snesreader/nall/qt/Makefile b/snesreader/nall/qt/Makefile new file mode 100644 index 00000000..69e84960 --- /dev/null +++ b/snesreader/nall/qt/Makefile @@ -0,0 +1,55 @@ +# requires nall/Makefile + +# imports: +# $(qtlibs) -- list of Qt components to link against + +# exports the following symbols: +# $(moc) -- meta-object compiler +# $(rcc) -- resource compiler +# $(qtinc) -- includes for compiling +# $(qtlib) -- libraries for linking + +ifeq ($(moc),) +moc := moc +endif + +ifeq ($(rcc),) +rcc := rcc +endif + +ifeq ($(platform),x) + qtinc := `pkg-config --cflags $(qtlibs)` + qtlib := `pkg-config --libs $(qtlibs)` +else ifeq ($(platform),osx) + qtinc := $(foreach lib,$(qtlibs),-I/Library/Frameworks/$(lib).framework/Versions/4/Headers) + + qtlib := -L/Library/Frameworks + qtlib += $(foreach lib,$(qtlibs),-framework $(lib)) + qtlib += -framework Carbon + qtlib += -framework Cocoa + qtlib += -framework OpenGL + qtlib += -framework AppKit + qtlib += -framework ApplicationServices +else ifeq ($(platform),win) + ifeq ($(qtpath),) + # find Qt install directory from PATH environment variable + qtpath := $(foreach path,$(subst ;, ,$(PATH)),$(if $(wildcard $(path)/$(moc).exe),$(path))) + qtpath := $(strip $(qtpath)) + qtpath := $(subst \,/,$(qtpath)) + qtpath := $(patsubst %/bin,%,$(qtpath)) + endif + + qtinc := -I$(qtpath)/include + qtinc += $(foreach lib,$(qtlibs),-I$(qtpath)/include/$(lib)) + + qtlib := -L$(qtpath)/lib + qtlib += -L$(qtpath)/plugins/imageformats + + qtlib += $(foreach lib,$(qtlibs),-l$(lib)4) + qtlib += -lmingw32 -lqtmain -lcomdlg32 -loleaut32 -limm32 -lwinmm + qtlib += -lwinspool -lmsimg32 -lole32 -ladvapi32 -lws2_32 -luuid -lgdi32 + qtlib += $(foreach lib,$(qtlibs),-l$(lib)4) + + # optional image-file support: + # qtlib += -lqjpeg -lqmng +endif diff --git a/snesreader/nall/qt/check-action.moc.hpp b/snesreader/nall/qt/check-action.moc.hpp new file mode 100644 index 00000000..db378fe9 --- /dev/null +++ b/snesreader/nall/qt/check-action.moc.hpp @@ -0,0 +1,41 @@ +#ifndef NALL_QT_CHECKACTION_HPP +#define NALL_QT_CHECKACTION_HPP + +namespace nall { + +class CheckAction : public QAction { + Q_OBJECT + +public: + bool isChecked() const; + void setChecked(bool); + void toggleChecked(); + CheckAction(const QString&, QObject*); + +protected slots: + +protected: + bool checked; +}; + +inline bool CheckAction::isChecked() const { + return checked; +} + +inline void CheckAction::setChecked(bool checked_) { + checked = checked_; + if(checked) setIcon(QIcon(":/16x16/item-check-on.png")); + else setIcon(QIcon(":/16x16/item-check-off.png")); +} + +inline void CheckAction::toggleChecked() { + setChecked(!isChecked()); +} + +inline CheckAction::CheckAction(const QString &text, QObject *parent) : QAction(text, parent) { + setChecked(false); +} + +} + +#endif diff --git a/snesreader/nall/qt/concept.hpp b/snesreader/nall/qt/concept.hpp new file mode 100644 index 00000000..51cacef4 --- /dev/null +++ b/snesreader/nall/qt/concept.hpp @@ -0,0 +1,10 @@ +#ifndef NALL_QT_CONCEPT_HPP +#define NALL_QT_CONCEPT_HPP + +#include + +namespace nall { + template struct has_count> { enum { value = true }; }; +} + +#endif diff --git a/snesreader/nall/qt/file-dialog.moc.hpp b/snesreader/nall/qt/file-dialog.moc.hpp new file mode 100644 index 00000000..6528289b --- /dev/null +++ b/snesreader/nall/qt/file-dialog.moc.hpp @@ -0,0 +1,392 @@ +#ifndef NALL_QT_FILEDIALOG_HPP +#define NALL_QT_FILEDIALOG_HPP + +#include +#include +#include + +namespace nall { + +class FileDialog; + +class NewFolderDialog : public Window { + Q_OBJECT + +public: + void show(); + NewFolderDialog(FileDialog*); + +protected slots: + void createFolderAction(); + +protected: + FileDialog *parent; + QVBoxLayout *layout; + QLineEdit *folderNameEdit; + QHBoxLayout *controlLayout; + QPushButton *okButton; + QPushButton *cancelButton; +}; + +class FileView : public QListView { + Q_OBJECT + +protected: + void keyPressEvent(QKeyEvent*); + +signals: + void changed(const QModelIndex&); + void browseUp(); + +protected slots: + void currentChanged(const QModelIndex&, const QModelIndex&); +}; + +class FileDialog : public Window { + Q_OBJECT + +public: + void showLoad(); + void showSave(); + void showFolder(); + + void setPath(string path); + void setNameFilters(const string &filters); + FileDialog(); + +signals: + void changed(const string&); + void activated(const string&); + void accepted(const string&); + void rejected(); + +protected slots: + void fileViewChange(const QModelIndex&); + void fileViewActivate(const QModelIndex&); + void pathBoxChanged(); + void filterBoxChanged(); + void createNewFolder(); + void browseUp(); + void acceptAction(); + void rejectAction(); + +protected: + NewFolderDialog *newFolderDialog; + QVBoxLayout *layout; + QHBoxLayout *navigationLayout; + QComboBox *pathBox; + QPushButton *newFolderButton; + QPushButton *upFolderButton; + QHBoxLayout *browseLayout; + QFileSystemModel *fileSystemModel; + FileView *fileView; + QGroupBox *previewFrame; + QLineEdit *fileNameEdit; + QHBoxLayout *controlLayout; + QComboBox *filterBox; + QPushButton *optionsButton; + QPushButton *acceptButton; + QPushButton *rejectButton; + bool lock; + void createFolderAction(const string &name); + void closeEvent(QCloseEvent*); + + friend class NewFolderDialog; +}; + +inline void NewFolderDialog::show() { + folderNameEdit->setText(""); + Window::show(); + folderNameEdit->setFocus(); +} + +inline void NewFolderDialog::createFolderAction() { + string name = folderNameEdit->text().toUtf8().constData(); + if(name == "") { + folderNameEdit->setFocus(); + } else { + parent->createFolderAction(name); + close(); + } +} + +inline NewFolderDialog::NewFolderDialog(FileDialog *fileDialog) : parent(fileDialog) { + setMinimumWidth(240); + setWindowTitle("Create New Folder"); + + layout = new QVBoxLayout; + layout->setAlignment(Qt::AlignTop); + layout->setMargin(5); + layout->setSpacing(5); + setLayout(layout); + + folderNameEdit = new QLineEdit; + layout->addWidget(folderNameEdit); + + controlLayout = new QHBoxLayout; + controlLayout->setAlignment(Qt::AlignRight); + layout->addLayout(controlLayout); + + okButton = new QPushButton("Ok"); + controlLayout->addWidget(okButton); + + cancelButton = new QPushButton("Cancel"); + controlLayout->addWidget(cancelButton); + + connect(folderNameEdit, SIGNAL(returnPressed()), this, SLOT(createFolderAction())); + connect(okButton, SIGNAL(released()), this, SLOT(createFolderAction())); + connect(cancelButton, SIGNAL(released()), this, SLOT(close())); +} + +inline void FileView::currentChanged(const QModelIndex ¤t, const QModelIndex &previous) { + QAbstractItemView::currentChanged(current, previous); + emit changed(current); +} + +inline void FileView::keyPressEvent(QKeyEvent *event) { + //enhance consistency: force OS X to act like Windows and Linux; enter = activate item + if(event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter) { + emit activated(currentIndex()); + return; + } + + //simulate popular file manager behavior; backspace = go up one directory + if(event->key() == Qt::Key_Backspace) { + emit browseUp(); + return; + } + + //fallback: unrecognized keypresses get handled by the widget itself + QListView::keyPressEvent(event); +} + +inline void FileDialog::showLoad() { + acceptButton->setText("Load"); + fileNameEdit->hide(); + filterBox->show(); + show(); +} + +inline void FileDialog::showSave() { + acceptButton->setText("Save"); + fileNameEdit->show(); + filterBox->show(); + show(); +} + +inline void FileDialog::showFolder() { + acceptButton->setText("Choose"); + fileNameEdit->hide(); + filterBox->hide(); + setNameFilters("Folders ()"); + show(); +} + +inline void FileDialog::fileViewChange(const QModelIndex &index) { + string path = fileSystemModel->filePath(index).toUtf8().constData(); + if(path == fileSystemModel->rootPath().toUtf8().constData()) path = ""; + fileNameEdit->setText(notdir(path)); + emit changed(path); +} + +inline void FileDialog::fileViewActivate(const QModelIndex &index) { + string path = fileSystemModel->filePath(index).toUtf8().constData(); + if(fileSystemModel->isDir(index)) { + emit activated(path); + setPath(path); + } else { + emit activated(path); + close(); + } +} + +inline void FileDialog::pathBoxChanged() { + if(lock) return; + setPath(pathBox->currentText().toUtf8().constData()); +} + +inline void FileDialog::filterBoxChanged() { + if(lock) return; + string filters = filterBox->currentText().toUtf8().constData(); + if(filters.length() == 0) { + fileSystemModel->setNameFilters(QStringList() << "*"); + } else { + filters = substr(filters, strpos(filters, "(")()); + ltrim(filters, "("); + rtrim(filters, ")"); + lstring part; + part.split(" ", filters); + QStringList list; + for(unsigned i = 0; i < part.size(); i++) list << part[i]; + fileSystemModel->setNameFilters(list); + } +} + +inline void FileDialog::createNewFolder() { + newFolderDialog->show(); +} + +inline void FileDialog::browseUp() { + if(pathBox->count() > 1) pathBox->setCurrentIndex(1); +} + +inline void FileDialog::setPath(string path) { + lock = true; + newFolderDialog->close(); + + if(QDir(path).exists()) { + newFolderButton->setEnabled(true); + } else { + newFolderButton->setEnabled(false); + path = ""; + } + + fileSystemModel->setRootPath(path); + fileView->setRootIndex(fileSystemModel->index(path)); + fileView->setCurrentIndex(fileView->rootIndex()); + fileView->setFocus(); + + pathBox->clear(); + if(path.length() > 0) { + QDir directory(path); + while(true) { + pathBox->addItem(directory.absolutePath()); + if(directory.isRoot()) break; + directory.cdUp(); + } + } + pathBox->addItem(""); + fileNameEdit->setText(""); + + lock = false; +} + +inline void FileDialog::setNameFilters(const string &filters) { + lock = true; + + lstring list; + list.split("\n", filters); + + filterBox->clear(); + for(unsigned i = 0; i < list.size(); i++) { + filterBox->addItem(list[i]); + } + + lock = false; + filterBoxChanged(); +} + +inline void FileDialog::acceptAction() { + string path = fileSystemModel->rootPath().toUtf8().constData(); + path << "/" << notdir(fileNameEdit->text().toUtf8().constData()); + rtrim(path, "/"); + if(QDir(path).exists()) { + emit accepted(path); + setPath(path); + } else { + emit accepted(path); + close(); + } +} + +inline void FileDialog::rejectAction() { + emit rejected(); + close(); +} + +inline void FileDialog::createFolderAction(const string &name) { + string path = fileSystemModel->rootPath().toUtf8().constData(); + path << "/" << notdir(name); + mkdir(path, 0755); +} + +inline void FileDialog::closeEvent(QCloseEvent *event) { + newFolderDialog->close(); + Window::closeEvent(event); +} + +inline FileDialog::FileDialog() { + newFolderDialog = new NewFolderDialog(this); + resize(640, 360); + + layout = new QVBoxLayout; + layout->setMargin(5); + layout->setSpacing(5); + setLayout(layout); + + navigationLayout = new QHBoxLayout; + layout->addLayout(navigationLayout); + + pathBox = new QComboBox; + pathBox->setEditable(true); + pathBox->setMinimumContentsLength(16); + pathBox->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLength); + pathBox->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); + navigationLayout->addWidget(pathBox); + + newFolderButton = new QPushButton; + newFolderButton->setIconSize(QSize(16, 16)); + newFolderButton->setIcon(QIcon(":/16x16/folder-new.png")); + navigationLayout->addWidget(newFolderButton); + + upFolderButton = new QPushButton; + upFolderButton->setIconSize(QSize(16, 16)); + upFolderButton->setIcon(QIcon(":/16x16/go-up.png")); + navigationLayout->addWidget(upFolderButton); + + browseLayout = new QHBoxLayout; + layout->addLayout(browseLayout); + + fileSystemModel = new QFileSystemModel; + fileSystemModel->setFilter(QDir::AllDirs | QDir::Files | QDir::NoDotAndDotDot); + fileSystemModel->setNameFilterDisables(false); + + fileView = new FileView; + fileView->setMinimumWidth(320); + fileView->setModel(fileSystemModel); + fileView->setIconSize(QSize(16, 16)); + browseLayout->addWidget(fileView); + + previewFrame = new QGroupBox; + previewFrame->hide(); + browseLayout->addWidget(previewFrame); + + fileNameEdit = new QLineEdit; + layout->addWidget(fileNameEdit); + + controlLayout = new QHBoxLayout; + controlLayout->setAlignment(Qt::AlignRight); + layout->addLayout(controlLayout); + + filterBox = new QComboBox; + filterBox->setMinimumContentsLength(16); + filterBox->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLength); + filterBox->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); + controlLayout->addWidget(filterBox); + + optionsButton = new QPushButton("Options"); + optionsButton->hide(); + controlLayout->addWidget(optionsButton); + + acceptButton = new QPushButton("Ok"); + controlLayout->addWidget(acceptButton); + + rejectButton = new QPushButton("Cancel"); + controlLayout->addWidget(rejectButton); + + lock = false; + connect(pathBox, SIGNAL(currentIndexChanged(int)), this, SLOT(pathBoxChanged())); + connect(newFolderButton, SIGNAL(released()), this, SLOT(createNewFolder())); + connect(upFolderButton, SIGNAL(released()), this, SLOT(browseUp())); + connect(fileView, SIGNAL(changed(const QModelIndex&)), this, SLOT(fileViewChange(const QModelIndex&))); + connect(fileView, SIGNAL(activated(const QModelIndex&)), this, SLOT(fileViewActivate(const QModelIndex&))); + connect(fileView, SIGNAL(browseUp()), this, SLOT(browseUp())); + connect(fileNameEdit, SIGNAL(returnPressed()), this, SLOT(acceptAction())); + connect(filterBox, SIGNAL(currentIndexChanged(int)), this, SLOT(filterBoxChanged())); + connect(acceptButton, SIGNAL(released()), this, SLOT(acceptAction())); + connect(rejectButton, SIGNAL(released()), this, SLOT(rejectAction())); +} + +} + +#endif diff --git a/snesreader/nall/qt/hex-editor.moc.hpp b/snesreader/nall/qt/hex-editor.moc.hpp new file mode 100644 index 00000000..d59f4be9 --- /dev/null +++ b/snesreader/nall/qt/hex-editor.moc.hpp @@ -0,0 +1,173 @@ +#ifndef NALL_QT_HEXEDITOR_HPP +#define NALL_QT_HEXEDITOR_HPP + +#include +#include +#include + +namespace nall { + +class HexEditor : public QTextEdit { + Q_OBJECT + +public: + function reader; + function writer; + + void setColumns(unsigned columns); + void setRows(unsigned rows); + void setOffset(unsigned offset); + void setSize(unsigned size); + unsigned lineWidth() const; + void refresh(); + + HexEditor(); + +protected slots: + void scrolled(); + +protected: + QHBoxLayout *layout; + QScrollBar *scrollBar; + unsigned editorColumns; + unsigned editorRows; + unsigned editorOffset; + unsigned editorSize; + bool lock; + + void keyPressEvent(QKeyEvent*); +}; + +inline void HexEditor::keyPressEvent(QKeyEvent *event) { + QTextCursor cursor = textCursor(); + unsigned x = cursor.position() % lineWidth(); + unsigned y = cursor.position() / lineWidth(); + + int hexCode = -1; + switch(event->key()) { + case Qt::Key_0: hexCode = 0; break; + case Qt::Key_1: hexCode = 1; break; + case Qt::Key_2: hexCode = 2; break; + case Qt::Key_3: hexCode = 3; break; + case Qt::Key_4: hexCode = 4; break; + case Qt::Key_5: hexCode = 5; break; + case Qt::Key_6: hexCode = 6; break; + case Qt::Key_7: hexCode = 7; break; + case Qt::Key_8: hexCode = 8; break; + case Qt::Key_9: hexCode = 9; break; + case Qt::Key_A: hexCode = 10; break; + case Qt::Key_B: hexCode = 11; break; + case Qt::Key_C: hexCode = 12; break; + case Qt::Key_D: hexCode = 13; break; + case Qt::Key_E: hexCode = 14; break; + case Qt::Key_F: hexCode = 15; break; + } + + if(cursor.hasSelection() == false && hexCode != -1) { + bool cursorOffsetValid = (x >= 11 && ((x - 11) % 3) != 2); + if(cursorOffsetValid) { + bool nibble = (x - 11) % 3; //0 = top nibble, 1 = bottom nibble + unsigned cursorOffset = y * editorColumns + ((x - 11) / 3); + unsigned effectiveOffset = editorOffset + cursorOffset; + if(effectiveOffset >= editorSize) effectiveOffset %= editorSize; + + uint8_t data = reader ? reader(effectiveOffset) : 0x00; + data &= (nibble == 0 ? 0x0f : 0xf0); + data |= (nibble == 0 ? (hexCode << 4) : (hexCode << 0)); + if(writer) writer(effectiveOffset, data); + refresh(); + + cursor.setPosition(y * lineWidth() + x + 1); //advance cursor + setTextCursor(cursor); + } + } else { + //allow navigation keys to move cursor, but block text input + setTextInteractionFlags(Qt::TextSelectableByKeyboard | Qt::TextSelectableByMouse); + QTextEdit::keyPressEvent(event); + setTextInteractionFlags(Qt::TextEditorInteraction); + } +} + +inline void HexEditor::setColumns(unsigned columns) { + editorColumns = columns; +} + +inline void HexEditor::setRows(unsigned rows) { + editorRows = rows; + scrollBar->setPageStep(editorRows); +} + +inline void HexEditor::setOffset(unsigned offset) { + lock = true; + editorOffset = offset; + scrollBar->setSliderPosition(editorOffset / editorColumns); + lock = false; +} + +inline void HexEditor::setSize(unsigned size) { + editorSize = size; + bool indivisible = (editorSize % editorColumns) != 0; //add one for incomplete row + scrollBar->setRange(0, editorSize / editorColumns + indivisible - editorRows); +} + +inline unsigned HexEditor::lineWidth() const { + return 11 + 3 * editorColumns; +} + +inline void HexEditor::refresh() { + string output; + char temp[256]; + unsigned offset = editorOffset; + + for(unsigned y = 0; y < editorRows; y++) { + if(offset >= editorSize) break; + sprintf(temp, "%.4x:%.4x", (offset >> 16) & 0xffff, (offset >> 0) & 0xffff); + output << "" << temp << "  "; + + for(unsigned x = 0; x < editorColumns; x++) { + if(offset >= editorSize) break; + sprintf(temp, "%.2x", reader ? reader(offset) : 0x00); + offset++; + output << "" << temp << ""; + if(x != (editorColumns - 1)) output << " "; + } + + if(y != (editorRows - 1)) output << "
    "; + } + + setHtml(output); +} + +inline void HexEditor::scrolled() { + if(lock) return; + unsigned offset = scrollBar->sliderPosition(); + editorOffset = offset * editorColumns; + refresh(); +} + +inline HexEditor::HexEditor() { + setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + + layout = new QHBoxLayout; + layout->setAlignment(Qt::AlignRight); + layout->setMargin(0); + layout->setSpacing(0); + setLayout(layout); + + scrollBar = new QScrollBar(Qt::Vertical); + scrollBar->setSingleStep(1); + layout->addWidget(scrollBar); + + lock = false; + connect(scrollBar, SIGNAL(actionTriggered(int)), this, SLOT(scrolled())); + + setColumns(16); + setRows(16); + setSize(0); + setOffset(0); +} + +} + +#endif diff --git a/snesreader/nall/qt/radio-action.moc.hpp b/snesreader/nall/qt/radio-action.moc.hpp new file mode 100644 index 00000000..a2bbca48 --- /dev/null +++ b/snesreader/nall/qt/radio-action.moc.hpp @@ -0,0 +1,41 @@ +#ifndef NALL_QT_RADIOACTION_HPP +#define NALL_QT_RADIOACTION_HPP + +namespace nall { + +class RadioAction : public QAction { + Q_OBJECT + +public: + bool isChecked() const; + void setChecked(bool); + void toggleChecked(); + RadioAction(const QString&, QObject*); + +protected slots: + +protected: + bool checked; +}; + +inline bool RadioAction::isChecked() const { + return checked; +} + +inline void RadioAction::setChecked(bool checked_) { + checked = checked_; + if(checked) setIcon(QIcon(":/16x16/item-radio-on.png")); + else setIcon(QIcon(":/16x16/item-radio-off.png")); +} + +inline void RadioAction::toggleChecked() { + setChecked(!isChecked()); +} + +inline RadioAction::RadioAction(const QString &text, QObject *parent) : QAction(text, parent) { + setChecked(false); +} + +} + +#endif diff --git a/snesreader/nall/qt/window.moc.hpp b/snesreader/nall/qt/window.moc.hpp new file mode 100644 index 00000000..0d3bf390 --- /dev/null +++ b/snesreader/nall/qt/window.moc.hpp @@ -0,0 +1,105 @@ +#ifndef NALL_QT_WINDOW_HPP +#define NALL_QT_WINDOW_HPP + +#include +#include + +namespace nall { + +class Window : public QWidget { + Q_OBJECT + +public: + void setGeometryString(string *geometryString); + void setCloseOnEscape(bool); + void show(); + void hide(); + void shrink(); + + Window(); + +protected slots: + +protected: + string *geometryString; + bool closeOnEscape; + void keyReleaseEvent(QKeyEvent *event); + void closeEvent(QCloseEvent *event); +}; + +inline void Window::setGeometryString(string *geometryString_) { + geometryString = geometryString_; + if(geometryString && isVisible() == false) { + uint8_t *data; + unsigned length; + base64::decode(data, length, *geometryString); + QByteArray array((const char*)data, length); + delete[] data; + restoreGeometry(array); + } +} + +inline void Window::setCloseOnEscape(bool value) { + closeOnEscape = value; +} + +inline void Window::show() { + if(geometryString && isVisible() == false) { + uint8_t *data; + unsigned length; + base64::decode(data, length, *geometryString); + QByteArray array((const char*)data, length); + delete[] data; + restoreGeometry(array); + } + QWidget::show(); + QApplication::processEvents(); + activateWindow(); + raise(); +} + +inline void Window::hide() { + if(geometryString && isVisible() == true) { + char *data; + QByteArray geometry = saveGeometry(); + base64::encode(data, (const uint8_t*)geometry.data(), geometry.length()); + *geometryString = data; + delete[] data; + } + QWidget::hide(); +} + +inline void Window::shrink() { + if(isFullScreen()) return; + + for(unsigned i = 0; i < 2; i++) { + resize(0, 0); + usleep(2000); + QApplication::processEvents(); + } +} + +inline void Window::keyReleaseEvent(QKeyEvent *event) { + if(closeOnEscape && (event->key() == Qt::Key_Escape)) close(); + QWidget::keyReleaseEvent(event); +} + +inline void Window::closeEvent(QCloseEvent *event) { + if(geometryString) { + char *data; + QByteArray geometry = saveGeometry(); + base64::encode(data, (const uint8_t*)geometry.data(), geometry.length()); + *geometryString = data; + delete[] data; + } + QWidget::closeEvent(event); +} + +inline Window::Window() { + geometryString = 0; + closeOnEscape = true; +} + +} + +#endif diff --git a/snesreader/nall/serial.hpp b/snesreader/nall/serial.hpp new file mode 100644 index 00000000..6f5cf6d6 --- /dev/null +++ b/snesreader/nall/serial.hpp @@ -0,0 +1,80 @@ +#ifndef NALL_SERIAL_HPP +#define NALL_SERIAL_HPP + +#include +#include +#include +#include + +#include + +namespace nall { + class serial { + public: + //-1 on error, otherwise return bytes read + int read(uint8_t *data, unsigned length) { + if(port_open == false) return -1; + return ::read(port, (void*)data, length); + } + + //-1 on error, otherwise return bytes written + int write(const uint8_t *data, unsigned length) { + if(port_open == false) return -1; + return ::write(port, (void*)data, length); + } + + bool open(const char *portname, unsigned rate) { + close(); + + port = ::open(portname, O_RDWR | O_NOCTTY | O_NDELAY | O_NONBLOCK); + if(port == -1) return false; + + if(ioctl(port, TIOCEXCL) == -1) { close(); return false; } + if(fcntl(port, F_SETFL, 0) == -1) { close(); return false; } + if(tcgetattr(port, &original_attr) == -1) { close(); return false; } + + termios attr = original_attr; + cfmakeraw(&attr); + cfsetspeed(&attr, rate); + + attr.c_lflag &=~ (ECHO | ECHONL | ISIG | ICANON | IEXTEN); + attr.c_iflag &=~ (BRKINT | PARMRK | INPCK | ISTRIP | INLCR | IGNCR | ICRNL | IXON | IXOFF | IXANY); + attr.c_iflag |= (IGNBRK | IGNPAR); + attr.c_oflag &=~ (OPOST); + attr.c_cflag &=~ (CSIZE | CSTOPB | PARENB); + attr.c_cflag |= (CS8 | CREAD | CLOCAL); + attr.c_cc[VTIME] = attr.c_cc[VMIN] = 0; + + if(tcsetattr(port, TCSANOW, &attr) == -1) { close(); return false; } + return port_open = true; + } + + void close() { + if(port != -1) { + tcdrain(port); + if(port_open == true) { + tcsetattr(port, TCSANOW, &original_attr); + port_open = false; + } + ::close(port); + port = -1; + } + } + + serial() { + port = -1; + port_open = false; + } + + ~serial() { + close(); + } + + private: + int port; + bool port_open; + termios original_attr; + }; +} + +#endif diff --git a/snesreader/nall/serializer.hpp b/snesreader/nall/serializer.hpp new file mode 100644 index 00000000..9f816dfe --- /dev/null +++ b/snesreader/nall/serializer.hpp @@ -0,0 +1,145 @@ +#ifndef NALL_SERIALIZER_HPP +#define NALL_SERIALIZER_HPP + +#include +#include +#include +#include + +namespace nall { + //serializer: a class designed to save and restore the state of classes. + // + //benefits: + //- data() will be portable in size (it is not necessary to specify type sizes.) + //- data() will be portable in endianness (always stored internally as little-endian.) + //- one serialize function can both save and restore class states. + // + //caveats: + //- only plain-old-data can be stored. complex classes must provide serialize(serializer&); + //- floating-point usage is not portable across platforms + + class serializer { + public: + enum mode_t { Load, Save, Size }; + + mode_t mode() const { + return imode; + } + + const uint8_t* data() const { + return idata; + } + + unsigned size() const { + return isize; + } + + unsigned capacity() const { + return icapacity; + } + + template void floatingpoint(T &value) { + enum { size = sizeof(T) }; + //this is rather dangerous, and not cross-platform safe; + //but there is no standardized way to export FP-values + uint8_t *p = (uint8_t*)&value; + if(imode == Save) { + for(unsigned n = 0; n < size; n++) idata[isize++] = p[n]; + } else if(imode == Load) { + for(unsigned n = 0; n < size; n++) p[n] = idata[isize++]; + } else { + isize += size; + } + } + + template void integer(T &value) { + enum { size = std::is_same::value ? 1 : sizeof(T) }; + if(imode == Save) { + for(unsigned n = 0; n < size; n++) idata[isize++] = value >> (n << 3); + } else if(imode == Load) { + value = 0; + for(unsigned n = 0; n < size; n++) value |= idata[isize++] << (n << 3); + } else if(imode == Size) { + isize += size; + } + } + + template void array(T &array) { + enum { size = sizeof(T) / sizeof(typename std::remove_extent::type) }; + for(unsigned n = 0; n < size; n++) integer(array[n]); + } + + template void array(T array, unsigned size) { + for(unsigned n = 0; n < size; n++) integer(array[n]); + } + + //copy + serializer& operator=(const serializer &s) { + if(idata) delete[] idata; + + imode = s.imode; + idata = new uint8_t[s.icapacity]; + isize = s.isize; + icapacity = s.icapacity; + + memcpy(idata, s.idata, s.icapacity); + return *this; + } + + serializer(const serializer &s) : idata(0) { + operator=(s); + } + + //move + serializer& operator=(serializer &&s) { + if(idata) delete[] idata; + + imode = s.imode; + idata = s.idata; + isize = s.isize; + icapacity = s.icapacity; + + s.idata = 0; + return *this; + } + + serializer(serializer &&s) { + operator=(std::move(s)); + } + + //construction + serializer() { + imode = Size; + idata = 0; + isize = 0; + } + + serializer(unsigned capacity) { + imode = Save; + idata = new uint8_t[capacity](); + isize = 0; + icapacity = capacity; + } + + serializer(const uint8_t *data, unsigned capacity) { + imode = Load; + idata = new uint8_t[capacity]; + isize = 0; + icapacity = capacity; + memcpy(idata, data, capacity); + } + + ~serializer() { + if(idata) delete[] idata; + } + + private: + mode_t imode; + uint8_t *idata; + unsigned isize; + unsigned icapacity; + }; + +}; + +#endif diff --git a/snesreader/nall/sha256.hpp b/snesreader/nall/sha256.hpp new file mode 100644 index 00000000..7f41f04e --- /dev/null +++ b/snesreader/nall/sha256.hpp @@ -0,0 +1,143 @@ +#ifndef NALL_SHA256_HPP +#define NALL_SHA256_HPP + +//author: vladitx + +namespace nall { + #define PTR(t, a) ((t*)(a)) + + #define SWAP32(x) ((uint32_t)( \ + (((uint32_t)(x) & 0x000000ff) << 24) | \ + (((uint32_t)(x) & 0x0000ff00) << 8) | \ + (((uint32_t)(x) & 0x00ff0000) >> 8) | \ + (((uint32_t)(x) & 0xff000000) >> 24) \ + )) + + #define ST32(a, d) *PTR(uint32_t, a) = (d) + #define ST32BE(a, d) ST32(a, SWAP32(d)) + + #define LD32(a) *PTR(uint32_t, a) + #define LD32BE(a) SWAP32(LD32(a)) + + #define LSL32(x, n) ((uint32_t)(x) << (n)) + #define LSR32(x, n) ((uint32_t)(x) >> (n)) + #define ROR32(x, n) (LSR32(x, n) | LSL32(x, 32 - (n))) + + //first 32 bits of the fractional parts of the square roots of the first 8 primes 2..19 + static const uint32_t T_H[8] = { + 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19, + }; + + //first 32 bits of the fractional parts of the cube roots of the first 64 primes 2..311 + static const uint32_t T_K[64] = { + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, + 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, + 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, + 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, + 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, + 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2, + }; + + struct sha256_ctx { + uint8_t in[64]; + unsigned inlen; + + uint32_t w[64]; + uint32_t h[8]; + uint64_t len; + }; + + void sha256_init(sha256_ctx *p) { + memset(p, 0, sizeof(sha256_ctx)); + memcpy(p->h, T_H, sizeof(T_H)); + } + + static void sha256_block(sha256_ctx *p) { + unsigned i; + uint32_t s0, s1; + uint32_t a, b, c, d, e, f, g, h; + uint32_t t1, t2, maj, ch; + + for(i = 0; i < 16; i++) p->w[i] = LD32BE(p->in + i * 4); + + for(i = 16; i < 64; i++) { + s0 = ROR32(p->w[i - 15], 7) ^ ROR32(p->w[i - 15], 18) ^ LSR32(p->w[i - 15], 3); + s1 = ROR32(p->w[i - 2], 17) ^ ROR32(p->w[i - 2], 19) ^ LSR32(p->w[i - 2], 10); + p->w[i] = p->w[i - 16] + s0 + p->w[i - 7] + s1; + } + + a = p->h[0]; b = p->h[1]; c = p->h[2]; d = p->h[3]; + e = p->h[4]; f = p->h[5]; g = p->h[6]; h = p->h[7]; + + for(i = 0; i < 64; i++) { + s0 = ROR32(a, 2) ^ ROR32(a, 13) ^ ROR32(a, 22); + maj = (a & b) ^ (a & c) ^ (b & c); + t2 = s0 + maj; + s1 = ROR32(e, 6) ^ ROR32(e, 11) ^ ROR32(e, 25); + ch = (e & f) ^ (~e & g); + t1 = h + s1 + ch + T_K[i] + p->w[i]; + + h = g; g = f; f = e; e = d + t1; + d = c; c = b; b = a; a = t1 + t2; + } + + p->h[0] += a; p->h[1] += b; p->h[2] += c; p->h[3] += d; + p->h[4] += e; p->h[5] += f; p->h[6] += g; p->h[7] += h; + + //next block + p->inlen = 0; + } + + void sha256_chunk(sha256_ctx *p, const uint8_t *s, unsigned len) { + unsigned l; + p->len += len; + + while(len) { + l = 64 - p->inlen; + l = (len < l) ? len : l; + + memcpy(p->in + p->inlen, s, l); + s += l; + p->inlen += l; + len -= l; + + if(p->inlen == 64) sha256_block(p); + } + } + + void sha256_final(sha256_ctx *p) { + uint64_t len; + p->in[p->inlen++] = 0x80; + + if(p->inlen > 56) { + memset(p->in + p->inlen, 0, 64 - p->inlen); + sha256_block(p); + } + + memset(p->in + p->inlen, 0, 56 - p->inlen); + + len = p->len << 3; + ST32BE(p->in + 56, len >> 32); + ST32BE(p->in + 60, len); + sha256_block(p); + } + + void sha256_hash(sha256_ctx *p, uint8_t *s) { + uint32_t *t = (uint32_t*)s; + for(unsigned i = 0; i < 8; i++) ST32BE(t++, p->h[i]); + } + + #undef PTR + #undef SWAP32 + #undef ST32 + #undef ST32BE + #undef LD32 + #undef LD32BE + #undef LSL32 + #undef LSR32 + #undef ROR32 +} + +#endif diff --git a/snesreader/nall/sort.hpp b/snesreader/nall/sort.hpp new file mode 100644 index 00000000..23c317a5 --- /dev/null +++ b/snesreader/nall/sort.hpp @@ -0,0 +1,62 @@ +#ifndef NALL_SORT_HPP +#define NALL_SORT_HPP + +#include + +//class: merge sort +//average: O(n log n) +//worst: O(n log n) +//memory: O(n) +//stack: O(log n) +//stable?: yes + +//notes: +//there are two primary reasons for choosing merge sort +//over the (usually) faster quick sort*: +//1: it is a stable sort. +//2: it lacks O(n^2) worst-case overhead. +//(* which is also O(n log n) in the average case.) + +namespace nall { + template + void sort(T list[], unsigned length) { + if(length <= 1) return; //nothing to sort + + //use insertion sort to quickly sort smaller blocks + if(length < 64) { + for(unsigned i = 0; i < length; i++) { + unsigned min = i; + for(unsigned j = i + 1; j < length; j++) { + if(list[j] < list[min]) min = j; + } + if(min != i) swap(list[i], list[min]); + } + return; + } + + //split list in half and recursively sort both + unsigned middle = length / 2; + sort(list, middle); + sort(list + middle, length - middle); + + //left and right are sorted here; perform merge sort + T *buffer = new T[length]; + unsigned offset = 0; + unsigned left = 0; + unsigned right = middle; + while(left < middle && right < length) { + if(list[left] < list[right]) { + buffer[offset++] = list[left++]; + } else { + buffer[offset++] = list[right++]; + } + } + while(left < middle) buffer[offset++] = list[left++]; + while(right < length) buffer[offset++] = list[right++]; + + for(unsigned i = 0; i < length; i++) list[i] = buffer[i]; + delete[] buffer; + } +} + +#endif diff --git a/snesreader/nall/static.hpp b/snesreader/nall/static.hpp new file mode 100644 index 00000000..4acb9fd0 --- /dev/null +++ b/snesreader/nall/static.hpp @@ -0,0 +1,20 @@ +#ifndef NALL_STATIC_HPP +#define NALL_STATIC_HPP + +namespace nall { + template struct static_if { typedef T type; }; + template struct static_if { typedef F type; }; + template struct mp_static_if { typedef typename static_if::type type; }; + + template struct static_and { enum { value = false }; }; + template<> struct static_and { enum { value = true }; }; + template struct mp_static_and { enum { value = static_and::value }; }; + + template struct static_or { enum { value = false }; }; + template<> struct static_or { enum { value = true }; }; + template<> struct static_or { enum { value = true }; }; + template<> struct static_or { enum { value = true }; }; + template struct mp_static_or { enum { value = static_or::value }; }; +} + +#endif diff --git a/snesreader/nall/stdint.hpp b/snesreader/nall/stdint.hpp new file mode 100644 index 00000000..d8b6c788 --- /dev/null +++ b/snesreader/nall/stdint.hpp @@ -0,0 +1,44 @@ +#ifndef NALL_STDINT_HPP +#define NALL_STDINT_HPP + +#include + +#if defined(_MSC_VER) + typedef signed char int8_t; + typedef signed short int16_t; + typedef signed int int32_t; + typedef signed long long int64_t; + typedef int64_t intmax_t; + #if defined(_WIN64) + typedef int64_t intptr_t; + #else + typedef int32_t intptr_t; + #endif + + typedef unsigned char uint8_t; + typedef unsigned short uint16_t; + typedef unsigned int uint32_t; + typedef unsigned long long uint64_t; + typedef uint64_t uintmax_t; + #if defined(_WIN64) + typedef uint64_t uintptr_t; + #else + typedef uint32_t uintptr_t; + #endif +#else + #include +#endif + +namespace nall { + static_assert(sizeof(int8_t) == 1, "int8_t is not of the correct size" ); + static_assert(sizeof(int16_t) == 2, "int16_t is not of the correct size"); + static_assert(sizeof(int32_t) == 4, "int32_t is not of the correct size"); + static_assert(sizeof(int64_t) == 8, "int64_t is not of the correct size"); + + static_assert(sizeof(uint8_t) == 1, "int8_t is not of the correct size" ); + static_assert(sizeof(uint16_t) == 2, "int16_t is not of the correct size"); + static_assert(sizeof(uint32_t) == 4, "int32_t is not of the correct size"); + static_assert(sizeof(uint64_t) == 8, "int64_t is not of the correct size"); +} + +#endif diff --git a/snesreader/nall/string.hpp b/snesreader/nall/string.hpp new file mode 100644 index 00000000..3ff0392c --- /dev/null +++ b/snesreader/nall/string.hpp @@ -0,0 +1,27 @@ +#ifndef NALL_STRING_HPP +#define NALL_STRING_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace nall { + template<> struct has_length { enum { value = true }; }; + template<> struct has_size { enum { value = true }; }; +} + +#endif diff --git a/snesreader/nall/string/base.hpp b/snesreader/nall/string/base.hpp new file mode 100644 index 00000000..40d0e98c --- /dev/null +++ b/snesreader/nall/string/base.hpp @@ -0,0 +1,136 @@ +#ifndef NALL_STRING_BASE_HPP +#define NALL_STRING_BASE_HPP + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace nall { + class string; + template inline string to_string(T); + + class string { + public: + inline void reserve(unsigned); + inline unsigned length() const; + + inline string& assign(const char*); + inline string& append(const char*); + template inline string& operator= (T value); + template inline string& operator<<(T value); + + inline operator const char*() const; + inline char* operator()(); + inline char& operator[](int); + + inline bool operator==(const char*) const; + inline bool operator!=(const char*) const; + inline bool operator< (const char*) const; + inline bool operator<=(const char*) const; + inline bool operator> (const char*) const; + inline bool operator>=(const char*) const; + + inline string& operator=(const string&); + inline string& operator=(string&&); + + inline string(); + inline string(const char*); + inline string(const string&); + inline string(string&&); + inline ~string(); + + inline bool readfile(const char*); + inline string& replace (const char*, const char*); + inline string& qreplace(const char*, const char*); + + protected: + char *data; + unsigned size; + + #if defined(QT_CORE_LIB) + public: + inline operator QString() const; + #endif + }; + + class lstring : public linear_vector { + public: + template inline lstring& operator<<(T value); + + inline int find(const char*); + inline void split (const char*, const char*, unsigned = 0); + inline void qsplit(const char*, const char*, unsigned = 0); + + lstring(); + lstring(std::initializer_list); + }; + + //compare.hpp + inline char chrlower(char c); + inline char chrupper(char c); + inline int stricmp(const char *dest, const char *src); + inline bool strbegin (const char *str, const char *key); + inline bool stribegin(const char *str, const char *key); + inline bool strend (const char *str, const char *key); + inline bool striend(const char *str, const char *key); + + //convert.hpp + inline char* strlower(char *str); + inline char* strupper(char *str); + inline char* strtr(char *dest, const char *before, const char *after); + inline uintmax_t strhex (const char *str); + inline intmax_t strsigned (const char *str); + inline uintmax_t strunsigned(const char *str); + inline uintmax_t strbin (const char *str); + inline double strdouble (const char *str); + + //match.hpp + inline bool match(const char *pattern, const char *str); + + //math.hpp + inline bool strint (const char *str, int &result); + inline bool strmath(const char *str, int &result); + + //strl.hpp + inline unsigned strlcpy(char *dest, const char *src, unsigned length); + inline unsigned strlcat(char *dest, const char *src, unsigned length); + + //trim.hpp + inline char* ltrim(char *str, const char *key = " "); + inline char* rtrim(char *str, const char *key = " "); + inline char* trim (char *str, const char *key = " "); + inline char* ltrim_once(char *str, const char *key = " "); + inline char* rtrim_once(char *str, const char *key = " "); + inline char* trim_once (char *str, const char *key = " "); + + //utility.hpp + inline unsigned strlcpy(string &dest, const char *src, unsigned length); + inline unsigned strlcat(string &dest, const char *src, unsigned length); + inline string substr(const char *src, unsigned start = 0, unsigned length = 0); + inline string& strlower(string &str); + inline string& strupper(string &str); + inline string& strtr(string &dest, const char *before, const char *after); + inline string& ltrim(string &str, const char *key = " "); + inline string& rtrim(string &str, const char *key = " "); + inline string& trim (string &str, const char *key = " "); + inline string& ltrim_once(string &str, const char *key = " "); + inline string& rtrim_once(string &str, const char *key = " "); + inline string& trim_once (string &str, const char *key = " "); + template inline string strhex(uintmax_t value); + template inline string strsigned(intmax_t value); + template inline string strunsigned(uintmax_t value); + template inline string strbin(uintmax_t value); + inline unsigned strdouble(char *str, double value); + inline string strdouble(double value); + + //variadic.hpp + template inline string sprint(Args... args); + template inline void print(Args... args); +}; + +#endif diff --git a/snesreader/nall/string/cast.hpp b/snesreader/nall/string/cast.hpp new file mode 100644 index 00000000..7b48eda0 --- /dev/null +++ b/snesreader/nall/string/cast.hpp @@ -0,0 +1,32 @@ +#ifndef NALL_STRING_CAST_HPP +#define NALL_STRING_CAST_HPP + +namespace nall { + +//this is needed, as C++0x does not support explicit template specialization inside classes +template<> inline string to_string (bool v) { return v ? "true" : "false"; } +template<> inline string to_string (signed int v) { return strsigned(v); } +template<> inline string to_string (unsigned int v) { return strunsigned(v); } +template<> inline string to_string (double v) { return strdouble(v); } +template<> inline string to_string (char *v) { return v; } +template<> inline string to_string (const char *v) { return v; } +template<> inline string to_string (string v) { return v; } +template<> inline string to_string(const string &v) { return v; } + +template string& string::operator= (T value) { return assign(to_string(value)); } +template string& string::operator<<(T value) { return append(to_string(value)); } + +template lstring& lstring::operator<<(T value) { + operator[](size()).assign(to_string(value)); + return *this; +} + +#if defined(QT_CORE_LIB) +template<> inline string to_string(QString v) { return v.toUtf8().constData(); } +template<> inline string to_string(const QString &v) { return v.toUtf8().constData(); } +string::operator QString() const { return QString::fromUtf8(*this); } +#endif + +} + +#endif diff --git a/snesreader/nall/string/compare.hpp b/snesreader/nall/string/compare.hpp new file mode 100644 index 00000000..bd289753 --- /dev/null +++ b/snesreader/nall/string/compare.hpp @@ -0,0 +1,72 @@ +#ifndef NALL_STRING_COMPARE_HPP +#define NALL_STRING_COMPARE_HPP + +namespace nall { + +char chrlower(char c) { + return (c >= 'A' && c <= 'Z') ? c + ('a' - 'A') : c; +} + +char chrupper(char c) { + return (c >= 'a' && c <= 'z') ? c - ('a' - 'A') : c; +} + +int stricmp(const char *dest, const char *src) { + while(*dest) { + if(chrlower(*dest) != chrlower(*src)) break; + dest++; + src++; + } + + return (int)chrlower(*dest) - (int)chrlower(*src); +} + +bool strbegin(const char *str, const char *key) { + int i, ssl = strlen(str), ksl = strlen(key); + + if(ksl > ssl) return false; + return (!memcmp(str, key, ksl)); +} + +bool stribegin(const char *str, const char *key) { + int ssl = strlen(str), ksl = strlen(key); + + if(ksl > ssl) return false; + for(int i = 0; i < ksl; i++) { + if(str[i] >= 'A' && str[i] <= 'Z') { + if(str[i] != key[i] && str[i]+0x20 != key[i])return false; + } else if(str[i] >= 'a' && str[i] <= 'z') { + if(str[i] != key[i] && str[i]-0x20 != key[i])return false; + } else { + if(str[i] != key[i])return false; + } + } + return true; +} + +bool strend(const char *str, const char *key) { + int ssl = strlen(str), ksl = strlen(key); + + if(ksl > ssl) return false; + return (!memcmp(str + ssl - ksl, key, ksl)); +} + +bool striend(const char *str, const char *key) { + int ssl = strlen(str), ksl = strlen(key); + + if(ksl > ssl) return false; + for(int i = ssl - ksl, z = 0; i < ssl; i++, z++) { + if(str[i] >= 'A' && str[i] <= 'Z') { + if(str[i] != key[z] && str[i]+0x20 != key[z])return false; + } else if(str[i] >= 'a' && str[i] <= 'z') { + if(str[i] != key[z] && str[i]-0x20 != key[z])return false; + } else { + if(str[i] != key[z])return false; + } + } + return true; +} + +} + +#endif diff --git a/snesreader/nall/string/convert.hpp b/snesreader/nall/string/convert.hpp new file mode 100644 index 00000000..3ff134be --- /dev/null +++ b/snesreader/nall/string/convert.hpp @@ -0,0 +1,153 @@ +#ifndef NALL_STRING_CONVERT_HPP +#define NALL_STRING_CONVERT_HPP + +namespace nall { + +char* strlower(char *str) { + if(!str) return 0; + int i = 0; + while(str[i]) { + str[i] = chrlower(str[i]); + i++; + } + return str; +} + +char* strupper(char *str) { + if(!str) return 0; + int i = 0; + while(str[i]) { + str[i] = chrupper(str[i]); + i++; + } + return str; +} + +char* strtr(char *dest, const char *before, const char *after) { + if(!dest || !before || !after) return dest; + int sl = strlen(dest), bsl = strlen(before), asl = strlen(after); + + if(bsl != asl || bsl == 0) return dest; //patterns must be the same length for 1:1 replace + for(unsigned i = 0; i < sl; i++) { + for(unsigned l = 0; l < bsl; l++) { + if(dest[i] == before[l]) { + dest[i] = after[l]; + break; + } + } + } + + return dest; +} + +uintmax_t strhex(const char *str) { + if(!str) return 0; + uintmax_t result = 0; + + //skip hex identifiers 0x and $, if present + if(*str == '0' && (*(str + 1) == 'X' || *(str + 1) == 'x')) str += 2; + else if(*str == '$') str++; + + while(*str) { + uint8_t x = *str++; + if(x >= '0' && x <= '9') x -= '0'; + else if(x >= 'A' && x <= 'F') x -= 'A' - 10; + else if(x >= 'a' && x <= 'f') x -= 'a' - 10; + else break; //stop at first invalid character + result = result * 16 + x; + } + + return result; +} + +intmax_t strsigned(const char *str) { + if(!str) return 0; + intmax_t result = 0; + bool negate = false; + + //check for negation + if(*str == '-') { + negate = true; + str++; + } + + while(*str) { + uint8_t x = *str++; + if(x >= '0' && x <= '9') x -= '0'; + else break; //stop at first invalid character + result = result * 10 + x; + } + + return !negate ? result : -result; +} + +uintmax_t strunsigned(const char *str) { + if(!str) return 0; + uintmax_t result = 0; + + while(*str) { + uint8_t x = *str++; + if(x >= '0' && x <= '9') x -= '0'; + else break; //stop at first invalid character + result = result * 10 + x; + } + + return result; +} + +uintmax_t strbin(const char *str) { + if(!str) return 0; + uintmax_t result = 0; + + //skip bin identifiers 0b and %, if present + if(*str == '0' && (*(str + 1) == 'B' || *(str + 1) == 'b')) str += 2; + else if(*str == '%') str++; + + while(*str) { + uint8_t x = *str++; + if(x == '0' || x == '1') x -= '0'; + else break; //stop at first invalid character + result = result * 2 + x; + } + + return result; +} + +double strdouble(const char *str) { + if(!str) return 0.0; + bool negate = false; + + //check for negation + if(*str == '-') { + negate = true; + str++; + } + + intmax_t result_integral = 0; + while(*str) { + uint8_t x = *str++; + if(x >= '0' && x <= '9') x -= '0'; + else if(x == '.') break; //break loop and read fractional part + else return (double)result_integral; //invalid value, assume no fractional part + result_integral = result_integral * 10 + x; + } + + intmax_t result_fractional = 0; + while(*str) { + uint8_t x = *str++; + if(x >= '0' && x <= '9') x -= '0'; + else break; //stop at first invalid character + result_fractional = result_fractional * 10 + x; + } + + //calculate fractional portion + double result = (double)result_fractional; + while((uintmax_t)result > 0) result /= 10.0; + result += (double)result_integral; + + return !negate ? result : -result; +} + +} + +#endif diff --git a/snesreader/nall/string/core.hpp b/snesreader/nall/string/core.hpp new file mode 100644 index 00000000..f69802e4 --- /dev/null +++ b/snesreader/nall/string/core.hpp @@ -0,0 +1,133 @@ +#ifndef NALL_STRING_CORE_HPP +#define NALL_STRING_CORE_HPP + +namespace nall { + +void string::reserve(unsigned size_) { + if(size_ > size) { + size = size_; + data = (char*)realloc(data, size + 1); + data[size] = 0; + } +} + +unsigned string::length() const { + return strlen(data); +} + +string& string::assign(const char *s) { + unsigned length = strlen(s); + reserve(length); + strcpy(data, s); + return *this; +} + +string& string::append(const char *s) { + unsigned length = strlen(data) + strlen(s); + reserve(length); + strcat(data, s); + return *this; +} + +string::operator const char*() const { + return data; +} + +char* string::operator()() { + return data; +} + +char& string::operator[](int index) { + reserve(index); + return data[index]; +} + +bool string::operator==(const char *str) const { return strcmp(data, str) == 0; } +bool string::operator!=(const char *str) const { return strcmp(data, str) != 0; } +bool string::operator< (const char *str) const { return strcmp(data, str) < 0; } +bool string::operator<=(const char *str) const { return strcmp(data, str) <= 0; } +bool string::operator> (const char *str) const { return strcmp(data, str) > 0; } +bool string::operator>=(const char *str) const { return strcmp(data, str) >= 0; } + +string& string::operator=(const string &value) { + assign(value); + return *this; +} + +string& string::operator=(string &&source) { + if(data) free(data); + size = source.size; + data = source.data; + source.data = 0; + source.size = 0; + return *this; +} + +string::string() { + size = 64; + data = (char*)malloc(size + 1); + *data = 0; +} + +string::string(const char *value) { + size = strlen(value); + data = strdup(value); +} + +string::string(const string &value) { + size = strlen(value); + data = strdup(value); +} + +string::string(string &&source) { + size = source.size; + data = source.data; + source.data = 0; +} + +string::~string() { + free(data); +} + +bool string::readfile(const char *filename) { + assign(""); + + #if !defined(_WIN32) + FILE *fp = fopen(filename, "rb"); + #else + FILE *fp = _wfopen(utf16_t(filename), L"rb"); + #endif + if(!fp) return false; + + fseek(fp, 0, SEEK_END); + unsigned size = ftell(fp); + rewind(fp); + char *fdata = new char[size + 1]; + unsigned unused = fread(fdata, 1, size, fp); + fclose(fp); + fdata[size] = 0; + assign(fdata); + delete[] fdata; + + return true; +} + +int lstring::find(const char *key) { + for(unsigned i = 0; i < size(); i++) { + if(operator[](i) == key) return i; + } + return -1; +} + +inline lstring::lstring() { +} + +inline lstring::lstring(std::initializer_list list) { + for(const string *s = list.begin(); s != list.end(); ++s) { + operator<<(*s); + } +} + +} + +#endif diff --git a/snesreader/nall/string/filename.hpp b/snesreader/nall/string/filename.hpp new file mode 100644 index 00000000..f3750760 --- /dev/null +++ b/snesreader/nall/string/filename.hpp @@ -0,0 +1,61 @@ +#ifndef NALL_FILENAME_HPP +#define NALL_FILENAME_HPP + +namespace nall { + +// "foo/bar.c" -> "foo/", "bar.c" -> "./" +inline string dir(char const *name) { + string result = name; + for(signed i = strlen(result); i >= 0; i--) { + if(result[i] == '/' || result[i] == '\\') { + result[i + 1] = 0; + break; + } + if(i == 0) result = "./"; + } + return result; +} + +// "foo/bar.c" -> "bar.c" +inline string notdir(char const *name) { + for(signed i = strlen(name); i >= 0; i--) { + if(name[i] == '/' || name[i] == '\\') { + name += i + 1; + break; + } + } + string result = name; + return result; +} + +// "foo/bar.c" -> "foo/bar" +inline string basename(char const *name) { + string result = name; + for(signed i = strlen(result); i >= 0; i--) { + if(result[i] == '/' || result[i] == '\\') { + //file has no extension + break; + } + if(result[i] == '.') { + result[i] = 0; + break; + } + } + return result; +} + +// "foo/bar.c" -> "c" +inline string extension(char const *name) { + for(signed i = strlen(name); i >= 0; i--) { + if(name[i] == '.') { + name += i + 1; + break; + } + } + string result = name; + return result; +} + +} + +#endif diff --git a/snesreader/nall/string/match.hpp b/snesreader/nall/string/match.hpp new file mode 100644 index 00000000..d8cf702d --- /dev/null +++ b/snesreader/nall/string/match.hpp @@ -0,0 +1,76 @@ +#ifndef NALL_STRING_MATCH_HPP +#define NALL_STRING_MATCH_HPP + +namespace nall { + +bool match(const char *p, const char *s) { + const char *p_ = 0, *s_ = 0; + + for(;;) { + if(!*s) { + while(*p == '*') p++; + return !*p; + } + + //wildcard match + if(*p == '*') { + p_ = p++, s_ = s; + continue; + } + + //any match + if(*p == '?') { + p++, s++; + continue; + } + + //ranged match + if(*p == '{') { + #define pattern(name_, rule_) \ + if(strbegin(p, name_)) { \ + if(rule_) { \ + p += sizeof(name_) - 1, s++; \ + continue; \ + } \ + goto failure; \ + } + + pattern("{alpha}", (*s >= 'A' && *s <= 'Z') || (*s >= 'a' && *s <= 'z')) + pattern("{alphanumeric}", (*s >= 'A' && *s <= 'Z') || (*s >= 'a' && *s <= 'z') || (*s >= '0' && *s <= '9')) + pattern("{binary}", (*s == '0' || *s == '1')) + pattern("{hex}", (*s >= '0' && *s <= '9') || (*s >= 'A' && *s <= 'F') || (*s >= 'a' && *s <= 'f')) + pattern("{lowercase}", (*s >= 'a' && *s <= 'z')) + pattern("{numeric}", (*s >= '0' && *s <= '9')) + pattern("{uppercase}", (*s >= 'A' && *s <= 'Z')) + pattern("{whitespace}", (*s == ' ' || *s == '\t')) + + #undef pattern + goto failure; + } + + //reserved character match + if(*p == '\\') { + p++; + //fallthrough + } + + //literal match + if(*p == *s) { + p++, *s++; + continue; + } + + //attempt wildcard rematch + failure: + if(p_) { + p = p_, s = s_ + 1; + continue; + } + + return false; + } +} + +} + +#endif diff --git a/snesreader/nall/string/math.hpp b/snesreader/nall/string/math.hpp new file mode 100644 index 00000000..ea8b99c8 --- /dev/null +++ b/snesreader/nall/string/math.hpp @@ -0,0 +1,164 @@ +#ifndef NALL_STRING_MATH_HPP +#define NALL_STRING_MATH_HPP + +namespace nall { + +static int eval_integer(const char *&s) { + if(!*s) throw "unrecognized_integer"; + int value = 0, x = *s, y = *(s + 1); + + //hexadecimal + if(x == '0' && (y == 'X' || y == 'x')) { + s += 2; + while(true) { + if(*s >= '0' && *s <= '9') { value = value * 16 + (*s++ - '0'); continue; } + if(*s >= 'A' && *s <= 'F') { value = value * 16 + (*s++ - 'A' + 10); continue; } + if(*s >= 'a' && *s <= 'f') { value = value * 16 + (*s++ - 'a' + 10); continue; } + return value; + } + } + + //binary + if(x == '0' && (y == 'B' || y == 'b')) { + s += 2; + while(true) { + if(*s == '0' || *s == '1') { value = value * 2 + (*s++ - '0'); continue; } + return value; + } + } + + //octal (or decimal '0') + if(x == '0') { + s += 1; + while(true) { + if(*s >= '0' && *s <= '7') { value = value * 8 + (*s++ - '0'); continue; } + return value; + } + } + + //decimal + if(x >= '0' && x <= '9') { + while(true) { + if(*s >= '0' && *s <= '9') { value = value * 10 + (*s++ - '0'); continue; } + return value; + } + } + + //char + if(x == '\'' && y != '\'') { + s += 1; + while(true) { + value = value * 256 + *s++; + if(*s == '\'') { s += 1; return value; } + if(!*s) throw "mismatched_char"; + } + } + + throw "unrecognized_integer"; +} + +static int eval(const char *&s, int depth = 0) { + while(*s == ' ' || *s == '\t') s++; //trim whitespace + if(!*s) throw "unrecognized_token"; + int value = 0, x = *s, y = *(s + 1); + + if(*s == '(') { + value = eval(++s, 1); + if(*s++ != ')') throw "mismatched_group"; + } + + else if(x == '!') value = !eval(++s, 13); + else if(x == '~') value = ~eval(++s, 13); + else if(x == '+') value = +eval(++s, 13); + else if(x == '-') value = -eval(++s, 13); + + else if((x >= '0' && x <= '9') || x == '\'') value = eval_integer(s); + + else throw "unrecognized_token"; + + while(true) { + while(*s == ' ' || *s == '\t') s++; //trim whitespace + if(!*s) break; + x = *s, y = *(s + 1); + + if(depth >= 13) break; + if(x == '*') { value *= eval(++s, 13); continue; } + if(x == '/') { value /= eval(++s, 13); continue; } + if(x == '%') { value %= eval(++s, 13); continue; } + + if(depth >= 12) break; + if(x == '+') { value += eval(++s, 12); continue; } + if(x == '-') { value -= eval(++s, 12); continue; } + + if(depth >= 11) break; + if(x == '<' && y == '<') { value <<= eval(++++s, 11); continue; } + if(x == '>' && y == '>') { value >>= eval(++++s, 11); continue; } + + if(depth >= 10) break; + if(x == '<' && y == '=') { value = value <= eval(++++s, 10); continue; } + if(x == '>' && y == '=') { value = value >= eval(++++s, 10); continue; } + if(x == '<') { value = value < eval(++s, 10); continue; } + if(x == '>') { value = value > eval(++s, 10); continue; } + + if(depth >= 9) break; + if(x == '=' && y == '=') { value = value == eval(++++s, 9); continue; } + if(x == '!' && y == '=') { value = value != eval(++++s, 9); continue; } + + if(depth >= 8) break; + if(x == '&' && y != '&') { value = value & eval(++s, 8); continue; } + + if(depth >= 7) break; + if(x == '^' && y != '^') { value = value ^ eval(++s, 7); continue; } + + if(depth >= 6) break; + if(x == '|' && y != '|') { value = value | eval(++s, 6); continue; } + + if(depth >= 5) break; + if(x == '&' && y == '&') { value = eval(++++s, 5) && value; continue; } + + if(depth >= 4) break; + if(x == '^' && y == '^') { value = (!eval(++++s, 4) != !value); continue; } + + if(depth >= 3) break; + if(x == '|' && y == '|') { value = eval(++++s, 3) || value; continue; } + + if(x == '?') { + int lhs = eval(++s, 2); + if(*s != ':') throw "mismatched_ternary"; + int rhs = eval(++s, 2); + value = value ? lhs : rhs; + continue; + } + if(depth >= 2) break; + + if(depth > 0 && x == ')') break; + + throw "unrecognized_token"; + } + + return value; +} + +bool strint(const char *s, int &result) { + try { + result = eval_integer(s); + return true; + } catch(const char*) { + result = 0; + return false; + } +} + +bool strmath(const char *s, int &result) { + try { + result = eval(s); + return true; + } catch(const char*) { + result = 0; + return false; + } +} + +} + +#endif diff --git a/snesreader/nall/string/replace.hpp b/snesreader/nall/string/replace.hpp new file mode 100644 index 00000000..db405a9b --- /dev/null +++ b/snesreader/nall/string/replace.hpp @@ -0,0 +1,103 @@ +#ifndef NALL_STRING_REPLACE_HPP +#define NALL_STRING_REPLACE_HPP + +namespace nall { + +string& string::replace(const char *key, const char *token) { + int i, z, ksl = strlen(key), tsl = strlen(token), ssl = length(); + unsigned int replace_count = 0, size = ssl; + char *buffer; + + if(ksl <= ssl) { + if(tsl > ksl) { //the new string may be longer than the old string... + for(i = 0; i <= ssl - ksl;) { //so let's find out how big of a string we'll need... + if(!memcmp(data + i, key, ksl)) { + replace_count++; + i += ksl; + } else i++; + } + size = ssl + ((tsl - ksl) * replace_count); + reserve(size); + } + + buffer = new char[size + 1]; + for(i = z = 0; i < ssl;) { + if(i <= ssl - ksl) { + if(!memcmp(data + i, key, ksl)) { + memcpy(buffer + z, token, tsl); + z += tsl; + i += ksl; + } else buffer[z++] = data[i++]; + } else buffer[z++] = data[i++]; + } + buffer[z] = 0; + + assign(buffer); + delete[] buffer; + } + + return *this; +} + +string& string::qreplace(const char *key, const char *token) { + int i, l, z, ksl = strlen(key), tsl = strlen(token), ssl = length(); + unsigned int replace_count = 0, size = ssl; + uint8_t x; + char *buffer; + + if(ksl <= ssl) { + if(tsl > ksl) { + for(i = 0; i <= ssl - ksl;) { + x = data[i]; + if(x == '\"' || x == '\'') { + l = i; + i++; + while(data[i++] != x) { + if(i == ssl) { + i = l; + break; + } + } + } + if(!memcmp(data + i, key, ksl)) { + replace_count++; + i += ksl; + } else i++; + } + size = ssl + ((tsl - ksl) * replace_count); + reserve(size); + } + + buffer = new char[size + 1]; + for(i = z = 0; i < ssl;) { + x = data[i]; + if(x == '\"' || x == '\'') { + l = i++; + while(data[i] != x && i < ssl)i++; + if(i >= ssl)i = l; + else { + memcpy(buffer + z, data + l, i - l); + z += i - l; + } + } + if(i <= ssl - ksl) { + if(!memcmp(data + i, key, ksl)) { + memcpy(buffer + z, token, tsl); + z += tsl; + i += ksl; + replace_count++; + } else buffer[z++] = data[i++]; + } else buffer[z++] = data[i++]; + } + buffer[z] = 0; + + assign(buffer); + delete[] buffer; + } + + return *this; +} + +}; + +#endif diff --git a/snesreader/nall/string/split.hpp b/snesreader/nall/string/split.hpp new file mode 100644 index 00000000..bb77dfcd --- /dev/null +++ b/snesreader/nall/string/split.hpp @@ -0,0 +1,56 @@ +#ifndef NALL_STRING_SPLIT_HPP +#define NALL_STRING_SPLIT_HPP + +namespace nall { + +void lstring::split(const char *key, const char *src, unsigned limit) { + reset(); + + int ssl = strlen(src), ksl = strlen(key); + int lp = 0, split_count = 0; + + for(int i = 0; i <= ssl - ksl;) { + if(!memcmp(src + i, key, ksl)) { + strlcpy(operator[](split_count++), src + lp, i - lp + 1); + i += ksl; + lp = i; + if(!--limit) break; + } else i++; + } + + operator[](split_count++) = src + lp; +} + +void lstring::qsplit(const char *key, const char *src, unsigned limit) { + reset(); + + int ssl = strlen(src), ksl = strlen(key); + int lp = 0, split_count = 0; + + for(int i = 0; i <= ssl - ksl;) { + uint8_t x = src[i]; + + if(x == '\"' || x == '\'') { + int z = i++; //skip opening quote + while(i < ssl && src[i] != x) i++; + if(i >= ssl) i = z; //failed match, rewind i + else { + i++; //skip closing quote + continue; //restart in case next char is also a quote + } + } + + if(!memcmp(src + i, key, ksl)) { + strlcpy(operator[](split_count++), src + lp, i - lp + 1); + i += ksl; + lp = i; + if(!--limit) break; + } else i++; + } + + operator[](split_count++) = src + lp; +} + +}; + +#endif diff --git a/snesreader/nall/string/strl.hpp b/snesreader/nall/string/strl.hpp new file mode 100644 index 00000000..84c841fa --- /dev/null +++ b/snesreader/nall/string/strl.hpp @@ -0,0 +1,52 @@ +#ifndef NALL_STRING_STRL_HPP +#define NALL_STRING_STRL_HPP + +namespace nall { + +//strlcpy, strlcat based on OpenBSD implementation by Todd C. Miller + +//return = strlen(src) +unsigned strlcpy(char *dest, const char *src, unsigned length) { + char *d = dest; + const char *s = src; + unsigned n = length; + + if(n) { + while(--n && (*d++ = *s++)); //copy as many bytes as possible, or until null terminator reached + } + + if(!n) { + if(length) *d = 0; + while(*s++); //traverse rest of s, so that s - src == strlen(src) + } + + return (s - src - 1); //return length of copied string, sans null terminator +} + +//return = strlen(src) + min(length, strlen(dest)) +unsigned strlcat(char *dest, const char *src, unsigned length) { + char *d = dest; + const char *s = src; + unsigned n = length; + + while(n-- && *d) d++; //find end of dest + unsigned dlength = d - dest; + n = length - dlength; //subtract length of dest from maximum string length + + if(!n) return dlength + strlen(s); + + while(*s) { + if(n != 1) { + *d++ = *s; + n--; + } + s++; + } + *d = 0; + + return dlength + (s - src); //return length of resulting string, sans null terminator +} + +} + +#endif diff --git a/snesreader/nall/string/strpos.hpp b/snesreader/nall/string/strpos.hpp new file mode 100644 index 00000000..a1bf85b4 --- /dev/null +++ b/snesreader/nall/string/strpos.hpp @@ -0,0 +1,60 @@ +#ifndef NALL_STRING_STRPOS_HPP +#define NALL_STRING_STRPOS_HPP + +//usage example: +//if(auto pos = strpos(str, key)) print(pos(), "\n"); +//prints position of key within str, only if it is found + +namespace nall { + +class strpos { + bool found; + unsigned position; +public: + inline operator bool() const { return found; } + inline unsigned operator()() const { return position; } + inline strpos(const char *str, const char *key) : found(false), position(0) { + unsigned ssl = strlen(str), ksl = strlen(key); + if(ksl > ssl) return; + + for(unsigned i = 0; i <= ssl - ksl; i++) { + if(!memcmp(str + i, key, ksl)) { + found = true; + position = i; + return; + } + } + } +}; + +class qstrpos { + bool found; + unsigned position; +public: + inline operator bool() const { return found; } + inline unsigned operator()() const { return position; } + inline qstrpos(const char *str, const char *key) : found(false), position(0) { + unsigned ssl = strlen(str), ksl = strlen(key); + if(ksl > ssl) return; + + for(unsigned i = 0; i <= ssl - ksl;) { + uint8_t x = str[i]; + if(x == '\"' || x == '\'') { + uint8_t z = i++; + while(str[i] != x && i < ssl) i++; + if(i >= ssl) i = z; + } + if(!memcmp(str + i, key, ksl)) { + found = true; + position = i; + return; + } else { + i++; + } + } + } +}; + +} + +#endif diff --git a/snesreader/nall/string/trim.hpp b/snesreader/nall/string/trim.hpp new file mode 100644 index 00000000..b13ab9ba --- /dev/null +++ b/snesreader/nall/string/trim.hpp @@ -0,0 +1,54 @@ +#ifndef NALL_STRING_TRIM_HPP +#define NALL_STRING_TRIM_HPP + +namespace nall { + +char* ltrim(char *str, const char *key) { + if(!key || !*key) return str; + while(strbegin(str, key)) { + char *dest = str, *src = str + strlen(key); + while(true) { + *dest = *src++; + if(!*dest) break; + dest++; + } + } + return str; +} + +char* rtrim(char *str, const char *key) { + if(!key || !*key) return str; + while(strend(str, key)) str[strlen(str) - strlen(key)] = 0; + return str; +} + +char* trim(char *str, const char *key) { + return ltrim(rtrim(str, key), key); +} + +char* ltrim_once(char *str, const char *key) { + if(!key || !*key) return str; + if(strbegin(str, key)) { + char *dest = str, *src = str + strlen(key); + while(true) { + *dest = *src++; + if(!*dest) break; + dest++; + } + } + return str; +} + +char* rtrim_once(char *str, const char *key) { + if(!key || !*key) return str; + if(strend(str, key)) str[strlen(str) - strlen(key)] = 0; + return str; +} + +char* trim_once(char *str, const char *key) { + return ltrim_once(rtrim_once(str, key), key); +} + +} + +#endif diff --git a/snesreader/nall/string/utility.hpp b/snesreader/nall/string/utility.hpp new file mode 100644 index 00000000..2da2762b --- /dev/null +++ b/snesreader/nall/string/utility.hpp @@ -0,0 +1,169 @@ +#ifndef NALL_STRING_UTILITY_HPP +#define NALL_STRING_UTILITY_HPP + +namespace nall { + +unsigned strlcpy(string &dest, const char *src, unsigned length) { + dest.reserve(length); + return strlcpy(dest(), src, length); +} + +unsigned strlcat(string &dest, const char *src, unsigned length) { + dest.reserve(length); + return strlcat(dest(), src, length); +} + +string substr(const char *src, unsigned start, unsigned length) { + string dest; + if(length == 0) { + //copy entire string + dest = src + start; + } else { + //copy partial string + strlcpy(dest, src + start, length + 1); + } + return dest; +} + +/* very simplistic wrappers to return string& instead of char* type */ + +string& strlower(string &str) { strlower(str()); return str; } +string& strupper(string &str) { strupper(str()); return str; } +string& strtr(string &dest, const char *before, const char *after) { strtr(dest(), before, after); return dest; } +string& ltrim(string &str, const char *key) { ltrim(str(), key); return str; } +string& rtrim(string &str, const char *key) { rtrim(str(), key); return str; } +string& trim (string &str, const char *key) { trim (str(), key); return str; } +string& ltrim_once(string &str, const char *key) { ltrim_once(str(), key); return str; } +string& rtrim_once(string &str, const char *key) { rtrim_once(str(), key); return str; } +string& trim_once (string &str, const char *key) { trim_once (str(), key); return str; } + +/* arithmetic <> string */ + +template string strhex(uintmax_t value) { + string output; + unsigned offset = 0; + + //render string backwards, as we do not know its length yet + do { + unsigned n = value & 15; + output[offset++] = n < 10 ? '0' + n : 'a' + n - 10; + value >>= 4; + } while(value); + + while(offset < length) output[offset++] = padding; + output[offset--] = 0; + + //reverse the string in-place + for(unsigned i = 0; i < (offset + 1) >> 1; i++) { + char temp = output[i]; + output[i] = output[offset - i]; + output[offset - i] = temp; + } + + return output; +} + +template string strsigned(intmax_t value) { + string output; + unsigned offset = 0; + + bool negative = value < 0; + if(negative) value = abs(value); + + do { + unsigned n = value % 10; + output[offset++] = '0' + n; + value /= 10; + } while(value); + + while(offset < length) output[offset++] = padding; + if(negative) output[offset++] = '-'; + output[offset--] = 0; + + for(unsigned i = 0; i < (offset + 1) >> 1; i++) { + char temp = output[i]; + output[i] = output[offset - i]; + output[offset - i] = temp; + } + + return output; +} + +template string strunsigned(uintmax_t value) { + string output; + unsigned offset = 0; + + do { + unsigned n = value % 10; + output[offset++] = '0' + n; + value /= 10; + } while(value); + + while(offset < length) output[offset++] = padding; + output[offset--] = 0; + + for(unsigned i = 0; i < (offset + 1) >> 1; i++) { + char temp = output[i]; + output[i] = output[offset - i]; + output[offset - i] = temp; + } + + return output; +} + +template string strbin(uintmax_t value) { + string output; + unsigned offset = 0; + + do { + unsigned n = value & 1; + output[offset++] = '0' + n; + value >>= 1; + } while(value); + + while(offset < length) output[offset++] = padding; + output[offset--] = 0; + + for(unsigned i = 0; i < (offset + 1) >> 1; i++) { + char temp = output[i]; + output[i] = output[offset - i]; + output[offset - i] = temp; + } + + return output; +} + +//using sprintf is certainly not the most ideal method to convert +//a double to a string ... but attempting to parse a double by +//hand, digit-by-digit, results in subtle rounding errors. +unsigned strdouble(char *str, double value) { + char buffer[256]; + sprintf(buffer, "%f", value); + + //remove excess 0's in fraction (2.500000 -> 2.5) + for(char *p = buffer; *p; p++) { + if(*p == '.') { + char *p = buffer + strlen(buffer) - 1; + while(*p == '0') { + if(*(p - 1) != '.') *p = 0; //... but not for eg 1.0 -> 1. + p--; + } + break; + } + } + + unsigned length = strlen(buffer); + if(str) strcpy(str, buffer); + return length + 1; +} + +string strdouble(double value) { + string temp; + temp.reserve(strdouble(0, value)); + strdouble(temp(), value); + return temp; +} + +} + +#endif diff --git a/snesreader/nall/string/variadic.hpp b/snesreader/nall/string/variadic.hpp new file mode 100644 index 00000000..13c477a8 --- /dev/null +++ b/snesreader/nall/string/variadic.hpp @@ -0,0 +1,27 @@ +#ifndef NALL_STRING_VARIADIC_HPP +#define NALL_STRING_VARIADIC_HPP + +namespace nall { + +static void isprint(string &output) { +} + +template +static void isprint(string &output, T value, Args... args) { + output << to_string(value); + isprint(output, args...); +} + +template inline string sprint(Args... args) { + string output; + isprint(output, args...); + return output; +} + +template inline void print(Args... args) { + printf("%s", (const char*)sprint(args...)); +} + +} + +#endif diff --git a/snesreader/nall/string/xml.hpp b/snesreader/nall/string/xml.hpp new file mode 100644 index 00000000..218b4cbf --- /dev/null +++ b/snesreader/nall/string/xml.hpp @@ -0,0 +1,265 @@ +#ifndef NALL_STRING_XML_HPP +#define NALL_STRING_XML_HPP + +//XML subset parser +//version 0.05 + +namespace nall { + +struct xml_attribute { + string name; + string content; + virtual string parse() const; +}; + +struct xml_element : xml_attribute { + string parse() const; + linear_vector attribute; + linear_vector element; + +protected: + void parse_doctype(const char *&data); + bool parse_head(string data); + bool parse_body(const char *&data); + friend xml_element xml_parse(const char *data); +}; + +inline string xml_attribute::parse() const { + string data; + unsigned offset = 0; + + const char *source = content; + while(*source) { + if(*source == '&') { + if(strbegin(source, "<")) { data[offset++] = '<'; source += 4; continue; } + if(strbegin(source, ">")) { data[offset++] = '>'; source += 4; continue; } + if(strbegin(source, "&")) { data[offset++] = '&'; source += 5; continue; } + if(strbegin(source, "'")) { data[offset++] = '\''; source += 6; continue; } + if(strbegin(source, """)) { data[offset++] = '"'; source += 6; continue; } + } + + //reject illegal characters + if(*source == '&') return ""; + if(*source == '<') return ""; + if(*source == '>') return ""; + + data[offset++] = *source++; + } + + data[offset] = 0; + return data; +} + +inline string xml_element::parse() const { + string data; + unsigned offset = 0; + + const char *source = content; + while(*source) { + if(*source == '&') { + if(strbegin(source, "<")) { data[offset++] = '<'; source += 4; continue; } + if(strbegin(source, ">")) { data[offset++] = '>'; source += 4; continue; } + if(strbegin(source, "&")) { data[offset++] = '&'; source += 5; continue; } + if(strbegin(source, "'")) { data[offset++] = '\''; source += 6; continue; } + if(strbegin(source, """)) { data[offset++] = '"'; source += 6; continue; } + } + + if(strbegin(source, "")) { + source += pos() + 3; + continue; + } else { + return ""; + } + } + + if(strbegin(source, "")) { + string cdata = substr(source, 9, pos() - 9); + data << cdata; + offset += strlen(cdata); + + source += offset + 3; + continue; + } else { + return ""; + } + } + + //reject illegal characters + if(*source == '&') return ""; + if(*source == '<') return ""; + if(*source == '>') return ""; + + data[offset++] = *source++; + } + + data[offset] = 0; + return data; +} + +inline void xml_element::parse_doctype(const char *&data) { + name = "!DOCTYPE"; + const char *content_begin = data; + + signed counter = 0; + while(*data) { + char value = *data++; + if(value == '<') counter++; + if(value == '>') counter--; + if(counter < 0) { + content = substr(content_begin, 0, data - content_begin - 1); + return; + } + } + throw "..."; +} + +inline bool xml_element::parse_head(string data) { + data.qreplace("\t", " "); + data.qreplace("\r", " "); + data.qreplace("\n", " "); + while(qstrpos(data, " ")) data.qreplace(" ", " "); + data.qreplace(" =", "="); + data.qreplace("= ", "="); + rtrim(data); + + lstring part; + part.qsplit(" ", data); + + name = part[0]; + if(name == "") throw "..."; + + for(unsigned i = 1; i < part.size(); i++) { + lstring side; + side.qsplit("=", part[i]); + if(side.size() != 2) throw "..."; + + xml_attribute attr; + attr.name = side[0]; + attr.content = side[1]; + if(strbegin(attr.content, "\"") && strend(attr.content, "\"")) trim_once(attr.content, "\""); + else if(strbegin(attr.content, "'") && strend(attr.content, "'")) trim_once(attr.content, "'"); + else throw "..."; + attribute.add(attr); + } +} + +inline bool xml_element::parse_body(const char *&data) { + while(true) { + if(!*data) return false; + if(*data++ != '<') continue; + if(*data == '/') return false; + + if(strbegin(data, "!DOCTYPE") == true) { + parse_doctype(data); + return true; + } + + if(strbegin(data, "!--")) { + if(auto offset = strpos(data, "-->")) { + data += offset() + 3; + continue; + } else { + throw "..."; + } + } + + if(strbegin(data, "![CDATA[")) { + if(auto offset = strpos(data, "]]>")) { + data += offset() + 3; + continue; + } else { + throw "..."; + } + } + + auto offset = strpos(data, ">"); + if(!offset) throw "..."; + + string tag = substr(data, 0, offset()); + data += offset() + 1; + const char *content_begin = data; + + bool self_terminating = false; + + if(strend(tag, "?") == true) { + self_terminating = true; + rtrim_once(tag, "?"); + } else if(strend(tag, "/") == true) { + self_terminating = true; + rtrim_once(tag, "/"); + } + + parse_head(tag); + if(self_terminating) return true; + + while(*data) { + unsigned index = element.size(); + xml_element node; + if(node.parse_body(data) == false) { + if(*data == '/') { + signed length = data - content_begin - 1; + if(length > 0) content = substr(content_begin, 0, length); + + data++; + auto offset = strpos(data, ">"); + if(!offset) throw "..."; + + tag = substr(data, 0, offset()); + data += offset() + 1; + + tag.replace("\t", " "); + tag.replace("\r", " "); + tag.replace("\n", " "); + while(strpos(tag, " ")) tag.replace(" ", " "); + rtrim(tag); + + if(name != tag) throw "..."; + return true; + } + } else { + element.add(node); + } + } + } +} + +//ensure there is only one root element +inline bool xml_validate(xml_element &document) { + unsigned root_counter = 0; + + for(unsigned i = 0; i < document.element.size(); i++) { + string &name = document.element[i].name; + if(strbegin(name, "?")) continue; + if(strbegin(name, "!")) continue; + if(++root_counter > 1) return false; + } + + return true; +} + +inline xml_element xml_parse(const char *data) { + xml_element self; + + try { + while(*data) { + xml_element node; + if(node.parse_body(data) == false) { + break; + } else { + self.element.add(node); + } + } + + if(xml_validate(self) == false) throw "..."; + return self; + } catch(const char*) { + xml_element empty; + return empty; + } +} + +} + +#endif diff --git a/snesreader/nall/ups.hpp b/snesreader/nall/ups.hpp new file mode 100644 index 00000000..f255ecb3 --- /dev/null +++ b/snesreader/nall/ups.hpp @@ -0,0 +1,190 @@ +#ifndef NALL_UPS_HPP +#define NALL_UPS_HPP + +#include + +#include +#include +#include +#include + +namespace nall { + class ups { + public: + enum result { + ok, + patch_unreadable, + patch_unwritable, + patch_invalid, + input_invalid, + output_invalid, + patch_crc32_invalid, + input_crc32_invalid, + output_crc32_invalid, + }; + + ups::result create(const char *patch_fn, const uint8_t *x_data, unsigned x_size, const uint8_t *y_data, unsigned y_size) { + if(!fp.open(patch_fn, file::mode_write)) return patch_unwritable; + + crc32 = ~0; + uint32_t x_crc32 = crc32_calculate(x_data, x_size); + uint32_t y_crc32 = crc32_calculate(y_data, y_size); + + //header + write('U'); + write('P'); + write('S'); + write('1'); + encptr(x_size); + encptr(y_size); + + //body + unsigned max_size = max(x_size, y_size); + unsigned relative = 0; + for(unsigned i = 0; i < max_size;) { + uint8_t x = i < x_size ? x_data[i] : 0x00; + uint8_t y = i < y_size ? y_data[i] : 0x00; + + if(x == y) { + i++; + continue; + } + + encptr(i++ - relative); + write(x ^ y); + + while(true) { + if(i >= max_size) { + write(0x00); + break; + } + + x = i < x_size ? x_data[i] : 0x00; + y = i < y_size ? y_data[i] : 0x00; + i++; + write(x ^ y); + if(x == y) break; + } + + relative = i; + } + + //footer + for(unsigned i = 0; i < 4; i++) write(x_crc32 >> (i << 3)); + for(unsigned i = 0; i < 4; i++) write(y_crc32 >> (i << 3)); + uint32_t p_crc32 = ~crc32; + for(unsigned i = 0; i < 4; i++) write(p_crc32 >> (i << 3)); + + fp.close(); + return ok; + } + + ups::result apply(const uint8_t *p_data, unsigned p_size, const uint8_t *x_data, unsigned x_size, uint8_t *&y_data, unsigned &y_size) { + if(p_size < 18) return patch_invalid; + p_buffer = p_data; + + crc32 = ~0; + + //header + if(read() != 'U') return patch_invalid; + if(read() != 'P') return patch_invalid; + if(read() != 'S') return patch_invalid; + if(read() != '1') return patch_invalid; + + unsigned px_size = decptr(); + unsigned py_size = decptr(); + + //mirror + if(x_size != px_size && x_size != py_size) return input_invalid; + y_size = (x_size == px_size) ? py_size : px_size; + y_data = new uint8_t[y_size](); + + for(unsigned i = 0; i < x_size && i < y_size; i++) y_data[i] = x_data[i]; + for(unsigned i = x_size; i < y_size; i++) y_data[i] = 0x00; + + //body + unsigned relative = 0; + while(p_buffer < p_data + p_size - 12) { + relative += decptr(); + + while(true) { + uint8_t x = read(); + if(x && relative < y_size) { + uint8_t y = relative < x_size ? x_data[relative] : 0x00; + y_data[relative] = x ^ y; + } + relative++; + if(!x) break; + } + } + + //footer + unsigned px_crc32 = 0, py_crc32 = 0, pp_crc32 = 0; + for(unsigned i = 0; i < 4; i++) px_crc32 |= read() << (i << 3); + for(unsigned i = 0; i < 4; i++) py_crc32 |= read() << (i << 3); + uint32_t p_crc32 = ~crc32; + for(unsigned i = 0; i < 4; i++) pp_crc32 |= read() << (i << 3); + + uint32_t x_crc32 = crc32_calculate(x_data, x_size); + uint32_t y_crc32 = crc32_calculate(y_data, y_size); + + if(px_size != py_size) { + if(x_size == px_size && x_crc32 != px_crc32) return input_crc32_invalid; + if(x_size == py_size && x_crc32 != py_crc32) return input_crc32_invalid; + if(y_size == px_size && y_crc32 != px_crc32) return output_crc32_invalid; + if(y_size == py_size && y_crc32 != py_crc32) return output_crc32_invalid; + } else { + if(x_crc32 != px_crc32 && x_crc32 != py_crc32) return input_crc32_invalid; + if(y_crc32 != px_crc32 && y_crc32 != py_crc32) return output_crc32_invalid; + if(x_crc32 == y_crc32 && px_crc32 != py_crc32) return output_crc32_invalid; + if(x_crc32 != y_crc32 && px_crc32 == py_crc32) return output_crc32_invalid; + } + + if(p_crc32 != pp_crc32) return patch_crc32_invalid; + return ok; + } + + private: + file fp; + uint32_t crc32; + const uint8_t *p_buffer; + + uint8_t read() { + uint8_t n = *p_buffer++; + crc32 = crc32_adjust(crc32, n); + return n; + } + + void write(uint8_t n) { + fp.write(n); + crc32 = crc32_adjust(crc32, n); + } + + void encptr(uint64_t offset) { + while(true) { + uint64_t x = offset & 0x7f; + offset >>= 7; + if(offset == 0) { + write(0x80 | x); + break; + } + write(x); + offset--; + } + } + + uint64_t decptr() { + uint64_t offset = 0, shift = 1; + while(true) { + uint8_t x = read(); + offset += (x & 0x7f) * shift; + if(x & 0x80) break; + shift <<= 7; + offset += shift; + } + return offset; + } + }; +} + +#endif diff --git a/snesreader/nall/utf8.hpp b/snesreader/nall/utf8.hpp new file mode 100644 index 00000000..c66c341a --- /dev/null +++ b/snesreader/nall/utf8.hpp @@ -0,0 +1,72 @@ +#ifndef NALL_UTF8_HPP +#define NALL_UTF8_HPP + +//UTF-8 <> UTF-16 conversion +//used only for Win32; Linux, etc use UTF-8 internally + +#if defined(_WIN32) + +#undef _WIN32_WINNT +#define _WIN32_WINNT 0x0501 +#undef NOMINMAX +#define NOMINMAX +#include +#undef interface + +namespace nall { + //UTF-8 to UTF-16 + class utf16_t { + public: + operator wchar_t*() { + return buffer; + } + + operator const wchar_t*() const { + return buffer; + } + + utf16_t(const char *s = "") { + if(!s) s = ""; + unsigned length = MultiByteToWideChar(CP_UTF8, 0, s, -1, 0, 0); + buffer = new wchar_t[length + 1](); + MultiByteToWideChar(CP_UTF8, 0, s, -1, buffer, length); + } + + ~utf16_t() { + delete[] buffer; + } + + private: + wchar_t *buffer; + }; + + //UTF-16 to UTF-8 + class utf8_t { + public: + operator char*() { + return buffer; + } + + operator const char*() const { + return buffer; + } + + utf8_t(const wchar_t *s = L"") { + if(!s) s = L""; + unsigned length = WideCharToMultiByte(CP_UTF8, 0, s, -1, 0, 0, (const char*)0, (BOOL*)0); + buffer = new char[length + 1](); + WideCharToMultiByte(CP_UTF8, 0, s, -1, buffer, length, (const char*)0, (BOOL*)0); + } + + ~utf8_t() { + delete[] buffer; + } + + private: + char *buffer; + }; +} + +#endif //if defined(_WIN32) + +#endif diff --git a/snesreader/nall/utility.hpp b/snesreader/nall/utility.hpp new file mode 100644 index 00000000..2a63f515 --- /dev/null +++ b/snesreader/nall/utility.hpp @@ -0,0 +1,30 @@ +#ifndef NALL_UTILITY_HPP +#define NALL_UTILITY_HPP + +#include +#include + +namespace nall { + template struct enable_if { typedef T type; }; + template struct enable_if {}; + template struct mp_enable_if : enable_if {}; + + template inline void swap(T &x, T &y) { + T temp(std::move(x)); + x = std::move(y); + y = std::move(temp); + } + + template struct base_from_member { + T value; + base_from_member(T value_) : value(value_) {} + }; + + template inline T* allocate(size_t size, const T &value) { + T *array = new T[size]; + for(size_t i = 0; i < size; i++) array[i] = value; + return array; + } +} + +#endif diff --git a/snesreader/nall/varint.hpp b/snesreader/nall/varint.hpp new file mode 100644 index 00000000..cc3bb17c --- /dev/null +++ b/snesreader/nall/varint.hpp @@ -0,0 +1,92 @@ +#ifndef NALL_VARINT_HPP +#define NALL_VARINT_HPP + +#include +#include +#include + +namespace nall { + template class uint_t { + private: + enum { bytes = (bits + 7) >> 3 }; //minimum number of bytes needed to store value + typedef typename static_if< + sizeof(int) >= bytes, + unsigned int, + typename static_if< + sizeof(long) >= bytes, + unsigned long, + typename static_if< + sizeof(long long) >= bytes, + unsigned long long, + void + >::type + >::type + >::type T; + static_assert::value> uint_assert; + T data; + + public: + inline operator T() const { return data; } + inline T operator ++(int) { T r = data; data = uclip(data + 1); return r; } + inline T operator --(int) { T r = data; data = uclip(data - 1); return r; } + inline T operator ++() { return data = uclip(data + 1); } + inline T operator --() { return data = uclip(data - 1); } + inline T operator =(const T i) { return data = uclip(i); } + inline T operator |=(const T i) { return data = uclip(data | i); } + inline T operator ^=(const T i) { return data = uclip(data ^ i); } + inline T operator &=(const T i) { return data = uclip(data & i); } + inline T operator<<=(const T i) { return data = uclip(data << i); } + inline T operator>>=(const T i) { return data = uclip(data >> i); } + inline T operator +=(const T i) { return data = uclip(data + i); } + inline T operator -=(const T i) { return data = uclip(data - i); } + inline T operator *=(const T i) { return data = uclip(data * i); } + inline T operator /=(const T i) { return data = uclip(data / i); } + inline T operator %=(const T i) { return data = uclip(data % i); } + + inline uint_t() : data(0) {} + inline uint_t(const T i) : data(uclip(i)) {} + }; + + template class int_t { + private: + enum { bytes = (bits + 7) >> 3 }; //minimum number of bytes needed to store value + typedef typename static_if< + sizeof(int) >= bytes, + signed int, + typename static_if< + sizeof(long) >= bytes, + signed long, + typename static_if< + sizeof(long long) >= bytes, + signed long long, + void + >::type + >::type + >::type T; + static_assert::value> int_assert; + T data; + + public: + inline operator T() const { return data; } + inline T operator ++(int) { T r = data; data = sclip(data + 1); return r; } + inline T operator --(int) { T r = data; data = sclip(data - 1); return r; } + inline T operator ++() { return data = sclip(data + 1); } + inline T operator --() { return data = sclip(data - 1); } + inline T operator =(const T i) { return data = sclip(i); } + inline T operator |=(const T i) { return data = sclip(data | i); } + inline T operator ^=(const T i) { return data = sclip(data ^ i); } + inline T operator &=(const T i) { return data = sclip(data & i); } + inline T operator<<=(const T i) { return data = sclip(data << i); } + inline T operator>>=(const T i) { return data = sclip(data >> i); } + inline T operator +=(const T i) { return data = sclip(data + i); } + inline T operator -=(const T i) { return data = sclip(data - i); } + inline T operator *=(const T i) { return data = sclip(data * i); } + inline T operator /=(const T i) { return data = sclip(data / i); } + inline T operator %=(const T i) { return data = sclip(data % i); } + + inline int_t() : data(0) {} + inline int_t(const T i) : data(sclip(i)) {} + }; +} + +#endif diff --git a/snesreader/nall/vector.hpp b/snesreader/nall/vector.hpp new file mode 100644 index 00000000..3d69d4d5 --- /dev/null +++ b/snesreader/nall/vector.hpp @@ -0,0 +1,240 @@ +#ifndef NALL_VECTOR_HPP +#define NALL_VECTOR_HPP + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace nall { + //linear_vector + //memory: O(capacity * 2) + // + //linear_vector uses placement new + manual destructor calls to create a + //contiguous block of memory for all objects. accessing individual elements + //is fast, though resizing the array incurs significant overhead. + //reserve() overhead is reduced from quadratic time to amortized constant time + //by resizing twice as much as requested. + // + //if objects hold memory address references to themselves (introspection), a + //valid copy constructor will be needed to keep pointers valid. + + template class linear_vector { + protected: + T *pool; + unsigned poolsize, objectsize; + + public: + unsigned size() const { return objectsize; } + unsigned capacity() const { return poolsize; } + + void reset() { + if(pool) { + for(unsigned i = 0; i < objectsize; i++) pool[i].~T(); + free(pool); + } + pool = 0; + poolsize = 0; + objectsize = 0; + } + + void reserve(unsigned newsize) { + newsize = bit::round(newsize); //round to nearest power of two (for amortized growth) + + T *poolcopy = (T*)malloc(newsize * sizeof(T)); + for(unsigned i = 0; i < min(objectsize, newsize); i++) new(poolcopy + i) T(pool[i]); + for(unsigned i = 0; i < objectsize; i++) pool[i].~T(); + free(pool); + pool = poolcopy; + poolsize = newsize; + objectsize = min(objectsize, newsize); + } + + void resize(unsigned newsize) { + if(newsize > poolsize) reserve(newsize); + + if(newsize < objectsize) { + //vector is shrinking; destroy excess objects + for(unsigned i = newsize; i < objectsize; i++) pool[i].~T(); + } else if(newsize > objectsize) { + //vector is expanding; allocate new objects + for(unsigned i = objectsize; i < newsize; i++) new(pool + i) T; + } + + objectsize = newsize; + } + + void add(const T data) { + if(objectsize + 1 > poolsize) reserve(objectsize + 1); + new(pool + objectsize++) T(data); + } + + inline T& operator[](unsigned index) { + if(index >= objectsize) resize(index + 1); + return pool[index]; + } + + inline const T& operator[](unsigned index) const { + if(index >= objectsize) throw "vector[] out of bounds"; + return pool[index]; + } + + //copy + inline linear_vector& operator=(const linear_vector &source) { + reset(); + reserve(source.capacity()); + resize(source.size()); + for(unsigned i = 0; i < source.size(); i++) operator[](i) = source.operator[](i); + return *this; + } + + linear_vector(const linear_vector &source) : pool(0), poolsize(0), objectsize(0) { + operator=(source); + } + + //move + inline linear_vector& operator=(linear_vector &&source) { + reset(); + pool = source.pool; + poolsize = source.poolsize; + objectsize = source.objectsize; + source.pool = 0; + source.reset(); + return *this; + } + + linear_vector(linear_vector &&source) : pool(0), poolsize(0), objectsize(0) { + operator=(std::move(source)); + } + + //construction + linear_vector() : pool(0), poolsize(0), objectsize(0) { + } + + linear_vector(std::initializer_list list) : pool(0), poolsize(0), objectsize(0) { + for(const T *p = list.begin(); p != list.end(); ++p) add(*p); + } + + ~linear_vector() { + reset(); + } + }; + + //pointer_vector + //memory: O(1) + // + //pointer_vector keeps an array of pointers to each vector object. this adds + //significant overhead to individual accesses, but allows for optimal memory + //utilization. + // + //by guaranteeing that the base memory address of each objects never changes, + //this avoids the need for an object to have a valid copy constructor. + + template class pointer_vector { + protected: + T **pool; + unsigned poolsize, objectsize; + + public: + unsigned size() const { return objectsize; } + unsigned capacity() const { return poolsize; } + + void reset() { + if(pool) { + for(unsigned i = 0; i < objectsize; i++) { if(pool[i]) delete pool[i]; } + free(pool); + } + pool = 0; + poolsize = 0; + objectsize = 0; + } + + void reserve(unsigned newsize) { + newsize = bit::round(newsize); //round to nearest power of two (for amortized growth) + + for(unsigned i = newsize; i < objectsize; i++) { + if(pool[i]) { delete pool[i]; pool[i] = 0; } + } + + pool = (T**)realloc(pool, newsize * sizeof(T*)); + for(unsigned i = poolsize; i < newsize; i++) pool[i] = 0; + poolsize = newsize; + objectsize = min(objectsize, newsize); + } + + void resize(unsigned newsize) { + if(newsize > poolsize) reserve(newsize); + + for(unsigned i = newsize; i < objectsize; i++) { + if(pool[i]) { delete pool[i]; pool[i] = 0; } + } + + objectsize = newsize; + } + + void add(const T data) { + if(objectsize + 1 > poolsize) reserve(objectsize + 1); + pool[objectsize++] = new T(data); + } + + inline T& operator[](unsigned index) { + if(index >= objectsize) resize(index + 1); + if(!pool[index]) pool[index] = new T; + return *pool[index]; + } + + inline const T& operator[](unsigned index) const { + if(index >= objectsize || !pool[index]) throw "vector[] out of bounds"; + return *pool[index]; + } + + //copy + inline pointer_vector& operator=(const pointer_vector &source) { + reset(); + reserve(source.capacity()); + resize(source.size()); + for(unsigned i = 0; i < source.size(); i++) operator[](i) = source.operator[](i); + return *this; + } + + pointer_vector(const pointer_vector &source) : pool(0), poolsize(0), objectsize(0) { + operator=(source); + } + + //move + inline pointer_vector& operator=(pointer_vector &&source) { + reset(); + pool = source.pool; + poolsize = source.poolsize; + objectsize = source.objectsize; + source.pool = 0; + source.reset(); + return *this; + } + + pointer_vector(pointer_vector &&source) : pool(0), poolsize(0), objectsize(0) { + operator=(std::move(source)); + } + + //construction + pointer_vector() : pool(0), poolsize(0), objectsize(0) { + } + + pointer_vector(std::initializer_list list) : pool(0), poolsize(0), objectsize(0) { + for(const T *p = list.begin(); p != list.end(); ++p) add(*p); + } + + ~pointer_vector() { + reset(); + } + }; + + template struct has_size> { enum { value = true }; }; + template struct has_size> { enum { value = true }; }; +} + +#endif diff --git a/snesreader/snesreader.cpp b/snesreader/snesreader.cpp new file mode 100644 index 00000000..1d93de68 --- /dev/null +++ b/snesreader/snesreader.cpp @@ -0,0 +1,227 @@ +#include "snesreader.hpp" + +#if defined(_WIN32) + #define dllexport __declspec(dllexport) +#else + #define dllexport +#endif + +#include "fex/fex.h" +#include "libjma/jma.h" +extern "C" char* uncompressStream(int, int); //micro-bunzip + +#define QT_CORE_LIB +#include + +#include +#include +using namespace nall; + +dllexport const char* snesreader_supported() { + return "*.zip *.z *.7z *.rar *.gz *.bz2 *.jma"; +} + +void snesreader_apply_ips(const char *filename, uint8_t *&data, unsigned &size) { + file fp; + if(fp.open(filename, file::mode_read) == false) return; + + unsigned psize = fp.size(); + uint8_t *pdata = new uint8_t[psize]; + fp.read(pdata, psize); + fp.close(); + + if(psize < 8 || pdata[0] != 'P' || pdata[1] != 'A' || pdata[2] != 'T' || pdata[3] != 'C' || pdata[4] != 'H') { delete[] pdata; return; } + + unsigned outsize = 0; + uint8_t *outdata = new uint8_t[16 * 1024 * 1024]; + memset(outdata, 0, 16 * 1024 * 1024); + memcpy(outdata, data, size); + + unsigned offset = 5; + while(offset < psize - 3) { + unsigned addr; + addr = pdata[offset++] << 16; + addr |= pdata[offset++] << 8; + addr |= pdata[offset++] << 0; + + unsigned size; + size = pdata[offset++] << 8; + size |= pdata[offset++] << 0; + + if(size == 0) { + //RLE + size = pdata[offset++] << 8; + size |= pdata[offset++] << 0; + + for(unsigned n = addr; n < addr + size;) { + outdata[n++] = pdata[offset]; + if(n > outsize) outsize = n; + } + offset++; + } else { + //uncompressed + for(unsigned n = addr; n < addr + size;) { + outdata[n++] = pdata[offset++]; + if(n > outsize) outsize = n; + } + } + } + + delete[] pdata; + delete[] data; + data = outdata; + size = max(size, outsize); +} + +bool snesreader_load_normal(const char *filename, uint8_t *&data, unsigned &size) { + file fp; + if(fp.open(filename, file::mode_read) == false) return false; + size = fp.size(); + data = new uint8_t[size]; + fp.read(data, size); + fp.close(); + return true; +} + +#include "filechooser.cpp" + +bool snesreader_load_fex(string &filename, uint8_t *&data, unsigned &size) { + fex_t *fex; + fex_open(&fex, filename); + if(fex_done(fex)) { fex_close(fex); return false; } + + if(!fileChooser) fileChooser = new FileChooser; + fileChooser->list.reset(); + + while(fex_done(fex) == false) { + fex_stat(fex); + const char *name = fex_name(fex); + //only add valid ROM extensions to list (ignore text files, save RAM files, etc) + if(striend(name, ".sfc") || striend(name, ".smc") + || striend(name, ".swc") || striend(name, ".fig") + || striend(name, ".bs") || striend(name, ".st") + || striend(name, ".gb") || striend(name, ".sgb") || striend(name, ".gbc") + || striend(filename, ".gz") //GZip files only contain a single file + ) { + fileChooser->list[fileChooser->list.size()] = name; + } + fex_next(fex); + } + + string name = fileChooser->exec(); + if(name == "") { fex_close(fex); return false; } + + fex_rewind(fex); + while(fex_done(fex) == false) { + fex_stat(fex); + if(name == fex_name(fex)) { + size = fex_size(fex); + data = new uint8_t[size]; + fex_read(fex, data, size); + fex_close(fex); + + if(fileChooser->list.size() > 1) { + strtr(name, "\\", "/"); + strtr(filename, "\\", "/"); + + //retain only path from filename, "/foo/bar.7z" -> "/foo/" + for(signed i = filename.length() - 1; i >= 0; i--) { + if(filename[i] == '/') { + filename[i + 1] = 0; + break; + } + } + + //append only filename from archive, "foo/bar.sfc" -> "bar.sfc" + lstring part; + part.split("/", name); + filename = string() << filename << part[part.size() - 1]; + } + + return true; + } + fex_next(fex); + } + + fex_close(fex); + return false; +} + +bool snesreader_load_bz2(const char *filename, uint8_t *&data, unsigned &size) { + //TODO: need a way to get the size of a bzip2 file, so we can pre-allocate + //a buffer to decompress into memory. for now, use a temporary file. + + string name = "/tmp/.bz2_temporary_decompression_object"; + FILE *wr; + wr = fopen_utf8(name, "wb"); + if(!wr) { + //try the local directory + name = ".bz2_temporary_decompression_object"; + wr = fopen_utf8(name, "wb"); + //can't get write access, so give up + if(!wr) return false; + } + + FILE *fp = fopen_utf8(filename, "rb"); + uncompressStream(fileno(fp), fileno(wr)); + fclose(fp); + fclose(wr); + + bool success = snesreader_load_normal(name, data, size); + unlink(name); + return success; +} + +bool snesreader_load_jma(const char *filename, uint8_t *&data, unsigned &size) { + try { + JMA::jma_open JMAFile(filename); + std::string name; + + std::vector file_info = JMAFile.get_files_info(); + for(std::vector::iterator i = file_info.begin(); i != file_info.end(); i++) { + name = i->name; + size = i->size; + break; + } + + data = new uint8_t[size]; + JMAFile.extract_file(name, data); + return true; + } catch(JMA::jma_errors) { + return false; + } +} + +dllexport bool snesreader_load(string &filename, uint8_t *&data, unsigned &size) { + if(file::exists(filename) == false) return false; + + bool success = false; + if(striend(filename, ".zip") + || striend(filename, ".z") + || striend(filename, ".7z") + || striend(filename, ".rar") + || striend(filename, ".gz")) { + success = snesreader_load_fex(filename, data, size); + } else if(striend(filename, ".bz2")) { + success = snesreader_load_bz2(filename, data, size); + } else if(striend(filename, ".jma")) { + success = snesreader_load_jma(filename, data, size); + } else { + success = snesreader_load_normal(filename, data, size); + } + + if(success == false) return false; + + //apply IPS patch, if it exists + string patchname = filename; + for(int i = patchname.length() - 1; i >= 0; i--) { + if(patchname[i] == '.') { patchname[i] = 0; break; } + } + patchname << ".ips"; + if(file::exists(patchname)) snesreader_apply_ips(patchname, data, size); + + //remove copier header, if it exists + if((size & 0x7fff) == 512) memmove(data, data + 512, size -= 512); + + return true; +} diff --git a/snesreader/snesreader.hpp b/snesreader/snesreader.hpp new file mode 100644 index 00000000..1935b43a --- /dev/null +++ b/snesreader/snesreader.hpp @@ -0,0 +1,7 @@ +#include +namespace nall { class string; } + +extern "C" { + const char* snesreader_supported(); + bool snesreader_load(nall::string &filename, uint8_t *&data, unsigned &size); +} diff --git a/snesreader/sync.sh b/snesreader/sync.sh new file mode 100644 index 00000000..4bbaf34f --- /dev/null +++ b/snesreader/sync.sh @@ -0,0 +1,2 @@ +rm -r nall +cp -r ../nall ./nall diff --git a/snesreader/unrar/archive.cpp b/snesreader/unrar/archive.cpp new file mode 100644 index 00000000..338a0eb7 --- /dev/null +++ b/snesreader/unrar/archive.cpp @@ -0,0 +1,97 @@ +#include +#include "rar.hpp" + +#include "unrar.h" + +Archive::Archive() : Raw( this ) +{ + OldFormat=false; + Solid=false; + + CurBlockPos=0; + NextBlockPos=0; + + memset(&NewMhd,0,sizeof(NewMhd)); + NewMhd.HeadType=MAIN_HEAD; + NewMhd.HeadSize=SIZEOF_NEWMHD; + HeaderCRC=0; +} + +bool Archive::IsSignature(byte *D) +{ + bool Valid=false; + if (D[0]==0x52) +#ifndef SFX_MODULE + if (D[1]==0x45 && D[2]==0x7e && D[3]==0x5e) + { + OldFormat=true; + Valid=true; + } + else +#endif + if (D[1]==0x61 && D[2]==0x72 && D[3]==0x21 && D[4]==0x1a && D[5]==0x07 && D[6]==0x00) + { + OldFormat=false; + Valid=true; + } + return(Valid); +} + + +unrar_err_t Archive::IsArchive() +{ + if (Read(MarkHead.Mark,SIZEOF_MARKHEAD)!=SIZEOF_MARKHEAD) + return unrar_err_not_arc; + + if (IsSignature(MarkHead.Mark)) + { + if (OldFormat) + Seek(0,SEEK_SET); + } + else + { + if (SFXSize==0) + return unrar_err_not_arc; + } + + unrar_err_t error = + ReadHeader(); + // (no need to seek to next) + if ( error != unrar_ok ) + return error; + +#ifndef SFX_MODULE + if (OldFormat) + { + NewMhd.Flags=OldMhd.Flags & 0x3f; + NewMhd.HeadSize=OldMhd.HeadSize; + } + else +#endif + { + if (HeaderCRC!=NewMhd.HeadCRC) + { + return unrar_err_corrupt; + } + } + bool + Volume=(NewMhd.Flags & MHD_VOLUME); + Solid=(NewMhd.Flags & MHD_SOLID)!=0; + bool + Encrypted=(NewMhd.Flags & MHD_PASSWORD)!=0; + + // (removed decryption and volume handling) + + if ( Encrypted ) + return unrar_err_encrypted; + + if ( Volume ) + return unrar_err_segmented; + + return unrar_ok; +} + +void Archive::SeekToNext() +{ + Seek(NextBlockPos,SEEK_SET); +} diff --git a/snesreader/unrar/archive.hpp b/snesreader/unrar/archive.hpp new file mode 100644 index 00000000..0106e6fd --- /dev/null +++ b/snesreader/unrar/archive.hpp @@ -0,0 +1,45 @@ +#ifndef _RAR_ARCHIVE_ +#define _RAR_ARCHIVE_ + +typedef ComprDataIO File; +#include "rawread.hpp" + +class Archive:public File +{ +private: + bool IsSignature(byte *D); + void ConvertUnknownHeader(); + int ReadOldHeader(); + + RawRead Raw; + + MarkHeader MarkHead; + OldMainHeader OldMhd; + + int CurHeaderType; + +public: + Archive(); + unrar_err_t IsArchive(); + unrar_err_t ReadHeader(); + void SeekToNext(); + bool IsArcDir(); + bool IsArcLabel(); + int GetHeaderType() {return(CurHeaderType);}; + + BaseBlock ShortBlock; + MainHeader NewMhd; + FileHeader NewLhd; + SubBlockHeader SubBlockHead; + FileHeader SubHead; + ProtectHeader ProtectHead; + + Int64 CurBlockPos; + Int64 NextBlockPos; + + bool Solid; + enum { SFXSize = 0 }; // self-extracting not supported + ushort HeaderCRC; +}; + +#endif diff --git a/snesreader/unrar/arcread.cpp b/snesreader/unrar/arcread.cpp new file mode 100644 index 00000000..3a9f711c --- /dev/null +++ b/snesreader/unrar/arcread.cpp @@ -0,0 +1,314 @@ +#include "rar.hpp" + +#include "unrar.h" +#include "unicode.hpp" +#include "encname.hpp" + +// arcread.cpp +unrar_err_t Archive::ReadHeader() +{ + CurBlockPos=Tell(); + +#ifndef SFX_MODULE + if (OldFormat) + { + ReadOldHeader(); + + if ( Raw.Size() == 0 ) + return unrar_err_arc_eof; // right at end of file + + if ( Raw.PaddedSize() > 0 ) // added check + return unrar_err_corrupt; // missing data + + return unrar_ok; + } +#endif + + Raw.Reset(); + + // (removed decryption) + + Raw.Read(SIZEOF_SHORTBLOCKHEAD); + if (Raw.Size()==0) + { + return unrar_err_arc_eof; // right at end of file + } + + Raw.Get(ShortBlock.HeadCRC); + byte HeadType; + Raw.Get(HeadType); + ShortBlock.HeadType=(HEADER_TYPE)HeadType; + Raw.Get(ShortBlock.Flags); + Raw.Get(ShortBlock.HeadSize); + if (ShortBlock.HeadSize 0 ) // fewer than requested bytes read above? + return unrar_err_corrupt; // missing data + + NextBlockPos=CurBlockPos+ShortBlock.HeadSize; + + switch(ShortBlock.HeadType) + { + case MAIN_HEAD: + *(BaseBlock *)&NewMhd=ShortBlock; + Raw.Get(NewMhd.HighPosAV); + Raw.Get(NewMhd.PosAV); + check( Raw.ReadPos == Raw.DataSize ); // we should have read all fields + break; + case FILE_HEAD: + case NEWSUB_HEAD: + { + FileHeader *hd=ShortBlock.HeadType==FILE_HEAD ? &NewLhd:&SubHead; + *(BaseBlock *)hd=ShortBlock; + Raw.Get(hd->PackSize); + Raw.Get(hd->UnpSize); + Raw.Get(hd->HostOS); + Raw.Get(hd->FileCRC); + Raw.Get(hd->FileTime); + Raw.Get(hd->UnpVer); + Raw.Get(hd->Method); + Raw.Get(hd->NameSize); + Raw.Get(hd->FileAttr); + if (hd->Flags & LHD_LARGE) + { + Raw.Get(hd->HighPackSize); + Raw.Get(hd->HighUnpSize); + } + else + { + hd->HighPackSize=hd->HighUnpSize=0; + if (hd->UnpSize==0xffffffff) + { + // TODO: what the heck is this for anyway? + hd->UnpSize=0; + hd->HighUnpSize=0x7fffffff; + } + } + hd->FullPackSize=int32to64(hd->HighPackSize,hd->PackSize); + hd->FullUnpSize=int32to64(hd->HighUnpSize,hd->UnpSize); + + if ( int32to64( 1, 0 ) == 0 && (hd->HighPackSize || hd->HighUnpSize) ) + return unrar_err_huge; + + char (&FileName) [sizeof hd->FileName] = hd->FileName; // eliminated local buffer + int NameSize=Min(hd->NameSize,sizeof(FileName)-1); + Raw.Get((byte *)FileName,NameSize); + FileName[NameSize]=0; + + if (hd->HeadType==NEWSUB_HEAD) + { + // have to adjust this, even through we're ignoring this block + NextBlockPos+=hd->FullPackSize; + break; + } + else + if (hd->HeadType==FILE_HEAD) + { + if (hd->Flags & LHD_UNICODE) + { + EncodeFileName NameCoder; + int Length=strlen(FileName); + if (Length==hd->NameSize) + { + UtfToWide(FileName,hd->FileNameW,sizeof(hd->FileNameW)/sizeof(hd->FileNameW[0])-1); + WideToChar(hd->FileNameW,hd->FileName,sizeof(hd->FileName)/sizeof(hd->FileName[0])-1); + ExtToInt(hd->FileName,hd->FileName); + } + else + { + Length++; + NameCoder.Decode(FileName,(byte *)FileName+Length, + hd->NameSize-Length,hd->FileNameW, + sizeof(hd->FileNameW)/sizeof(hd->FileNameW[0])); + } + if (*hd->FileNameW==0) + hd->Flags &= ~LHD_UNICODE; + } + else + *hd->FileNameW=0; + + ConvertUnknownHeader(); + } + if (hd->Flags & LHD_SALT) + Raw.Get(hd->Salt,SALT_SIZE); + hd->mtime.SetDos(hd->FileTime); + if (hd->Flags & LHD_EXTTIME) + { + ushort Flags; + Raw.Get(Flags); + // Ignore additional time information + for (int I=0;I<4;I++) + { + uint rmode=Flags>>(3-I)*4; + if ((rmode & 8)==0) + continue; + if (I!=0) + { + uint DosTime; + Raw.Get(DosTime); + } + + // skip time info + int count=rmode&3; + for (int J=0;JFullPackSize; + bool CRCProcessedOnly=(hd->Flags & LHD_COMMENT)!=0; + HeaderCRC=~Raw.GetCRC(CRCProcessedOnly)&0xffff; + if (hd->HeadCRC!=HeaderCRC) + return unrar_err_corrupt; + check( CRCProcessedOnly == false ); // I need to test on archives where this doesn't hold + check( Raw.ReadPos == Raw.DataSize ); // we should have read all fields + } + break; +#ifndef SFX_MODULE + // Handle these block types just so we can adjust NextBlockPos properly + case PROTECT_HEAD: + Raw.Get(ProtectHead.DataSize); + NextBlockPos+=ProtectHead.DataSize; + break; + case SUB_HEAD: + Raw.Get(SubBlockHead.DataSize); + NextBlockPos+=SubBlockHead.DataSize; + break; +#endif + default: + if (ShortBlock.Flags & LONG_BLOCK) + { + uint DataSize; + Raw.Get(DataSize); + NextBlockPos+=DataSize; + } + break; + } + HeaderCRC=~Raw.GetCRC(false)&0xffff; + CurHeaderType=ShortBlock.HeadType; + // (removed decryption) + + if (NextBlockPosCurBlockPos ? Raw.Size():0); +} +#endif + +// (removed name case and attribute conversion) + +bool Archive::IsArcDir() +{ + return((NewLhd.Flags & LHD_WINDOWMASK)==LHD_DIRECTORY); +} + + +bool Archive::IsArcLabel() +{ + return(NewLhd.HostOS<=HOST_WIN32 && (NewLhd.FileAttr & 8)); +} + +// TODO: use '\\' on Windows? +char const CPATHDIVIDER = '/'; +#define charnext(s) ((s)+1) + +void Archive::ConvertUnknownHeader() +{ + if (NewLhd.UnpVer<20 && (NewLhd.FileAttr & 0x10)) + NewLhd.Flags|=LHD_DIRECTORY; + if (NewLhd.HostOS>=HOST_MAX) + { + if ((NewLhd.Flags & LHD_WINDOWMASK)==LHD_DIRECTORY) + NewLhd.FileAttr=0x10; + else + NewLhd.FileAttr=0x20; + } + { + for (char *s=NewLhd.FileName;*s!=0;s=charnext(s)) + { + if (*s=='/' || *s=='\\') + *s=CPATHDIVIDER; + } + } + // (removed Apple Unicode handling) + for (wchar *s=NewLhd.FileNameW;*s!=0;s++) + { + if (*s=='/' || *s=='\\') + *s=CPATHDIVIDER; + } +} diff --git a/snesreader/unrar/array.hpp b/snesreader/unrar/array.hpp new file mode 100644 index 00000000..1f2d4e8c --- /dev/null +++ b/snesreader/unrar/array.hpp @@ -0,0 +1,135 @@ +#ifndef _RAR_ARRAY_ +#define _RAR_ARRAY_ + +template class Array +{ +private: + T *Buffer; + int BufSize; + int AllocSize; +public: + Rar_Error_Handler& ErrHandler; + Array(Rar_Error_Handler*); + Array(int Size,Rar_Error_Handler*); + ~Array(); + inline void CleanData(); + inline T& operator [](int Item); + inline int Size(); + void Add(int Items); + void Alloc(int Items); + void Reset(); + void operator = (Array &Src); + void Push(T Item); + T* Addr() {return(Buffer);} +}; + +template void Array::CleanData() +{ + Buffer=NULL; + BufSize=0; + AllocSize=0; +} + + +template Array::Array(Rar_Error_Handler* eh) : ErrHandler( *eh ) +{ + CleanData(); +} + + +template Array::Array(int Size, Rar_Error_Handler* eh) : ErrHandler( *eh ) +{ + Buffer=(T *)rarmalloc(sizeof(T)*Size); + if (Buffer==NULL && Size!=0) + ErrHandler.MemoryError(); + + AllocSize=BufSize=Size; +} + + +template Array::~Array() +{ + if (Buffer!=NULL) + rarfree(Buffer); +} + + +template inline T& Array::operator [](int Item) +{ + return(Buffer[Item]); +} + + +template inline int Array::Size() +{ + return(BufSize); +} + + +template void Array::Add(int Items) +{ + int BufSize = this->BufSize; // don't change actual vars until alloc succeeds + T* Buffer = this->Buffer; + + BufSize+=Items; + if (BufSize>AllocSize) + { + int Suggested=AllocSize+AllocSize/4+32; + int NewSize=Max(BufSize,Suggested); + + Buffer=(T *)rarrealloc(Buffer,NewSize*sizeof(T)); + if (Buffer==NULL) + ErrHandler.MemoryError(); + AllocSize=NewSize; + } + + this->Buffer = Buffer; + this->BufSize = BufSize; +} + + +template void Array::Alloc(int Items) +{ + if (Items>AllocSize) + Add(Items-BufSize); + else + BufSize=Items; +} + + +template void Array::Reset() +{ + // Keep memory allocated if it's small + // Eliminates constant reallocation when scanning archive + if ( AllocSize < 1024/sizeof(T) ) + { + BufSize = 0; + return; + } + + if (Buffer!=NULL) + { + rarfree(Buffer); + Buffer=NULL; + } + BufSize=0; + AllocSize=0; +} + + +template void Array::operator =(Array &Src) +{ + Reset(); + Alloc(Src.BufSize); + if (Src.BufSize!=0) + memcpy((void *)Buffer,(void *)Src.Buffer,Src.BufSize*sizeof(T)); +} + + +template void Array::Push(T Item) +{ + Add(1); + (*this)[Size()-1]=Item; +} + +#endif diff --git a/snesreader/unrar/changes.txt b/snesreader/unrar/changes.txt new file mode 100644 index 00000000..35345fd5 --- /dev/null +++ b/snesreader/unrar/changes.txt @@ -0,0 +1,141 @@ +unrar_core source code changes +------------------------------ +Unrar_core is based on UnRAR (unrarsrc-3.8.5.tar.gz) by Alexander L. +Roshal. The original sources have been HEAVILY modified, trimmed down, +and purged of all OS-specific calls for file access and other +unnecessary operations. Support for encryption, recovery records, and +segmentation has been REMOVED. See license.txt for licensing. In +particular, this code cannot be used to re-create the RAR compression +algorithm, which is proprietary. + +If you obtained this code as a part of my File_Extractor library and +want to use it on its own, get my unrar_core library, which includes +examples and documentation. + +The source is as close as possible to the original, to make it simple to +update when a new version of UnRAR comes out. In many places the +original names and object nesting are kept, even though it's a bit +harder to follow. See rar.hpp for the main "glue". + +Website: http://www.slack.net/~ant/ +E-mail : Shay Green + + +Contents +-------- +* Diff-friendly changes +* Removal of features +* Error reporting changes +* Minor tweaks +* Unrar findings + + +Diff-friendly changes +--------------------- +To make my source code changes more easily visible with a line-based +file diff, I've tried to make changes by inserting or deleting lines, +rather than modifying them. So if the original declared a static array + + static int array [4] = { 1, 2, 3, 4 }; + +and I want to make it const, I add the const on a line before + + const // added + static int array [4] = { 1, 2, 3, 4 }; + +rather than on the same line + + static const int array [4] = { 1, 2, 3, 4 }; + +This way a diff will simply show an added line, making it clear what was +added. If I simply inserted const on the same line, it wouldn't be as +clear what all I had changed. + +I've also made use of several macros rather than changing the source +text. For example, since a class name like Unpack might easily conflict, +I've renamed it to Rar_Unpack by using #define Unpack Rar_Unpack rather +than changing the source text. These macros are only defined when +compiling the library sources; the user-visible unrar.h is very clean. + + +Removal of features +------------------- +This library is meant for simple access to common archives without +having to extract them first. Encryption, segmentation, huge files, and +self-extracting archives aren't common for things that need to be +accessed in this manner, so I've removed support for them. Also, +encryption adds complexity to the code that must be maintained. +Segmentation would require a way to specify the other segments. + + +Error reporting changes +----------------------- +The original used C++ exceptions to report errors. I've eliminated use +of these through a combination of error codes and longjmp. This allows +use of the library from C or some other language which doesn't easily +support exceptions. + +I tried to make as few changes as possible in the conversion. Due to the +number of places file reads occur, propagating an error code via return +statements would have required too many code changes. Instead, I perform +the read, save the error code, and return 0 bytes read in case of an +error. I also ensure that the calling code interprets this zero in an +acceptable way. I then check this saved error code after the operation +completes, and have it take priority over the error the RAR code +returned. I do a similar thing for write errors. + + +Minor tweaks +------------ +- Eliminated as many GCC warnings as reasonably possible. + +- Non-class array allocations now use malloc(), allowing the code to be +linked without the standard C++ library (particularly, operator new). +Class object allocations use a class-specific allocator that just calls +malloc(), also avoiding calls to operator new. + +- Made all unchanging static data const. Several pieces of static data +in the original code weren't marked const where they could be. + +- Initialization of some static tables was done on an as-needed basis, +creating a problem when extracting from archives in multiple threads. +This initialization can now be done by the user before any archives are +opened. + +- Tweaked CopyString, the major bottleneck during compression. I inlined +it, cached some variables in locals in case the compiler couldn't easily +see that the memory accesses don't modify them, and made them use +memcpy() where possible. This improved performance by at least 20% on +x86. Perhaps it won't work as well on files with lots of smaller string +matches. + +- Some .cpp files are #included by others. I've added guards to these so +that you can simply compile all .cpp files and not get any redefinition +errors. + +- The current solid extraction position is kept track of, allowing the +user to randomly extract files without regard to proper extraction +order. The library optimizes solid extraction and only restarts it if +the user is extracting a file earlier in the archive than the last +solid-extracted one. + +- Most of the time a solid file's data is already contiguously in the +internal Unpack::Window, which unrar_extract_mem() takes advantage of. +This avoids extra allocation in many cases. + +- Allocation of Unpack is delayed until the first extraction, rather +than being allocated immediately on opening the archive. This allows +scanning with minimal memory usage. + + +Unrar findings +-------------- +- Apparently the LHD_SOLID flag indicates that file depends on previous +files, rather than that later files depend on the current file's +contents. Thus this flag can't be used to intelligently decide which +files need to be internally extracted when skipping them, making it +necessary to internally extract every file before the one to be +extracted, if the archive is solid. + +-- +Shay Green diff --git a/snesreader/unrar/coder.cpp b/snesreader/unrar/coder.cpp new file mode 100644 index 00000000..c3f3aac6 --- /dev/null +++ b/snesreader/unrar/coder.cpp @@ -0,0 +1,49 @@ +// #included by unpack.cpp +#ifdef RAR_COMMON_HPP + +inline unsigned int RangeCoder::GetChar() +{ + return(UnpackRead->GetChar()); +} + + +void RangeCoder::InitDecoder(Unpack *UnpackRead) +{ + RangeCoder::UnpackRead=UnpackRead; + + low=code=0; + range=uint(-1); + for (int i=0;i < 4;i++) + code=(code << 8) | GetChar(); +} + + +#define ARI_DEC_NORMALIZE(code,low,range,read) \ +{ \ + while ((low^(low+range))GetChar(); \ + range <<= 8; \ + low <<= 8; \ + } \ +} + + +inline int RangeCoder::GetCurrentCount() +{ + return (code-low)/(range /= SubRange.scale); +} + + +inline uint RangeCoder::GetCurrentShiftCount(uint SHIFT) +{ + return (code-low)/(range >>= SHIFT); +} + + +inline void RangeCoder::Decode() +{ + low += range*SubRange.LowCount; + range *= SubRange.HighCount-SubRange.LowCount; +} +#endif diff --git a/snesreader/unrar/coder.hpp b/snesreader/unrar/coder.hpp new file mode 100644 index 00000000..8384cdc6 --- /dev/null +++ b/snesreader/unrar/coder.hpp @@ -0,0 +1,24 @@ +/**************************************************************************** + * Contents: 'Carryless rangecoder' by Dmitry Subbotin * + ****************************************************************************/ + +const uint TOP=1 << 24, BOT=1 << 15; + +class RangeCoder +{ +public: + void InitDecoder(Unpack *UnpackRead); + inline int GetCurrentCount(); + inline uint GetCurrentShiftCount(uint SHIFT); + inline void Decode(); + inline void PutChar(unsigned int c); + inline unsigned int GetChar(); + + uint low, code, range; + struct SUBRANGE + { + uint LowCount, HighCount, scale; + } SubRange; + + Unpack *UnpackRead; +}; diff --git a/snesreader/unrar/compress.hpp b/snesreader/unrar/compress.hpp new file mode 100644 index 00000000..3181e45d --- /dev/null +++ b/snesreader/unrar/compress.hpp @@ -0,0 +1,36 @@ +#ifndef _RAR_COMPRESS_ +#define _RAR_COMPRESS_ + +class ComprDataIO; +class PackingFileTable; + +#define CODEBUFSIZE 0x4000 +#define MAXWINSIZE 0x400000 +#define MAXWINMASK (MAXWINSIZE-1) + +#define LOW_DIST_REP_COUNT 16 + +#define NC 299 /* alphabet = {0, 1, 2, ..., NC - 1} */ +#define DC 60 +#define LDC 17 +#define RC 28 +#define HUFF_TABLE_SIZE (NC+DC+RC+LDC) +#define BC 20 + +#define NC20 298 /* alphabet = {0, 1, 2, ..., NC - 1} */ +#define DC20 48 +#define RC20 28 +#define BC20 19 +#define MC20 257 + +enum {CODE_HUFFMAN,CODE_LZ,CODE_LZ2,CODE_REPEATLZ,CODE_CACHELZ, + CODE_STARTFILE,CODE_ENDFILE,CODE_VM,CODE_VMDATA}; + + +enum FilterType { + FILTER_NONE, FILTER_PPM /*dummy*/, FILTER_E8, FILTER_E8E9, + FILTER_UPCASETOLOW, FILTER_AUDIO, FILTER_RGB, FILTER_DELTA, + FILTER_ITANIUM, FILTER_E8E9V2 +}; + +#endif diff --git a/snesreader/unrar/crc.cpp b/snesreader/unrar/crc.cpp new file mode 100644 index 00000000..bc23b5a9 --- /dev/null +++ b/snesreader/unrar/crc.cpp @@ -0,0 +1,69 @@ +#include "rar.hpp" + +uint CRCTab[256]; + +void InitCRC() +{ + for (int I=0;I<256;I++) + { + uint C=I; + for (int J=0;J<8;J++) + C=(C & 1) ? (C>>1)^0xEDB88320L : (C>>1); + CRCTab[I]=C; + } +} + + +uint CRC(uint StartCRC,const void *Addr,size_t Size) +{ + // Always initialized ahead of time, and this func call makes it a non-leaf func. + if (false) + if (CRCTab[1]==0) + InitCRC(); + byte *Data=(byte *)Addr; +#if defined(LITTLE_ENDIAN) && defined(PRESENT_INT32) && defined(ALLOW_NOT_ALIGNED_INT) + +#ifdef _MSC_VER + // avoid a warning about 'Data' pointer truncation in 64 bit mode + #pragma warning( disable : 4311 ) +#endif + + while (Size>0 && ((long)Data & 7)) + { + StartCRC=CRCTab[(byte)(StartCRC^Data[0])]^(StartCRC>>8); + Size--; + Data++; + } + while (Size>=8) + { + StartCRC^=*(uint32 *)Data; + StartCRC=CRCTab[(byte)StartCRC]^(StartCRC>>8); + StartCRC=CRCTab[(byte)StartCRC]^(StartCRC>>8); + StartCRC=CRCTab[(byte)StartCRC]^(StartCRC>>8); + StartCRC=CRCTab[(byte)StartCRC]^(StartCRC>>8); + StartCRC^=*(uint32 *)(Data+4); + StartCRC=CRCTab[(byte)StartCRC]^(StartCRC>>8); + StartCRC=CRCTab[(byte)StartCRC]^(StartCRC>>8); + StartCRC=CRCTab[(byte)StartCRC]^(StartCRC>>8); + StartCRC=CRCTab[(byte)StartCRC]^(StartCRC>>8); + Data+=8; + Size-=8; + } +#endif + for (size_t I=0;I>8); + return(StartCRC); +} + +#ifndef SFX_MODULE +ushort OldCRC(ushort StartCRC,const void *Addr,size_t Size) +{ + byte *Data=(byte *)Addr; + for (size_t I=0;I>15))&0xffff; + } + return(StartCRC); +} +#endif diff --git a/snesreader/unrar/encname.cpp b/snesreader/unrar/encname.cpp new file mode 100644 index 00000000..6f57cd91 --- /dev/null +++ b/snesreader/unrar/encname.cpp @@ -0,0 +1,57 @@ +#include "rar.hpp" + +EncodeFileName::EncodeFileName() +{ + Flags=0; + FlagBits=0; + FlagsPos=0; + DestSize=0; +} + + + + +void EncodeFileName::Decode(char *Name,byte *EncName,int EncSize,wchar *NameW, + int MaxDecSize) +{ + int EncPos=0,DecPos=0; + byte HighByte=EncName[EncPos++]; + while (EncPos>6) + { + case 0: + NameW[DecPos++]=EncName[EncPos++]; + break; + case 1: + NameW[DecPos++]=EncName[EncPos++]+(HighByte<<8); + break; + case 2: + NameW[DecPos++]=EncName[EncPos]+(EncName[EncPos+1]<<8); + EncPos+=2; + break; + case 3: + { + int Length=EncName[EncPos++]; + if (Length & 0x80) + { + byte Correction=EncName[EncPos++]; + for (Length=(Length&0x7f)+2;Length>0 && DecPos0 && DecPos +#include "rar.hpp" + +#include "unrar.h" + +#define DataIO Arc + +unrar_err_t CmdExtract::ExtractCurrentFile( bool SkipSolid, bool check_compatibility_only ) +{ + check( Arc.GetHeaderType() == FILE_HEAD ); + + if ( Arc.NewLhd.Flags & (LHD_SPLIT_AFTER | LHD_SPLIT_BEFORE) ) + return unrar_err_segmented; + + if ( Arc.NewLhd.Flags & LHD_PASSWORD ) + return unrar_err_encrypted; + + if ( !check_compatibility_only ) + { + check( Arc.NextBlockPos-Arc.NewLhd.FullPackSize == Arc.Tell() ); + Arc.Seek(Arc.NextBlockPos-Arc.NewLhd.FullPackSize,SEEK_SET); + } + + // (removed lots of command-line handling) + +#ifdef SFX_MODULE + if ((Arc.NewLhd.UnpVer!=UNP_VER && Arc.NewLhd.UnpVer!=29) && + Arc.NewLhd.Method!=0x30) +#else + if (Arc.NewLhd.UnpVer<13 || Arc.NewLhd.UnpVer>UNP_VER) +#endif + { + if (Arc.NewLhd.UnpVer>UNP_VER) + return unrar_err_new_algo; + return unrar_err_old_algo; + } + + if ( check_compatibility_only ) + return unrar_ok; + + // (removed lots of command-line/encryption/volume handling) + + update_first_file_pos(); + FileCount++; + DataIO.UnpFileCRC=Arc.OldFormat ? 0 : 0xffffffff; + // (removed decryption) + DataIO.SetPackedSizeToRead(Arc.NewLhd.FullPackSize); + // (removed command-line handling) + DataIO.SetSkipUnpCRC(SkipSolid); + + if (Arc.NewLhd.Method==0x30) + UnstoreFile(Arc.NewLhd.FullUnpSize); + else + { + // Defer creation of Unpack until first extraction + if ( !Unp ) + { + Unp = new Unpack( &Arc ); + if ( !Unp ) + return unrar_err_memory; + + Unp->Init( NULL ); + } + + Unp->SetDestSize(Arc.NewLhd.FullUnpSize); +#ifndef SFX_MODULE + if (Arc.NewLhd.UnpVer<=15) + Unp->DoUnpack(15,FileCount>1 && Arc.Solid); + else +#endif + Unp->DoUnpack(Arc.NewLhd.UnpVer,Arc.NewLhd.Flags & LHD_SOLID); + } + + // (no need to seek to next file) + + if (!SkipSolid) + { + if (Arc.OldFormat && UINT32(DataIO.UnpFileCRC)==UINT32(Arc.NewLhd.FileCRC) || + !Arc.OldFormat && UINT32(DataIO.UnpFileCRC)==UINT32(Arc.NewLhd.FileCRC^0xffffffff)) + { + // CRC is correct + } + else + { + return unrar_err_corrupt; + } + } + + // (removed broken file handling) + // (removed command-line handling) + + return unrar_ok; +} + + +void CmdExtract::UnstoreFile(Int64 DestUnpSize) +{ + Buffer.Alloc(Min(DestUnpSize,0x10000)); + while (1) + { + unsigned int Code=DataIO.UnpRead(&Buffer[0],Buffer.Size()); + if (Code==0 || (int)Code==-1) + break; + Code=Code=0) + DestUnpSize-=Code; + } + Buffer.Reset(); +} diff --git a/snesreader/unrar/getbits.cpp b/snesreader/unrar/getbits.cpp new file mode 100644 index 00000000..559bdd03 --- /dev/null +++ b/snesreader/unrar/getbits.cpp @@ -0,0 +1,34 @@ +#include "rar.hpp" + +BitInput::BitInput() +{ + InBuf = (byte*) rarmalloc( MAX_SIZE ); + + // Otherwise getbits() reads uninitialized memory + // TODO: instead of clearing entire block, just clear last two + // bytes after reading from file + if ( InBuf ) + memset( InBuf, 0, MAX_SIZE ); +} + +BitInput::~BitInput() +{ + rarfree( InBuf ); +} + +void BitInput::handle_mem_error( Rar_Error_Handler& ErrHandler ) +{ + if ( !InBuf ) + ErrHandler.MemoryError(); +} + +void BitInput::faddbits(int Bits) +{ + addbits(Bits); +} + + +unsigned int BitInput::fgetbits() +{ + return(getbits()); +} diff --git a/snesreader/unrar/getbits.hpp b/snesreader/unrar/getbits.hpp new file mode 100644 index 00000000..5a4cb4a3 --- /dev/null +++ b/snesreader/unrar/getbits.hpp @@ -0,0 +1,40 @@ +#ifndef _RAR_GETBITS_ +#define _RAR_GETBITS_ + +class BitInput + : public Rar_Allocator +{ +public: + enum BufferSize {MAX_SIZE=0x8000}; +protected: + int InAddr,InBit; +public: + BitInput(); + ~BitInput(); + void handle_mem_error( Rar_Error_Handler& ); + + byte *InBuf; + + void InitBitInput() + { + InAddr=InBit=0; + } + void addbits(int Bits) + { + Bits+=InBit; + InAddr+=Bits>>3; + InBit=Bits&7; + } + unsigned int getbits() + { + unsigned int BitField=(uint)InBuf[InAddr] << 16; + BitField|=(uint)InBuf[InAddr+1] << 8; + BitField|=(uint)InBuf[InAddr+2]; + BitField >>= (8-InBit); + return(BitField & 0xffff); + } + void faddbits(int Bits); + unsigned int fgetbits(); + bool Overflow(int IncPtr) {return(InAddr+IncPtr>=MAX_SIZE);} +}; +#endif diff --git a/snesreader/unrar/headers.hpp b/snesreader/unrar/headers.hpp new file mode 100644 index 00000000..abe1c66a --- /dev/null +++ b/snesreader/unrar/headers.hpp @@ -0,0 +1,145 @@ +#ifndef _RAR_HEADERS_ +#define _RAR_HEADERS_ + +#define SIZEOF_MARKHEAD 7 +#define SIZEOF_OLDMHD 7 +#define SIZEOF_NEWMHD 13 +#define SIZEOF_OLDLHD 21 +#define SIZEOF_NEWLHD 32 +#define SIZEOF_SHORTBLOCKHEAD 7 +#define SIZEOF_SUBBLOCKHEAD 14 +#define SIZEOF_COMMHEAD 13 + +#define UNP_VER 36 + +#define MHD_VOLUME 0x0001 +#define MHD_COMMENT 0x0002 +#define MHD_SOLID 0x0008 +#define MHD_PASSWORD 0x0080 + +#define LHD_SPLIT_BEFORE 0x0001 +#define LHD_SPLIT_AFTER 0x0002 +#define LHD_PASSWORD 0x0004 +#define LHD_COMMENT 0x0008 +#define LHD_SOLID 0x0010 + +#define LHD_WINDOWMASK 0x00e0 +#define LHD_DIRECTORY 0x00e0 + +#define LHD_LARGE 0x0100 +#define LHD_UNICODE 0x0200 +#define LHD_SALT 0x0400 +#define LHD_EXTTIME 0x1000 + +#define LONG_BLOCK 0x8000 + +enum HEADER_TYPE { + MARK_HEAD=0x72,MAIN_HEAD=0x73,FILE_HEAD=0x74,COMM_HEAD=0x75,AV_HEAD=0x76, + SUB_HEAD=0x77,PROTECT_HEAD=0x78,SIGN_HEAD=0x79,NEWSUB_HEAD=0x7a, + ENDARC_HEAD=0x7b +}; + +enum HOST_SYSTEM { + HOST_MSDOS=0,HOST_OS2=1,HOST_WIN32=2,HOST_UNIX=3,HOST_MACOS=4, + HOST_BEOS=5,HOST_MAX +}; + +struct OldMainHeader +{ + byte Mark[4]; + ushort HeadSize; + byte Flags; +}; + + +struct OldFileHeader +{ + uint PackSize; + uint UnpSize; + ushort FileCRC; + ushort HeadSize; + uint FileTime; + byte FileAttr; + byte Flags; + byte UnpVer; + byte NameSize; + byte Method; +}; + + +struct MarkHeader +{ + byte Mark[7]; +}; + + +struct BaseBlock +{ + ushort HeadCRC; + HEADER_TYPE HeadType;//byte + ushort Flags; + ushort HeadSize; +}; + +struct BlockHeader:BaseBlock +{ + union { + uint DataSize; + uint PackSize; + }; +}; + + +struct MainHeader:BaseBlock +{ + ushort HighPosAV; + uint PosAV; +}; + +#define SALT_SIZE 8 + +struct FileHeader:BlockHeader +{ + uint UnpSize; + byte HostOS; + uint FileCRC; + uint FileTime; + byte UnpVer; + byte Method; + ushort NameSize; + union { + uint FileAttr; + uint SubFlags; + }; +/* optional */ + uint HighPackSize; + uint HighUnpSize; +/* names */ + char FileName[NM*4]; // *4 to avoid using lots of stack in arcread + wchar FileNameW[NM]; +/* optional */ + byte Salt[SALT_SIZE]; + + RarTime mtime; +/* dummy */ + Int64 FullPackSize; + Int64 FullUnpSize; +}; + +// SubBlockHeader and its successors were used in RAR 2.x format. +// RAR 3.x uses FileHeader with NEWSUB_HEAD HeadType for subblocks. +struct SubBlockHeader:BlockHeader +{ + ushort SubType; + byte Level; +}; + +struct ProtectHeader:BlockHeader +{ + byte Version; + ushort RecSectors; + uint TotalBlocks; + byte Mark[8]; +}; + +#endif diff --git a/snesreader/unrar/license.txt b/snesreader/unrar/license.txt new file mode 100644 index 00000000..2aa475c7 --- /dev/null +++ b/snesreader/unrar/license.txt @@ -0,0 +1,40 @@ + ****** ***** ****** UnRAR - free utility for RAR archives + ** ** ** ** ** ** ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ****** ******* ****** License for use and distribution of + ** ** ** ** ** ** ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + ** ** ** ** ** ** FREE portable version + ~~~~~~~~~~~~~~~~~~~~~ + + The source code of UnRAR utility is freeware. This means: + + 1. All copyrights to RAR and the utility UnRAR are exclusively + owned by the author - Alexander Roshal. + + 2. The UnRAR sources may be used in any software to handle RAR + archives without limitations free of charge, but cannot be used + to re-create the RAR compression algorithm, which is proprietary. + Distribution of modified UnRAR sources in separate form or as a + part of other software is permitted, provided that it is clearly + stated in the documentation and source comments that the code may + not be used to develop a RAR (WinRAR) compatible archiver. + + 3. The UnRAR utility may be freely distributed. It is allowed + to distribute UnRAR inside of other software packages. + + 4. THE RAR ARCHIVER AND THE UnRAR UTILITY ARE DISTRIBUTED "AS IS". + NO WARRANTY OF ANY KIND IS EXPRESSED OR IMPLIED. YOU USE AT + YOUR OWN RISK. THE AUTHOR WILL NOT BE LIABLE FOR DATA LOSS, + DAMAGES, LOSS OF PROFITS OR ANY OTHER KIND OF LOSS WHILE USING + OR MISUSING THIS SOFTWARE. + + 5. Installing and using the UnRAR utility signifies acceptance of + these terms and conditions of the license. + + 6. If you don't agree with terms of the license you must remove + UnRAR files from your storage devices and cease to use the + utility. + + Thank you for your interest in RAR and UnRAR. + + + Alexander L. Roshal \ No newline at end of file diff --git a/snesreader/unrar/model.cpp b/snesreader/unrar/model.cpp new file mode 100644 index 00000000..68e1a4f1 --- /dev/null +++ b/snesreader/unrar/model.cpp @@ -0,0 +1,612 @@ +// #included by unpack.cpp +#ifdef RAR_COMMON_HPP +/**************************************************************************** + * This file is part of PPMd project * + * Written and distributed to public domain by Dmitry Shkarin 1997, * + * 1999-2000 * + * Contents: model description and encoding/decoding routines * + ****************************************************************************/ + +inline PPM_CONTEXT* PPM_CONTEXT::createChild(ModelPPM *Model,STATE* pStats, + STATE& FirstState) +{ + PPM_CONTEXT* pc = (PPM_CONTEXT*) Model->SubAlloc.AllocContext(); + if ( pc ) + { + pc->NumStats=1; + pc->OneState=FirstState; + pc->Suffix=this; + pStats->Successor=pc; + } + return pc; +} + + +ModelPPM::ModelPPM() +{ + MinContext=NULL; + MaxContext=NULL; + MedContext=NULL; +} + + +void ModelPPM::RestartModelRare() +{ + int i, k, m; + memset(CharMask,0,sizeof(CharMask)); + SubAlloc.InitSubAllocator(); + InitRL=-(MaxOrder < 12 ? MaxOrder:12)-1; + MinContext = MaxContext = (PPM_CONTEXT*) SubAlloc.AllocContext(); + MinContext->Suffix=NULL; + OrderFall=MaxOrder; + MinContext->U.SummFreq=(MinContext->NumStats=256)+1; + FoundState=MinContext->U.Stats=(STATE*)SubAlloc.AllocUnits(256/2); + for (RunLength=InitRL, PrevSuccess=i=0;i < 256;i++) + { + MinContext->U.Stats[i].Symbol=i; + MinContext->U.Stats[i].Freq=1; + MinContext->U.Stats[i].Successor=NULL; + } + + static const ushort InitBinEsc[]={ + 0x3CDD,0x1F3F,0x59BF,0x48F3,0x64A1,0x5ABC,0x6632,0x6051 + }; + + for (i=0;i < 128;i++) + for (k=0;k < 8;k++) + for (m=0;m < 64;m += 8) + BinSumm[i][k+m]=BIN_SCALE-InitBinEsc[k]/(i+2); + for (i=0;i < 25;i++) + for (k=0;k < 16;k++) + SEE2Cont[i][k].init(5*i+10); +} + + +void ModelPPM::StartModelRare(int MaxOrder) +{ + int i, k, m ,Step; + EscCount=1; +/* + if (MaxOrder < 2) + { + memset(CharMask,0,sizeof(CharMask)); + OrderFall=ModelPPM::MaxOrder; + MinContext=MaxContext; + while (MinContext->Suffix != NULL) + { + MinContext=MinContext->Suffix; + OrderFall--; + } + FoundState=MinContext->U.Stats; + MinContext=MaxContext; + } + else +*/ + { + ModelPPM::MaxOrder=MaxOrder; + RestartModelRare(); + NS2BSIndx[0]=2*0; + NS2BSIndx[1]=2*1; + memset(NS2BSIndx+2,2*2,9); + memset(NS2BSIndx+11,2*3,256-11); + for (i=0;i < 3;i++) + NS2Indx[i]=i; + for (m=i, k=Step=1;i < 256;i++) + { + NS2Indx[i]=m; + if ( !--k ) + { + k = ++Step; + m++; + } + } + memset(HB2Flag,0,0x40); + memset(HB2Flag+0x40,0x08,0x100-0x40); + DummySEE2Cont.Shift=PERIOD_BITS; + } +} + + +void PPM_CONTEXT::rescale(ModelPPM *Model) +{ + int OldNS=NumStats, i=NumStats-1, Adder, EscFreq; + STATE* p1, * p; + for (p=Model->FoundState;p != U.Stats;p--) + _PPMD_SWAP(p[0],p[-1]); + U.Stats->Freq += 4; + U.SummFreq += 4; + EscFreq=U.SummFreq-p->Freq; + Adder=(Model->OrderFall != 0); + U.SummFreq = (p->Freq=(p->Freq+Adder) >> 1); + do + { + EscFreq -= (++p)->Freq; + U.SummFreq += (p->Freq=(p->Freq+Adder) >> 1); + if (p[0].Freq > p[-1].Freq) + { + STATE tmp=*(p1=p); + do + { + p1[0]=p1[-1]; + } while (--p1 != U.Stats && tmp.Freq > p1[-1].Freq); + *p1=tmp; + } + } while ( --i ); + if (p->Freq == 0) + { + do + { + i++; + } while ((--p)->Freq == 0); + EscFreq += i; + if ((NumStats -= i) == 1) + { + STATE tmp=*U.Stats; + do + { + tmp.Freq-=(tmp.Freq >> 1); + EscFreq>>=1; + } while (EscFreq > 1); + Model->SubAlloc.FreeUnits(U.Stats,(OldNS+1) >> 1); + *(Model->FoundState=&OneState)=tmp; return; + } + } + U.SummFreq += (EscFreq -= (EscFreq >> 1)); + int n0=(OldNS+1) >> 1, n1=(NumStats+1) >> 1; + if (n0 != n1) + U.Stats = (STATE*) Model->SubAlloc.ShrinkUnits(U.Stats,n0,n1); + Model->FoundState=U.Stats; +} + + +inline PPM_CONTEXT* ModelPPM::CreateSuccessors(bool Skip,STATE* p1) +{ + // (removed conditional static) + STATE UpState; + PPM_CONTEXT* pc=MinContext, * UpBranch=FoundState->Successor; + STATE * p, * ps[MAX_O], ** pps=ps; + if ( !Skip ) + { + *pps++ = FoundState; + if ( !pc->Suffix ) + goto NO_LOOP; + } + if ( p1 ) + { + p=p1; + pc=pc->Suffix; + goto LOOP_ENTRY; + } + do + { + pc=pc->Suffix; + if (pc->NumStats != 1) + { + if ((p=pc->U.Stats)->Symbol != FoundState->Symbol) + do + { + p++; + } while (p->Symbol != FoundState->Symbol); + } + else + p=&(pc->OneState); +LOOP_ENTRY: + if (p->Successor != UpBranch) + { + pc=p->Successor; + break; + } + *pps++ = p; + } while ( pc->Suffix ); +NO_LOOP: + if (pps == ps) + return pc; + UpState.Symbol=*(byte*) UpBranch; + UpState.Successor=(PPM_CONTEXT*) (((byte*) UpBranch)+1); + if (pc->NumStats != 1) + { + if ((byte*) pc <= SubAlloc.pText) + return(NULL); + if ((p=pc->U.Stats)->Symbol != UpState.Symbol) + do + { + p++; + } while (p->Symbol != UpState.Symbol); + uint cf=p->Freq-1; + uint s0=pc->U.SummFreq-pc->NumStats-cf; + UpState.Freq=1+((2*cf <= s0)?(5*cf > s0):((2*cf+3*s0-1)/(2*s0))); + } + else + UpState.Freq=pc->OneState.Freq; + do + { + pc = pc->createChild(this,*--pps,UpState); + if ( !pc ) + return NULL; + } while (pps != ps); + return pc; +} + + +inline void ModelPPM::UpdateModel() +{ + STATE fs = *FoundState, *p = NULL; + PPM_CONTEXT *pc, *Successor; + uint ns1, ns, cf, sf, s0; + if (fs.Freq < MAX_FREQ/4 && (pc=MinContext->Suffix) != NULL) + { + if (pc->NumStats != 1) + { + if ((p=pc->U.Stats)->Symbol != fs.Symbol) + { + do + { + p++; + } while (p->Symbol != fs.Symbol); + if (p[0].Freq >= p[-1].Freq) + { + _PPMD_SWAP(p[0],p[-1]); + p--; + } + } + if (p->Freq < MAX_FREQ-9) + { + p->Freq += 2; + pc->U.SummFreq += 2; + } + } + else + { + p=&(pc->OneState); + p->Freq += (p->Freq < 32); + } + } + if ( !OrderFall ) + { + MinContext=MaxContext=FoundState->Successor=CreateSuccessors(true,p); + if ( !MinContext ) + goto RESTART_MODEL; + return; + } + *SubAlloc.pText++ = fs.Symbol; + Successor = (PPM_CONTEXT*) SubAlloc.pText; + if (SubAlloc.pText >= SubAlloc.FakeUnitsStart) + goto RESTART_MODEL; + if ( fs.Successor ) + { + if ((byte*) fs.Successor <= SubAlloc.pText && + (fs.Successor=CreateSuccessors(false,p)) == NULL) + goto RESTART_MODEL; + if ( !--OrderFall ) + { + Successor=fs.Successor; + SubAlloc.pText -= (MaxContext != MinContext); + } + } + else + { + FoundState->Successor=Successor; + fs.Successor=MinContext; + } + s0=MinContext->U.SummFreq-(ns=MinContext->NumStats)-(fs.Freq-1); + for (pc=MaxContext;pc != MinContext;pc=pc->Suffix) + { + if ((ns1=pc->NumStats) != 1) + { + if ((ns1 & 1) == 0) + { + pc->U.Stats=(STATE*) SubAlloc.ExpandUnits(pc->U.Stats,ns1 >> 1); + if ( !pc->U.Stats ) + goto RESTART_MODEL; + } + pc->U.SummFreq += (2*ns1 < ns)+2*((4*ns1 <= ns) & (pc->U.SummFreq <= 8*ns1)); + } + else + { + p=(STATE*) SubAlloc.AllocUnits(1); + if ( !p ) + goto RESTART_MODEL; + *p=pc->OneState; + pc->U.Stats=p; + if (p->Freq < MAX_FREQ/4-1) + p->Freq += p->Freq; + else + p->Freq = MAX_FREQ-4; + pc->U.SummFreq=p->Freq+InitEsc+(ns > 3); + } + cf=2*fs.Freq*(pc->U.SummFreq+6); + sf=s0+pc->U.SummFreq; + if (cf < 6*sf) + { + cf=1+(cf > sf)+(cf >= 4*sf); + pc->U.SummFreq += 3; + } + else + { + cf=4+(cf >= 9*sf)+(cf >= 12*sf)+(cf >= 15*sf); + pc->U.SummFreq += cf; + } + p=pc->U.Stats+ns1; + p->Successor=Successor; + p->Symbol = fs.Symbol; + p->Freq = cf; + pc->NumStats=++ns1; + } + MaxContext=MinContext=fs.Successor; + return; +RESTART_MODEL: + RestartModelRare(); + EscCount=0; +} + + +// Tabulated escapes for exponential symbol distribution +static const byte ExpEscape[16]={ 25,14, 9, 7, 5, 5, 4, 4, 4, 3, 3, 3, 2, 2, 2, 2 }; +#define GET_MEAN(SUMM,SHIFT,ROUND) ((SUMM+(1 << (SHIFT-ROUND))) >> (SHIFT)) + + + +inline void PPM_CONTEXT::decodeBinSymbol(ModelPPM *Model) +{ + STATE& rs=OneState; + Model->HiBitsFlag=Model->HB2Flag[Model->FoundState->Symbol]; + ushort& bs=Model->BinSumm[rs.Freq-1][Model->PrevSuccess+ + Model->NS2BSIndx[Suffix->NumStats-1]+ + Model->HiBitsFlag+2*Model->HB2Flag[rs.Symbol]+ + ((Model->RunLength >> 26) & 0x20)]; + if (Model->Coder.GetCurrentShiftCount(TOT_BITS) < bs) + { + Model->FoundState=&rs; + rs.Freq += (rs.Freq < 128); + Model->Coder.SubRange.LowCount=0; + Model->Coder.SubRange.HighCount=bs; + bs = SHORT16(bs+INTERVAL-GET_MEAN(bs,PERIOD_BITS,2)); + Model->PrevSuccess=1; + Model->RunLength++; + } + else + { + Model->Coder.SubRange.LowCount=bs; + bs = SHORT16(bs-GET_MEAN(bs,PERIOD_BITS,2)); + Model->Coder.SubRange.HighCount=BIN_SCALE; + Model->InitEsc=ExpEscape[bs >> 10]; + Model->NumMasked=1; + Model->CharMask[rs.Symbol]=Model->EscCount; + Model->PrevSuccess=0; + Model->FoundState=NULL; + } +} + + +inline void PPM_CONTEXT::update1(ModelPPM *Model,STATE* p) +{ + (Model->FoundState=p)->Freq += 4; + U.SummFreq += 4; + if (p[0].Freq > p[-1].Freq) + { + _PPMD_SWAP(p[0],p[-1]); + Model->FoundState=--p; + if (p->Freq > MAX_FREQ) + rescale(Model); + } +} + + + + +inline bool PPM_CONTEXT::decodeSymbol1(ModelPPM *Model) +{ + Model->Coder.SubRange.scale=U.SummFreq; + STATE* p=U.Stats; + int i, HiCnt; + int count=Model->Coder.GetCurrentCount(); + if (count>=Model->Coder.SubRange.scale) + return(false); + if (count < (HiCnt=p->Freq)) + { + Model->PrevSuccess=(2*(Model->Coder.SubRange.HighCount=HiCnt) > Model->Coder.SubRange.scale); + Model->RunLength += Model->PrevSuccess; + (Model->FoundState=p)->Freq=(HiCnt += 4); + U.SummFreq += 4; + if (HiCnt > MAX_FREQ) + rescale(Model); + Model->Coder.SubRange.LowCount=0; + return(true); + } + else + if (Model->FoundState==NULL) + return(false); + Model->PrevSuccess=0; + i=NumStats-1; + while ((HiCnt += (++p)->Freq) <= count) + if (--i == 0) + { + Model->HiBitsFlag=Model->HB2Flag[Model->FoundState->Symbol]; + Model->Coder.SubRange.LowCount=HiCnt; + Model->CharMask[p->Symbol]=Model->EscCount; + i=(Model->NumMasked=NumStats)-1; + Model->FoundState=NULL; + do + { + Model->CharMask[(--p)->Symbol]=Model->EscCount; + } while ( --i ); + Model->Coder.SubRange.HighCount=Model->Coder.SubRange.scale; + return(true); + } + Model->Coder.SubRange.LowCount=(Model->Coder.SubRange.HighCount=HiCnt)-p->Freq; + update1(Model,p); + return(true); +} + + +inline void PPM_CONTEXT::update2(ModelPPM *Model,STATE* p) +{ + (Model->FoundState=p)->Freq += 4; + U.SummFreq += 4; + if (p->Freq > MAX_FREQ) + rescale(Model); + Model->EscCount++; + Model->RunLength=Model->InitRL; +} + + +inline SEE2_CONTEXT* PPM_CONTEXT::makeEscFreq2(ModelPPM *Model,int Diff) +{ + SEE2_CONTEXT* psee2c; + if (NumStats != 256) + { + psee2c=Model->SEE2Cont[Model->NS2Indx[Diff-1]]+ + (Diff < Suffix->NumStats-NumStats)+ + 2*(U.SummFreq < 11*NumStats)+4*(Model->NumMasked > Diff)+ + Model->HiBitsFlag; + Model->Coder.SubRange.scale=psee2c->getMean(); + } + else + { + psee2c=&Model->DummySEE2Cont; + Model->Coder.SubRange.scale=1; + } + return psee2c; +} + + + + +inline bool PPM_CONTEXT::decodeSymbol2(ModelPPM *Model) +{ + int count, HiCnt, i=NumStats-Model->NumMasked; + SEE2_CONTEXT* psee2c=makeEscFreq2(Model,i); + STATE* ps[256], ** pps=ps, * p=U.Stats-1; + HiCnt=0; + do + { + do + { + p++; + } while (Model->CharMask[p->Symbol] == Model->EscCount); + HiCnt += p->Freq; + *pps++ = p; + } while ( --i ); + Model->Coder.SubRange.scale += HiCnt; + count=Model->Coder.GetCurrentCount(); + if (count>=Model->Coder.SubRange.scale) + return(false); + p=*(pps=ps); + if (count < HiCnt) + { + HiCnt=0; + while ((HiCnt += p->Freq) <= count) + p=*++pps; + Model->Coder.SubRange.LowCount = (Model->Coder.SubRange.HighCount=HiCnt)-p->Freq; + psee2c->update(); + update2(Model,p); + } + else + { + Model->Coder.SubRange.LowCount=HiCnt; + Model->Coder.SubRange.HighCount=Model->Coder.SubRange.scale; + i=NumStats-Model->NumMasked; + pps--; + do + { + Model->CharMask[(*++pps)->Symbol]=Model->EscCount; + } while ( --i ); + psee2c->Summ += Model->Coder.SubRange.scale; + Model->NumMasked = NumStats; + } + return(true); +} + + +inline void ModelPPM::ClearMask() +{ + EscCount=1; + memset(CharMask,0,sizeof(CharMask)); +} + + + + +// reset PPM variables after data error allowing safe resuming +// of further data processing +void ModelPPM::CleanUp() +{ + SubAlloc.StopSubAllocator(); + SubAlloc.StartSubAllocator(1); + StartModelRare(2); +} + + +bool ModelPPM::DecodeInit(Unpack *UnpackRead,int &EscChar) +{ + int MaxOrder=UnpackRead->GetChar(); + bool Reset=MaxOrder & 0x20; + + int MaxMB; + MaxMB = 0; // avoids warning of being uninitialized + if (Reset) + MaxMB=UnpackRead->GetChar(); + else + if (SubAlloc.GetAllocatedMemory()==0) + return(false); + if (MaxOrder & 0x40) + EscChar=UnpackRead->GetChar(); + Coder.InitDecoder(UnpackRead); + if (Reset) + { + MaxOrder=(MaxOrder & 0x1f)+1; + if (MaxOrder>16) + MaxOrder=16+(MaxOrder-16)*3; + if (MaxOrder==1) + { + SubAlloc.StopSubAllocator(); + return(false); + } + SubAlloc.StartSubAllocator(MaxMB+1); + StartModelRare(MaxOrder); + } + return(MinContext!=NULL); +} + + +int ModelPPM::DecodeChar() +{ + if ((byte*)MinContext <= SubAlloc.pText || (byte*)MinContext>SubAlloc.HeapEnd) + return(-1); + if (MinContext->NumStats != 1) + { + if ((byte*)MinContext->U.Stats <= SubAlloc.pText || (byte*)MinContext->U.Stats>SubAlloc.HeapEnd) + return(-1); + if (!MinContext->decodeSymbol1(this)) + return(-1); + } + else + MinContext->decodeBinSymbol(this); + Coder.Decode(); + while ( !FoundState ) + { + ARI_DEC_NORMALIZE(Coder.code,Coder.low,Coder.range,Coder.UnpackRead); + do + { + OrderFall++; + MinContext=MinContext->Suffix; + if ((byte*)MinContext <= SubAlloc.pText || (byte*)MinContext>SubAlloc.HeapEnd) + return(-1); + } while (MinContext->NumStats == NumMasked); + if (!MinContext->decodeSymbol2(this)) + return(-1); + Coder.Decode(); + } + int Symbol=FoundState->Symbol; + if (!OrderFall && (byte*) FoundState->Successor > SubAlloc.pText) + MinContext=MaxContext=FoundState->Successor; + else + { + UpdateModel(); + if (EscCount == 0) + ClearMask(); + } + ARI_DEC_NORMALIZE(Coder.code,Coder.low,Coder.range,Coder.UnpackRead); + return(Symbol); +} +#endif diff --git a/snesreader/unrar/model.hpp b/snesreader/unrar/model.hpp new file mode 100644 index 00000000..1ccf2f1d --- /dev/null +++ b/snesreader/unrar/model.hpp @@ -0,0 +1,133 @@ +#ifndef _RAR_PPMMODEL_ +#define _RAR_PPMMODEL_ + +#include "coder.hpp" +#include "suballoc.hpp" + +const int MAX_O=64; /* maximum allowed model order */ + +const int INT_BITS=7, PERIOD_BITS=7, TOT_BITS=INT_BITS+PERIOD_BITS, + INTERVAL=1 << INT_BITS, BIN_SCALE=1 << TOT_BITS, MAX_FREQ=124; + +#ifndef STRICT_ALIGNMENT_REQUIRED +#pragma pack(1) +#endif + +struct SEE2_CONTEXT +{ // SEE-contexts for PPM-contexts with masked symbols + ushort Summ; + byte Shift, Count; + void init(int InitVal) + { + Summ=InitVal << (Shift=PERIOD_BITS-4); + Count=4; + } + uint getMean() + { + uint RetVal=SHORT16(Summ) >> Shift; + Summ -= RetVal; + return RetVal+(RetVal == 0); + } + void update() + { + if (Shift < PERIOD_BITS && --Count == 0) + { + Summ += Summ; + Count=3 << Shift++; + } + } +}; + + +class ModelPPM; +struct PPM_CONTEXT; + +struct STATE +{ + byte Symbol; + byte Freq; + PPM_CONTEXT* Successor; +}; + +struct FreqData +{ + ushort SummFreq; + STATE _PACK_ATTR * Stats; +}; + +struct PPM_CONTEXT +{ + ushort NumStats; + union + { + FreqData U; + STATE OneState; + }; + + PPM_CONTEXT* Suffix; + inline void encodeBinSymbol(ModelPPM *Model,int symbol); // MaxOrder: + inline void encodeSymbol1(ModelPPM *Model,int symbol); // ABCD context + inline void encodeSymbol2(ModelPPM *Model,int symbol); // BCD suffix + inline void decodeBinSymbol(ModelPPM *Model); // BCDE successor + inline bool decodeSymbol1(ModelPPM *Model); // other orders: + inline bool decodeSymbol2(ModelPPM *Model); // BCD context + inline void update1(ModelPPM *Model,STATE* p); // CD suffix + inline void update2(ModelPPM *Model,STATE* p); // BCDE successor + void rescale(ModelPPM *Model); + inline PPM_CONTEXT* createChild(ModelPPM *Model,STATE* pStats,STATE& FirstState); + inline SEE2_CONTEXT* makeEscFreq2(ModelPPM *Model,int Diff); +}; + +#ifndef STRICT_ALIGNMENT_REQUIRED +#ifdef _AIX +#pragma pack(pop) +#else +#pragma pack() +#endif +#endif + +const uint UNIT_SIZE=Max(sizeof(PPM_CONTEXT),sizeof(RAR_MEM_BLK)); +const uint FIXED_UNIT_SIZE=12; + +/* +inline PPM_CONTEXT::PPM_CONTEXT(STATE* pStats,PPM_CONTEXT* ShorterContext): + NumStats(1), Suffix(ShorterContext) { pStats->Successor=this; } +inline PPM_CONTEXT::PPM_CONTEXT(): NumStats(0) {} +*/ + +template +inline void _PPMD_SWAP(T& t1,T& t2) { T tmp=t1; t1=t2; t2=tmp; } + + +class ModelPPM +{ + private: + friend struct PPM_CONTEXT; + + /*_PACK_ATTR*/ SEE2_CONTEXT SEE2Cont[25][16], DummySEE2Cont; + + struct PPM_CONTEXT *MinContext, *MedContext, *MaxContext; + STATE* FoundState; // found next state transition + int NumMasked, InitEsc, OrderFall, MaxOrder, RunLength, InitRL; + byte CharMask[256], NS2Indx[256], NS2BSIndx[256], HB2Flag[256]; + byte EscCount, PrevSuccess, HiBitsFlag; + ushort BinSumm[128][64]; // binary SEE-contexts + + RangeCoder Coder; + SubAllocator SubAlloc; + + void RestartModelRare(); + void StartModelRare(int MaxOrder); + inline PPM_CONTEXT* CreateSuccessors(bool Skip,STATE* p1); + + inline void UpdateModel(); + inline void ClearMask(); + friend class Unpack; + public: + ModelPPM(); + void CleanUp(); // reset PPM variables after data error + bool DecodeInit(Unpack *UnpackRead,int &EscChar); + int DecodeChar(); +}; + +#endif diff --git a/snesreader/unrar/rar.hpp b/snesreader/unrar/rar.hpp new file mode 100644 index 00000000..3302b2b2 --- /dev/null +++ b/snesreader/unrar/rar.hpp @@ -0,0 +1,209 @@ +// This source code is a heavily modified version based on the unrar package. +// It may NOT be used to develop a RAR (WinRAR) compatible archiver. +// See license.txt for copyright and licensing. + +// unrar_core 3.8.5 +#ifndef RAR_COMMON_HPP +#define RAR_COMMON_HPP + +#include "unrar.h" + +#include +#include +#include +#include + +//// Glue + +// One goal is to keep source code as close to original as possible, so +// that changes to the original can be found and merged more easily. + +// These names are too generic and might clash (or have already, hmpf) +#define Array Rar_Array +#define uint32 rar_uint32 +#define sint32 rar_sint32 +#define Unpack Rar_Unpack +#define Archive Rar_Archive +#define RawRead Rar_RawRead +#define BitInput Rar_BitInput +#define ModelPPM Rar_ModelPPM +#define RangeCoder Rar_RangeCoder +#define SubAllocator Rar_SubAllocator +#define UnpackFilter Rar_UnpackFilter +#define VM_PreparedProgram Rar_VM_PreparedProgram +#define CRCTab Rar_CRCTab + +// original source used rar* names for these as well +#define rarmalloc malloc +#define rarrealloc realloc +#define rarfree free + +// Internal flags, possibly set later +#undef SFX_MODULE +#undef VM_OPTIMIZE +#undef VM_STANDARDFILTERS +#undef NORARVM + +// During debugging if expr is false, prints message then continues execution +#ifndef check + #define check( expr ) ((void) 0) +#endif + +struct Rar_Error_Handler +{ + jmp_buf jmp_env; + + void MemoryError(); + void ReportError( unrar_err_t ); +}; + +// throw spec is mandatory in ISO C++ if operator new can return NULL +#if __cplusplus >= 199711 || __GNUC__ >= 3 + #define UNRAR_NOTHROW throw () +#else + #define UNRAR_NOTHROW +#endif + +struct Rar_Allocator +{ + // provides allocator that doesn't throw an exception on failure + static void operator delete ( void* p ) { free( p ); } + static void* operator new ( size_t s ) UNRAR_NOTHROW { return malloc( s ); } + static void* operator new ( size_t, void* p ) UNRAR_NOTHROW { return p; } +}; + +//// os.hpp +#undef STRICT_ALIGNMENT_REQUIRED +#undef LITTLE_ENDIAN +#define NM 1024 + +#if defined (__i386__) || defined (__x86_64__) || defined (_M_IX86) || defined (_M_X64) + // Optimizations mostly only apply to x86 + #define LITTLE_ENDIAN + #define ALLOW_NOT_ALIGNED_INT +#endif + +#if defined(__sparc) || defined(sparc) || defined(__sparcv9) +/* prohibit not aligned access to data structures in text comression + algorithm, increases memory requirements */ + #define STRICT_ALIGNMENT_REQUIRED +#endif + +//// rartypes.hpp +#if INT_MAX == 0x7FFFFFFF && UINT_MAX == 0xFFFFFFFF + typedef unsigned int uint32; //32 bits exactly + typedef int sint32; //signed 32 bits exactly + #define PRESENT_INT32 +#endif + +typedef unsigned char byte; //8 bits +typedef unsigned short ushort; //preferably 16 bits, but can be more +typedef unsigned int uint; //32 bits or more + +typedef wchar_t wchar; + +#define SHORT16(x) (sizeof(ushort)==2 ? (ushort)(x):((x)&0xffff)) +#define UINT32(x) (sizeof(uint )==4 ? (uint )(x):((x)&0xffffffff)) + +//// rardefs.hpp +#define Min(x,y) (((x)<(y)) ? (x):(y)) +#define Max(x,y) (((x)>(y)) ? (x):(y)) + +//// int64.hpp +typedef unrar_long_long Int64; + +#define int64to32(x) ((uint)(x)) +#define int32to64(high,low) ((((Int64)(high))<<31<<1)+(low)) +#define is64plus(x) (x>=0) + +#define INT64MAX int32to64(0x7fffffff,0) + +//// crc.hpp +extern uint CRCTab[256]; +void InitCRC(); +uint CRC(uint StartCRC,const void *Addr,size_t Size); +ushort OldCRC(ushort StartCRC,const void *Addr,size_t Size); + +//// rartime.hpp +struct RarTime +{ + unsigned time; + void SetDos(uint DosTime) { time = DosTime; } +}; + +//// rdwrfn.hpp +class ComprDataIO + : public Rar_Error_Handler +{ +public: + unrar_read_func user_read; + unrar_write_func user_write; + void* user_read_data; + void* user_write_data; + unrar_err_t write_error; // once write error occurs, no more writes are made + Int64 Tell_; + bool OldFormat; + +private: + Int64 UnpPackedSize; + bool SkipUnpCRC; + +public: + int UnpRead(byte *Addr,uint Count); + void UnpWrite(byte *Addr,uint Count); + void SetSkipUnpCRC( bool b ) { SkipUnpCRC = b; } + void SetPackedSizeToRead( Int64 n ) { UnpPackedSize = n; } + + uint UnpFileCRC; + + void Seek(Int64 Offset, int Method = 0 ) { (void)Method; Tell_ = Offset; } + Int64 Tell() { return Tell_; } + int Read( void* p, int n ); +}; + +//// rar.hpp +class Unpack; +#include "array.hpp" +#include "headers.hpp" +#include "getbits.hpp" +#include "archive.hpp" +#include "rawread.hpp" +#include "encname.hpp" +#include "compress.hpp" +#include "rarvm.hpp" +#include "model.hpp" +#include "unpack.hpp" + +//// extract.hpp +/** RAR archive */ +struct unrar_t + : public Rar_Allocator +{ + unrar_info_t info; + unrar_pos_t begin_pos; + unrar_pos_t solid_pos; + unrar_pos_t first_file_pos; + void const* data_; + void* own_data_; + void (*close_file)( void* ); // func ptr to avoid linking fclose() in unnecessarily + bool done; + long FileCount; + Unpack* Unp; + Array Buffer; + // large items last + Archive Arc; + + unrar_t(); + ~unrar_t(); + void UnstoreFile( Int64 ); + unrar_err_t ExtractCurrentFile( bool SkipSolid = false, bool check_compatibility_only = false ); + void update_first_file_pos() + { + if ( FileCount == 0 ) + first_file_pos = Arc.CurBlockPos; + } +}; + +typedef unrar_t CmdExtract; + +#endif diff --git a/snesreader/unrar/rarvm.cpp b/snesreader/unrar/rarvm.cpp new file mode 100644 index 00000000..d13e6264 --- /dev/null +++ b/snesreader/unrar/rarvm.cpp @@ -0,0 +1,1158 @@ +#include "rar.hpp" + +#include "rarvmtbl.cpp" + +// avoids warning of enumeration and non-enumeration in ?: expressions +#define VM_FC ((unsigned) VM_FC) +#define VM_FZ ((unsigned) VM_FZ) +#define VM_FS ((unsigned) VM_FS) + +RarVM::RarVM() +{ + Mem=NULL; +} + + +RarVM::~RarVM() +{ + rarfree( Mem ); +} + + +void RarVM::Init() +{ + if (Mem==NULL) + Mem = (byte*) rarmalloc( VM_MEMSIZE+4 ); +} + +void RarVM::handle_mem_error( Rar_Error_Handler& ErrHandler ) +{ + BitInput::handle_mem_error( ErrHandler ); + if ( !Mem ) + ErrHandler.MemoryError(); +} + +/********************************************************************* + IS_VM_MEM macro checks if address belongs to VM memory pool (Mem). + Only Mem data are always low endian regardless of machine architecture, + so we need to convert them to native format when reading or writing. + VM registers have endianness of host machine. +**********************************************************************/ +#define IS_VM_MEM(a) (((byte*)a)>=Mem && ((byte*)a)>8); + ((byte *)Addr)[2]=(byte)(Value>>16); + ((byte *)Addr)[3]=(byte)(Value>>24); + } + else + *(uint *)Addr=Value; +#else + *(uint32 *)Addr=Value; +#endif + } +} + +#if defined(BIG_ENDIAN) || !defined(ALLOW_NOT_ALIGNED_INT) || !defined(PRESENT_INT32) +#define SET_VALUE(ByteMode,Addr,Value) SetValue(ByteMode,(uint *)Addr,Value) +#else + #define SET_VALUE(ByteMode,Addr,Value) ((ByteMode) ? (*(byte *)(Addr)=(Value)):(*(uint32 *)(Addr)=((uint32)(Value)))) +#endif + + +void RarVM::SetLowEndianValue(uint *Addr,uint Value) +{ +#if defined(BIG_ENDIAN) || !defined(ALLOW_NOT_ALIGNED_INT) || !defined(PRESENT_INT32) + ((byte *)Addr)[0]=(byte)Value; + ((byte *)Addr)[1]=(byte)(Value>>8); + ((byte *)Addr)[2]=(byte)(Value>>16); + ((byte *)Addr)[3]=(byte)(Value>>24); +#else + *(uint32 *)Addr=Value; +#endif +} + + +inline uint* RarVM::GetOperand(VM_PreparedOperand *CmdOp) +{ + if (CmdOp->Type==VM_OPREGMEM) + return((uint *)&Mem[(*CmdOp->Addr+CmdOp->Base)&VM_MEMMASK]); + else + return(CmdOp->Addr); +} + + +void RarVM::Execute(VM_PreparedProgram *Prg) +{ + memcpy(R,Prg->InitR,sizeof(Prg->InitR)); + unsigned int GlobalSize=Min(Prg->GlobalData.Size(),VM_GLOBALMEMSIZE); + if (GlobalSize) + memcpy(Mem+VM_GLOBALMEMADDR,&Prg->GlobalData[0],GlobalSize); + unsigned int StaticSize=Min(Prg->StaticData.Size(),VM_GLOBALMEMSIZE-GlobalSize); + if (StaticSize) + memcpy(Mem+VM_GLOBALMEMADDR+GlobalSize,&Prg->StaticData[0],StaticSize); + + R[7]=VM_MEMSIZE; + Flags=0; + + VM_PreparedCommand *PreparedCode=Prg->AltCmd ? Prg->AltCmd:&Prg->Cmd[0]; + if (!ExecuteCode(PreparedCode,Prg->CmdCount)) + PreparedCode[0].OpCode=VM_RET; + uint NewBlockPos=GET_VALUE(false,&Mem[VM_GLOBALMEMADDR+0x20])&VM_MEMMASK; + uint NewBlockSize=GET_VALUE(false,&Mem[VM_GLOBALMEMADDR+0x1c])&VM_MEMMASK; + if (NewBlockPos+NewBlockSize>=VM_MEMSIZE) + NewBlockPos=NewBlockSize=0; + Prg->FilteredData=Mem+NewBlockPos; + Prg->FilteredDataSize=NewBlockSize; + + Prg->GlobalData.Reset(); + + uint DataSize=Min(GET_VALUE(false,(uint*)&Mem[VM_GLOBALMEMADDR+0x30]),VM_GLOBALMEMSIZE-VM_FIXEDGLOBALSIZE); + if (DataSize!=0) + { + Prg->GlobalData.Add(DataSize+VM_FIXEDGLOBALSIZE); + memcpy(&Prg->GlobalData[0],&Mem[VM_GLOBALMEMADDR],DataSize+VM_FIXEDGLOBALSIZE); + } +} + + +/* +Note: + Due to performance considerations RAR VM may set VM_FS, VM_FC, VM_FZ + incorrectly for byte operands. These flags are always valid only + for 32-bit operands. Check implementation of concrete VM command + to see if it sets flags right. +*/ + +#define SET_IP(IP) \ + if ((IP)>=CodeSize) \ + return(true); \ + if (--MaxOpCount<=0) \ + return(false); \ + Cmd=PreparedCode+(IP); + +bool RarVM::ExecuteCode(VM_PreparedCommand *PreparedCode,int CodeSize) +{ + int MaxOpCount=25000000; + VM_PreparedCommand *Cmd=PreparedCode; + while (1) + { +#ifndef NORARVM + // Get addresses to quickly access operands. + uint *Op1=GetOperand(&Cmd->Op1); + uint *Op2=GetOperand(&Cmd->Op2); +#endif + switch(Cmd->OpCode) + { +#ifndef NORARVM + case VM_MOV: + SET_VALUE(Cmd->ByteMode,Op1,GET_VALUE(Cmd->ByteMode,Op2)); + break; +#ifdef VM_OPTIMIZE + case VM_MOVB: + SET_VALUE(true,Op1,GET_VALUE(true,Op2)); + break; + case VM_MOVD: + SET_VALUE(false,Op1,GET_VALUE(false,Op2)); + break; +#endif + case VM_CMP: + { + uint Value1=GET_VALUE(Cmd->ByteMode,Op1); + uint Result=UINT32(Value1-GET_VALUE(Cmd->ByteMode,Op2)); + Flags=Result==0 ? VM_FZ:(Result>Value1)|(Result&VM_FS); + } + break; +#ifdef VM_OPTIMIZE + case VM_CMPB: + { + uint Value1=GET_VALUE(true,Op1); + uint Result=UINT32(Value1-GET_VALUE(true,Op2)); + Flags=Result==0 ? VM_FZ:(Result>Value1)|(Result&VM_FS); + } + break; + case VM_CMPD: + { + uint Value1=GET_VALUE(false,Op1); + uint Result=UINT32(Value1-GET_VALUE(false,Op2)); + Flags=Result==0 ? VM_FZ:(Result>Value1)|(Result&VM_FS); + } + break; +#endif + case VM_ADD: + { + uint Value1=GET_VALUE(Cmd->ByteMode,Op1); + uint Result=UINT32(Value1+GET_VALUE(Cmd->ByteMode,Op2)); + if (Cmd->ByteMode) + { + Result&=0xff; + Flags=(ResultByteMode,Op1,Result); + } + break; +#ifdef VM_OPTIMIZE + case VM_ADDB: + SET_VALUE(true,Op1,GET_VALUE(true,Op1)+GET_VALUE(true,Op2)); + break; + case VM_ADDD: + SET_VALUE(false,Op1,GET_VALUE(false,Op1)+GET_VALUE(false,Op2)); + break; +#endif + case VM_SUB: + { + uint Value1=GET_VALUE(Cmd->ByteMode,Op1); + uint Result=UINT32(Value1-GET_VALUE(Cmd->ByteMode,Op2)); + Flags=Result==0 ? VM_FZ:(Result>Value1)|(Result&VM_FS); + SET_VALUE(Cmd->ByteMode,Op1,Result); + } + break; +#ifdef VM_OPTIMIZE + case VM_SUBB: + SET_VALUE(true,Op1,GET_VALUE(true,Op1)-GET_VALUE(true,Op2)); + break; + case VM_SUBD: + SET_VALUE(false,Op1,GET_VALUE(false,Op1)-GET_VALUE(false,Op2)); + break; +#endif + case VM_JZ: + if ((Flags & VM_FZ)!=0) + { + SET_IP(GET_VALUE(false,Op1)); + continue; + } + break; + case VM_JNZ: + if ((Flags & VM_FZ)==0) + { + SET_IP(GET_VALUE(false,Op1)); + continue; + } + break; + case VM_INC: + { + uint Result=UINT32(GET_VALUE(Cmd->ByteMode,Op1)+1); + if (Cmd->ByteMode) + Result&=0xff; + SET_VALUE(Cmd->ByteMode,Op1,Result); + Flags=Result==0 ? VM_FZ:Result&VM_FS; + } + break; +#ifdef VM_OPTIMIZE + case VM_INCB: + SET_VALUE(true,Op1,GET_VALUE(true,Op1)+1); + break; + case VM_INCD: + SET_VALUE(false,Op1,GET_VALUE(false,Op1)+1); + break; +#endif + case VM_DEC: + { + uint Result=UINT32(GET_VALUE(Cmd->ByteMode,Op1)-1); + SET_VALUE(Cmd->ByteMode,Op1,Result); + Flags=Result==0 ? VM_FZ:Result&VM_FS; + } + break; +#ifdef VM_OPTIMIZE + case VM_DECB: + SET_VALUE(true,Op1,GET_VALUE(true,Op1)-1); + break; + case VM_DECD: + SET_VALUE(false,Op1,GET_VALUE(false,Op1)-1); + break; +#endif + case VM_JMP: + SET_IP(GET_VALUE(false,Op1)); + continue; + case VM_XOR: + { + uint Result=UINT32(GET_VALUE(Cmd->ByteMode,Op1)^GET_VALUE(Cmd->ByteMode,Op2)); + Flags=Result==0 ? VM_FZ:Result&VM_FS; + SET_VALUE(Cmd->ByteMode,Op1,Result); + } + break; + case VM_AND: + { + uint Result=UINT32(GET_VALUE(Cmd->ByteMode,Op1)&GET_VALUE(Cmd->ByteMode,Op2)); + Flags=Result==0 ? VM_FZ:Result&VM_FS; + SET_VALUE(Cmd->ByteMode,Op1,Result); + } + break; + case VM_OR: + { + uint Result=UINT32(GET_VALUE(Cmd->ByteMode,Op1)|GET_VALUE(Cmd->ByteMode,Op2)); + Flags=Result==0 ? VM_FZ:Result&VM_FS; + SET_VALUE(Cmd->ByteMode,Op1,Result); + } + break; + case VM_TEST: + { + uint Result=UINT32(GET_VALUE(Cmd->ByteMode,Op1)&GET_VALUE(Cmd->ByteMode,Op2)); + Flags=Result==0 ? VM_FZ:Result&VM_FS; + } + break; + case VM_JS: + if ((Flags & VM_FS)!=0) + { + SET_IP(GET_VALUE(false,Op1)); + continue; + } + break; + case VM_JNS: + if ((Flags & VM_FS)==0) + { + SET_IP(GET_VALUE(false,Op1)); + continue; + } + break; + case VM_JB: + if ((Flags & VM_FC)!=0) + { + SET_IP(GET_VALUE(false,Op1)); + continue; + } + break; + case VM_JBE: + if ((Flags & (VM_FC|VM_FZ))!=0) + { + SET_IP(GET_VALUE(false,Op1)); + continue; + } + break; + case VM_JA: + if ((Flags & (VM_FC|VM_FZ))==0) + { + SET_IP(GET_VALUE(false,Op1)); + continue; + } + break; + case VM_JAE: + if ((Flags & VM_FC)==0) + { + SET_IP(GET_VALUE(false,Op1)); + continue; + } + break; + case VM_PUSH: + R[7]-=4; + SET_VALUE(false,(uint *)&Mem[R[7]&VM_MEMMASK],GET_VALUE(false,Op1)); + break; + case VM_POP: + SET_VALUE(false,Op1,GET_VALUE(false,(uint *)&Mem[R[7] & VM_MEMMASK])); + R[7]+=4; + break; + case VM_CALL: + R[7]-=4; + SET_VALUE(false,(uint *)&Mem[R[7]&VM_MEMMASK],Cmd-PreparedCode+1); + SET_IP(GET_VALUE(false,Op1)); + continue; + case VM_NOT: + SET_VALUE(Cmd->ByteMode,Op1,~GET_VALUE(Cmd->ByteMode,Op1)); + break; + case VM_SHL: + { + uint Value1=GET_VALUE(Cmd->ByteMode,Op1); + uint Value2=GET_VALUE(Cmd->ByteMode,Op2); + uint Result=UINT32(Value1<ByteMode,Op1,Result); + } + break; + case VM_SHR: + { + uint Value1=GET_VALUE(Cmd->ByteMode,Op1); + uint Value2=GET_VALUE(Cmd->ByteMode,Op2); + uint Result=UINT32(Value1>>Value2); + Flags=(Result==0 ? VM_FZ:(Result&VM_FS))|((Value1>>(Value2-1))&VM_FC); + SET_VALUE(Cmd->ByteMode,Op1,Result); + } + break; + case VM_SAR: + { + uint Value1=GET_VALUE(Cmd->ByteMode,Op1); + uint Value2=GET_VALUE(Cmd->ByteMode,Op2); + uint Result=UINT32(((int)Value1)>>Value2); + Flags=(Result==0 ? VM_FZ:(Result&VM_FS))|((Value1>>(Value2-1))&VM_FC); + SET_VALUE(Cmd->ByteMode,Op1,Result); + } + break; + case VM_NEG: + { + uint Result=UINT32(-GET_VALUE(Cmd->ByteMode,Op1)); + Flags=Result==0 ? VM_FZ:VM_FC|(Result&VM_FS); + SET_VALUE(Cmd->ByteMode,Op1,Result); + } + break; +#ifdef VM_OPTIMIZE + case VM_NEGB: + SET_VALUE(true,Op1,-GET_VALUE(true,Op1)); + break; + case VM_NEGD: + SET_VALUE(false,Op1,-GET_VALUE(false,Op1)); + break; +#endif + case VM_PUSHA: + { + const int RegCount=sizeof(R)/sizeof(R[0]); + for (int I=0,SP=R[7]-4;IByteMode,Op1); + SET_VALUE(Cmd->ByteMode,Op1,GET_VALUE(Cmd->ByteMode,Op2)); + SET_VALUE(Cmd->ByteMode,Op2,Value1); + } + break; + case VM_MUL: + { + uint Result=GET_VALUE(Cmd->ByteMode,Op1)*GET_VALUE(Cmd->ByteMode,Op2); + SET_VALUE(Cmd->ByteMode,Op1,Result); + } + break; + case VM_DIV: + { + uint Divider=GET_VALUE(Cmd->ByteMode,Op2); + if (Divider!=0) + { + uint Result=GET_VALUE(Cmd->ByteMode,Op1)/Divider; + SET_VALUE(Cmd->ByteMode,Op1,Result); + } + } + break; + case VM_ADC: + { + uint Value1=GET_VALUE(Cmd->ByteMode,Op1); + uint FC=(Flags&VM_FC); + uint Result=UINT32(Value1+GET_VALUE(Cmd->ByteMode,Op2)+FC); + if (Cmd->ByteMode) + Result&=0xff; + Flags=(ResultByteMode,Op1,Result); + } + break; + case VM_SBB: + { + uint Value1=GET_VALUE(Cmd->ByteMode,Op1); + uint FC=(Flags&VM_FC); + uint Result=UINT32(Value1-GET_VALUE(Cmd->ByteMode,Op2)-FC); + if (Cmd->ByteMode) + Result&=0xff; + Flags=(Result>Value1 || Result==Value1 && FC)|(Result==0 ? VM_FZ:(Result&VM_FS)); + SET_VALUE(Cmd->ByteMode,Op1,Result); + } + break; +#endif // for #ifndef NORARVM + case VM_RET: + if (R[7]>=VM_MEMSIZE) + return(true); + SET_IP(GET_VALUE(false,(uint *)&Mem[R[7] & VM_MEMMASK])); + R[7]+=4; + continue; +#ifdef VM_STANDARDFILTERS + case VM_STANDARD: + ExecuteStandardFilter((VM_StandardFilters)Cmd->Op1.Data); + break; +#endif + case VM_PRINT: + break; + } + Cmd++; + --MaxOpCount; + } +} + + + + +void RarVM::Prepare(byte *Code,int CodeSize,VM_PreparedProgram *Prg) +{ + InitBitInput(); + memcpy(InBuf,Code,Min(CodeSize,BitInput::MAX_SIZE)); + + // Calculate the single byte XOR checksum to check validity of VM code. + byte XorSum=0; + { + for (int I=1;ICmdCount=0; + if (XorSum==Code[0]) // VM code is valid if equal. + { +#ifdef VM_STANDARDFILTERS + VM_StandardFilters FilterType=IsStandardFilter(Code,CodeSize); + if (FilterType!=VMSF_NONE) + { + // VM code is found among standard filters. + Prg->Cmd.Add(1); + VM_PreparedCommand *CurCmd=&Prg->Cmd[Prg->CmdCount++]; + CurCmd->OpCode=VM_STANDARD; + CurCmd->Op1.Data=FilterType; + CurCmd->Op1.Addr=&CurCmd->Op1.Data; + CurCmd->Op2.Addr=&CurCmd->Op2.Data; + CurCmd->Op1.Type=CurCmd->Op2.Type=VM_OPNONE; + CodeSize=0; + } +#endif + uint DataFlag=fgetbits(); + faddbits(1); + + // Read static data contained in DB operators. This data cannot be + // changed, it is a part of VM code, not a filter parameter. + + if (DataFlag&0x8000) + { + int DataSize=ReadData(*this)+1; + for (int I=0;InAddrStaticData.Add(1); + Prg->StaticData[I]=fgetbits()>>8; + faddbits(8); + } + } + + while (InAddrCmd.Add(1); + VM_PreparedCommand *CurCmd=&Prg->Cmd[Prg->CmdCount]; + uint Data=fgetbits(); + if ((Data&0x8000)==0) + { + CurCmd->OpCode=(VM_Commands)(Data>>12); + faddbits(4); + } + else + { + CurCmd->OpCode=(VM_Commands)((Data>>10)-24); + faddbits(6); + } + if (VM_CmdFlags[CurCmd->OpCode] & VMCF_BYTEMODE) + { + CurCmd->ByteMode=fgetbits()>>15; + faddbits(1); + } + else + CurCmd->ByteMode=0; + CurCmd->Op1.Type=CurCmd->Op2.Type=VM_OPNONE; + int OpNum=(VM_CmdFlags[CurCmd->OpCode] & VMCF_OPMASK); + CurCmd->Op1.Addr=CurCmd->Op2.Addr=NULL; + if (OpNum>0) + { + DecodeArg(CurCmd->Op1,CurCmd->ByteMode); // reading the first operand + if (OpNum==2) + DecodeArg(CurCmd->Op2,CurCmd->ByteMode); // reading the second operand + else + { + if (CurCmd->Op1.Type==VM_OPINT && (VM_CmdFlags[CurCmd->OpCode]&(VMCF_JUMP|VMCF_PROC))) + { + // Calculating jump distance. + int Distance=CurCmd->Op1.Data; + if (Distance>=256) + Distance-=256; + else + { + if (Distance>=136) + Distance-=264; + else + if (Distance>=16) + Distance-=8; + else + if (Distance>=8) + Distance-=16; + Distance+=Prg->CmdCount; + } + CurCmd->Op1.Data=Distance; + } + } + } + Prg->CmdCount++; + } + } + + // Adding RET command at the end of program. + Prg->Cmd.Add(1); + VM_PreparedCommand *CurCmd=&Prg->Cmd[Prg->CmdCount++]; + CurCmd->OpCode=VM_RET; + CurCmd->Op1.Addr=&CurCmd->Op1.Data; + CurCmd->Op2.Addr=&CurCmd->Op2.Data; + CurCmd->Op1.Type=CurCmd->Op2.Type=VM_OPNONE; + + // If operand 'Addr' field has not been set by DecodeArg calls above, + // let's set it to point to operand 'Data' field. It is necessary for + // VM_OPINT type operands (usual integers) or maybe if something was + // not set properly for other operands. 'Addr' field is required + // for quicker addressing of operand data. + for (int I=0;ICmdCount;I++) + { + VM_PreparedCommand *Cmd=&Prg->Cmd[I]; + if (Cmd->Op1.Addr==NULL) + Cmd->Op1.Addr=&Cmd->Op1.Data; + if (Cmd->Op2.Addr==NULL) + Cmd->Op2.Addr=&Cmd->Op2.Data; + } + +#ifdef VM_OPTIMIZE + if (CodeSize!=0) + Optimize(Prg); +#endif +} + + +void RarVM::DecodeArg(VM_PreparedOperand &Op,bool ByteMode) +{ + uint Data=fgetbits(); + if (Data & 0x8000) + { + Op.Type=VM_OPREG; // Operand is register (R[0]..R[7]) + Op.Data=(Data>>12)&7; // Register number + Op.Addr=&R[Op.Data]; // Register address + faddbits(4); // 1 flag bit and 3 register number bits + } + else + if ((Data & 0xc000)==0) + { + Op.Type=VM_OPINT; // Operand is integer + if (ByteMode) + { + Op.Data=(Data>>6) & 0xff; // Byte integer. + faddbits(10); + } + else + { + faddbits(2); + Op.Data=ReadData(*this); // 32 bit integer. + } + } + else + { + // Operand is data addressed by register data, base address or both. + Op.Type=VM_OPREGMEM; + if ((Data & 0x2000)==0) + { + // Base address is zero, just use the address from register. + Op.Data=(Data>>10)&7; + Op.Addr=&R[Op.Data]; + Op.Base=0; + faddbits(6); + } + else + { + if ((Data & 0x1000)==0) + { + // Use both register and base address. + Op.Data=(Data>>9)&7; + Op.Addr=&R[Op.Data]; + faddbits(7); + } + else + { + // Use base address only. Access memory by fixed address. + Op.Data=0; + faddbits(4); + } + Op.Base=ReadData(*this); // Read base address. + } + } +} + + +uint RarVM::ReadData(BitInput &Inp) +{ + uint Data=Inp.fgetbits(); + switch(Data&0xc000) + { + case 0: + Inp.faddbits(6); + return((Data>>10)&0xf); + case 0x4000: + if ((Data&0x3c00)==0) + { + Data=0xffffff00|((Data>>2)&0xff); + Inp.faddbits(14); + } + else + { + Data=(Data>>6)&0xff; + Inp.faddbits(10); + } + return(Data); + case 0x8000: + Inp.faddbits(2); + Data=Inp.fgetbits(); + Inp.faddbits(16); + return(Data); + default: + Inp.faddbits(2); + Data=(Inp.fgetbits()<<16); + Inp.faddbits(16); + Data|=Inp.fgetbits(); + Inp.faddbits(16); + return(Data); + } +} + + +void RarVM::SetMemory(unsigned int Pos,byte *Data,unsigned int DataSize) +{ + if (PosCmd[0]; + int CodeSize=Prg->CmdCount; + + for (int I=0;IOpCode) + { + case VM_MOV: + Cmd->OpCode=Cmd->ByteMode ? VM_MOVB:VM_MOVD; + continue; + case VM_CMP: + Cmd->OpCode=Cmd->ByteMode ? VM_CMPB:VM_CMPD; + continue; + } + if ((VM_CmdFlags[Cmd->OpCode] & VMCF_CHFLAGS)==0) + continue; + + // If we do not have jump commands between the current operation + // and next command which will modify processor flags, we can replace + // the current command with faster version which does not need to + // modify flags. + bool FlagsRequired=false; + for (int J=I+1;JOpCode) + { + case VM_ADD: + Cmd->OpCode=Cmd->ByteMode ? VM_ADDB:VM_ADDD; + continue; + case VM_SUB: + Cmd->OpCode=Cmd->ByteMode ? VM_SUBB:VM_SUBD; + continue; + case VM_INC: + Cmd->OpCode=Cmd->ByteMode ? VM_INCB:VM_INCD; + continue; + case VM_DEC: + Cmd->OpCode=Cmd->ByteMode ? VM_DECB:VM_DECD; + continue; + case VM_NEG: + Cmd->OpCode=Cmd->ByteMode ? VM_NEGB:VM_NEGD; + continue; + } + } +} +#endif + + +#ifdef VM_STANDARDFILTERS +VM_StandardFilters RarVM::IsStandardFilter(byte *Code,int CodeSize) +{ + static const + struct StandardFilterSignature + { + int Length; + uint CRC; + VM_StandardFilters Type; + } StdList[]={ + { + 53, 0xad576887, VMSF_E8, + },{ + 57, 0x3cd7e57e, VMSF_E8E9, + },{ + 120, 0x3769893f, VMSF_ITANIUM, + },{ + 29, 0x0e06077d, VMSF_DELTA, + },{ + 149, 0x1c2c5dc8, VMSF_RGB, + },{ + 216, 0xbc85e701, VMSF_AUDIO, + },{ + 40, 0x46b9c560, VMSF_UPCASE + } + }; + uint CodeCRC=CRC(0xffffffff,Code,CodeSize)^0xffffffff; + for (int I=0;I=VM_GLOBALMEMADDR || DataSize<4) + break; + + const int FileSize=0x1000000; + byte CmpByte2=FilterType==VMSF_E8E9 ? 0xe9:0xe8; + for (int CurPos=0;CurPos=0) + SET_VALUE(false,Data,Addr+FileSize); + } + else + if (Addr=VM_GLOBALMEMADDR || DataSize<21) + break; + + int CurPos=0; + + FileOffset>>=4; + + while (CurPos=0) + { + const + static byte Masks[16]={4,4,6,6,0,0,7,7,4,4,0,0,4,4,0,0}; + byte CmdMask=Masks[Byte]; + if (CmdMask!=0) + for (int I=0;I<=2;I++) + if (CmdMask & (1<=VM_GLOBALMEMADDR/2) + break; + + // Bytes from same channels are grouped to continual data blocks, + // so we need to place them back to their interleaving positions. + for (int CurChannel=0;CurChannel=VM_GLOBALMEMADDR/2 || PosR<0) + break; + for (int CurChannel=0;CurChannel=3) + { + byte *UpperData=DestData+UpperPos; + unsigned int UpperByte=*UpperData; + unsigned int UpperLeftByte=*(UpperData-3); + Predicted=PrevByte+UpperByte-UpperLeftByte; + int pa=abs((int)(Predicted-PrevByte)); + int pb=abs((int)(Predicted-UpperByte)); + int pc=abs((int)(Predicted-UpperLeftByte)); + if (pa<=pb && pa<=pc) + Predicted=PrevByte; + else + if (pb<=pc) + Predicted=UpperByte; + else + Predicted=UpperLeftByte; + } + else + Predicted=PrevByte; + DestData[I]=PrevByte=(byte)(Predicted-*(SrcData++)); + } + } + for (int I=PosR,Border=DataSize-2;I=VM_GLOBALMEMADDR/2) + break; + for (int CurChannel=0;CurChannel>3) & 0xff; + + unsigned int CurByte=*(SrcData++); + + Predicted-=CurByte; + DestData[I]=Predicted; + PrevDelta=(signed char)(Predicted-PrevByte); + PrevByte=Predicted; + + int D=((signed char)CurByte)<<3; + + Dif[0]+=abs(D); + Dif[1]+=abs(D-D1); + Dif[2]+=abs(D+D1); + Dif[3]+=abs(D-D2); + Dif[4]+=abs(D+D2); + Dif[5]+=abs(D-D3); + Dif[6]+=abs(D+D3); + + if ((ByteCount & 0x1f)==0) + { + unsigned int MinDif=Dif[0],NumMinDif=0; + Dif[0]=0; + for (int J=1;J=-16) K1--; break; + case 2: if (K1 < 16) K1++; break; + case 3: if (K2>=-16) K2--; break; + case 4: if (K2 < 16) K2++; break; + case 5: if (K3>=-16) K3--; break; + case 6: if (K3 < 16) K3++; break; + } + } + } + } + } + break; + case VMSF_UPCASE: + { + int DataSize=R[4],SrcPos=0,DestPos=DataSize; + if (DataSize>=VM_GLOBALMEMADDR/2) + break; + while (SrcPos>= InBit; + return(BitField & (0xffffffff>>(32-BitCount))); +} + + +void RarVM::FilterItanium_SetBits(byte *Data,unsigned int BitField,int BitPos, + int BitCount) +{ + int InAddr=BitPos/8; + int InBit=BitPos&7; + unsigned int AndMask=0xffffffff>>(32-BitCount); + AndMask=~(AndMask<>8)|0xff000000; + BitField>>=8; + } +} +#endif diff --git a/snesreader/unrar/rarvm.hpp b/snesreader/unrar/rarvm.hpp new file mode 100644 index 00000000..835e5299 --- /dev/null +++ b/snesreader/unrar/rarvm.hpp @@ -0,0 +1,112 @@ +#ifndef _RAR_VM_ +#define _RAR_VM_ + +#define VM_STANDARDFILTERS + +#ifndef SFX_MODULE +#define VM_OPTIMIZE +#endif + + +#define VM_MEMSIZE 0x40000 +#define VM_MEMMASK (VM_MEMSIZE-1) +#define VM_GLOBALMEMADDR 0x3C000 +#define VM_GLOBALMEMSIZE 0x2000 +#define VM_FIXEDGLOBALSIZE 64 + +enum VM_Commands +{ + VM_MOV, VM_CMP, VM_ADD, VM_SUB, VM_JZ, VM_JNZ, VM_INC, VM_DEC, + VM_JMP, VM_XOR, VM_AND, VM_OR, VM_TEST, VM_JS, VM_JNS, VM_JB, + VM_JBE, VM_JA, VM_JAE, VM_PUSH, VM_POP, VM_CALL, VM_RET, VM_NOT, + VM_SHL, VM_SHR, VM_SAR, VM_NEG, VM_PUSHA,VM_POPA, VM_PUSHF,VM_POPF, + VM_MOVZX,VM_MOVSX,VM_XCHG, VM_MUL, VM_DIV, VM_ADC, VM_SBB, VM_PRINT, + +#ifdef VM_OPTIMIZE + VM_MOVB, VM_MOVD, VM_CMPB, VM_CMPD, + + VM_ADDB, VM_ADDD, VM_SUBB, VM_SUBD, VM_INCB, VM_INCD, VM_DECB, VM_DECD, + VM_NEGB, VM_NEGD, +#endif + + VM_STANDARD +}; + +enum VM_StandardFilters { + VMSF_NONE, VMSF_E8, VMSF_E8E9, VMSF_ITANIUM, VMSF_RGB, VMSF_AUDIO, + VMSF_DELTA, VMSF_UPCASE +}; + +enum VM_Flags {VM_FC=1,VM_FZ=2,VM_FS=0x80000000}; + +enum VM_OpType {VM_OPREG,VM_OPINT,VM_OPREGMEM,VM_OPNONE}; + +struct VM_PreparedOperand +{ + VM_OpType Type; + uint Data; + uint Base; + uint *Addr; +}; + +struct VM_PreparedCommand +{ + VM_Commands OpCode; + bool ByteMode; + VM_PreparedOperand Op1,Op2; +}; + + +struct VM_PreparedProgram +{ + VM_PreparedProgram( Rar_Error_Handler* eh ) : Cmd( eh ), GlobalData( eh ), StaticData( eh ) + {AltCmd=NULL;} + + Array Cmd; + VM_PreparedCommand *AltCmd; + int CmdCount; + + Array GlobalData; + Array StaticData; // static data contained in DB operators + uint InitR[7]; + + byte *FilteredData; + unsigned int FilteredDataSize; +}; + +class RarVM:private BitInput +{ + private: + inline uint GetValue(bool ByteMode,uint *Addr); + inline void SetValue(bool ByteMode,uint *Addr,uint Value); + inline uint* GetOperand(VM_PreparedOperand *CmdOp); + void DecodeArg(VM_PreparedOperand &Op,bool ByteMode); +#ifdef VM_OPTIMIZE + void Optimize(VM_PreparedProgram *Prg); +#endif + bool ExecuteCode(VM_PreparedCommand *PreparedCode,int CodeSize); +#ifdef VM_STANDARDFILTERS + VM_StandardFilters IsStandardFilter(byte *Code,int CodeSize); + void ExecuteStandardFilter(VM_StandardFilters FilterType); + unsigned int FilterItanium_GetBits(byte *Data,int BitPos,int BitCount); + void FilterItanium_SetBits(byte *Data,unsigned int BitField,int BitPos, + int BitCount); +#endif + + byte *Mem; + uint R[8]; + uint Flags; + public: + RarVM(); + ~RarVM(); + void Init(); + void handle_mem_error( Rar_Error_Handler& ); + friend class Unpack; + void Prepare(byte *Code,int CodeSize,VM_PreparedProgram *Prg); + void Execute(VM_PreparedProgram *Prg); + void SetLowEndianValue(uint *Addr,uint Value); + void SetMemory(unsigned int Pos,byte *Data,unsigned int DataSize); + static uint ReadData(BitInput &Inp); +}; + +#endif diff --git a/snesreader/unrar/rarvmtbl.cpp b/snesreader/unrar/rarvmtbl.cpp new file mode 100644 index 00000000..abfdbeeb --- /dev/null +++ b/snesreader/unrar/rarvmtbl.cpp @@ -0,0 +1,57 @@ +// #included by rarvm.cpp +#ifdef RAR_COMMON_HPP +#define VMCF_OP0 0 +#define VMCF_OP1 1 +#define VMCF_OP2 2 +#define VMCF_OPMASK 3 +#define VMCF_BYTEMODE 4 +#define VMCF_JUMP 8 +#define VMCF_PROC 16 +#define VMCF_USEFLAGS 32 +#define VMCF_CHFLAGS 64 + +const +static byte VM_CmdFlags[]= +{ + /* VM_MOV */ VMCF_OP2 | VMCF_BYTEMODE , + /* VM_CMP */ VMCF_OP2 | VMCF_BYTEMODE | VMCF_CHFLAGS , + /* VM_ADD */ VMCF_OP2 | VMCF_BYTEMODE | VMCF_CHFLAGS , + /* VM_SUB */ VMCF_OP2 | VMCF_BYTEMODE | VMCF_CHFLAGS , + /* VM_JZ */ VMCF_OP1 | VMCF_JUMP | VMCF_USEFLAGS , + /* VM_JNZ */ VMCF_OP1 | VMCF_JUMP | VMCF_USEFLAGS , + /* VM_INC */ VMCF_OP1 | VMCF_BYTEMODE | VMCF_CHFLAGS , + /* VM_DEC */ VMCF_OP1 | VMCF_BYTEMODE | VMCF_CHFLAGS , + /* VM_JMP */ VMCF_OP1 | VMCF_JUMP , + /* VM_XOR */ VMCF_OP2 | VMCF_BYTEMODE | VMCF_CHFLAGS , + /* VM_AND */ VMCF_OP2 | VMCF_BYTEMODE | VMCF_CHFLAGS , + /* VM_OR */ VMCF_OP2 | VMCF_BYTEMODE | VMCF_CHFLAGS , + /* VM_TEST */ VMCF_OP2 | VMCF_BYTEMODE | VMCF_CHFLAGS , + /* VM_JS */ VMCF_OP1 | VMCF_JUMP | VMCF_USEFLAGS , + /* VM_JNS */ VMCF_OP1 | VMCF_JUMP | VMCF_USEFLAGS , + /* VM_JB */ VMCF_OP1 | VMCF_JUMP | VMCF_USEFLAGS , + /* VM_JBE */ VMCF_OP1 | VMCF_JUMP | VMCF_USEFLAGS , + /* VM_JA */ VMCF_OP1 | VMCF_JUMP | VMCF_USEFLAGS , + /* VM_JAE */ VMCF_OP1 | VMCF_JUMP | VMCF_USEFLAGS , + /* VM_PUSH */ VMCF_OP1 , + /* VM_POP */ VMCF_OP1 , + /* VM_CALL */ VMCF_OP1 | VMCF_PROC , + /* VM_RET */ VMCF_OP0 | VMCF_PROC , + /* VM_NOT */ VMCF_OP1 | VMCF_BYTEMODE , + /* VM_SHL */ VMCF_OP2 | VMCF_BYTEMODE | VMCF_CHFLAGS , + /* VM_SHR */ VMCF_OP2 | VMCF_BYTEMODE | VMCF_CHFLAGS , + /* VM_SAR */ VMCF_OP2 | VMCF_BYTEMODE | VMCF_CHFLAGS , + /* VM_NEG */ VMCF_OP1 | VMCF_BYTEMODE | VMCF_CHFLAGS , + /* VM_PUSHA */ VMCF_OP0 , + /* VM_POPA */ VMCF_OP0 , + /* VM_PUSHF */ VMCF_OP0 | VMCF_USEFLAGS , + /* VM_POPF */ VMCF_OP0 | VMCF_CHFLAGS , + /* VM_MOVZX */ VMCF_OP2 , + /* VM_MOVSX */ VMCF_OP2 , + /* VM_XCHG */ VMCF_OP2 | VMCF_BYTEMODE , + /* VM_MUL */ VMCF_OP2 | VMCF_BYTEMODE , + /* VM_DIV */ VMCF_OP2 | VMCF_BYTEMODE , + /* VM_ADC */ VMCF_OP2 | VMCF_BYTEMODE | VMCF_USEFLAGS | VMCF_CHFLAGS , + /* VM_SBB */ VMCF_OP2 | VMCF_BYTEMODE | VMCF_USEFLAGS | VMCF_CHFLAGS , + /* VM_PRINT */ VMCF_OP0 +}; +#endif diff --git a/snesreader/unrar/rawread.cpp b/snesreader/unrar/rawread.cpp new file mode 100644 index 00000000..afe6b06f --- /dev/null +++ b/snesreader/unrar/rawread.cpp @@ -0,0 +1,86 @@ +#include "rar.hpp" + +RawRead::RawRead(ComprDataIO *SrcFile) : Data( SrcFile ) +{ + RawRead::SrcFile=SrcFile; + ReadPos=0; + DataSize=0; +} + +void RawRead::Reset() +{ + ReadPos=0; + DataSize=0; + Data.Reset(); +} + +void RawRead::Read(int Size) +{ + // (removed decryption) + if (Size!=0) + { + Data.Add(Size); + DataSize+=SrcFile->Read(&Data[DataSize],Size); + } +} + + + + +void RawRead::Get(byte &Field) +{ + if (ReadPos2 ? CRC(0xffffffff,&Data[2],(ProcessedOnly ? ReadPos:DataSize)-2):0xffffffff); +} diff --git a/snesreader/unrar/rawread.hpp b/snesreader/unrar/rawread.hpp new file mode 100644 index 00000000..dc37c304 --- /dev/null +++ b/snesreader/unrar/rawread.hpp @@ -0,0 +1,25 @@ +#ifndef _RAR_RAWREAD_ +#define _RAR_RAWREAD_ + +class RawRead +{ +private: + Array Data; + File *SrcFile; + int DataSize; + int ReadPos; + friend class Archive; +public: + RawRead(File *SrcFile); + void Reset(); + void Read(int Size); + void Get(byte &Field); + void Get(ushort &Field); + void Get(uint &Field); + void Get(byte *Field,int Size); + uint GetCRC(bool ProcessedOnly); + int Size() {return DataSize;} + int PaddedSize() {return Data.Size()-DataSize;} +}; + +#endif diff --git a/snesreader/unrar/readme.txt b/snesreader/unrar/readme.txt new file mode 100644 index 00000000..20e70c77 --- /dev/null +++ b/snesreader/unrar/readme.txt @@ -0,0 +1,63 @@ + + Portable UnRAR version + + + 1. General + + This package includes freeware Unrar C++ source and a few makefiles + (makefile.bcc, makefile.msc+msc.dep, makefile.unix). Unrar source + is subset of RAR and generated from RAR source automatically, + by a small program removing blocks like '#ifndef UNRAR ... #endif'. + Such method is not perfect and you may find some RAR related + stuff unnecessary in Unrar, especially in header files. + + If you wish to port Unrar to a new platform, you may need to edit + '#define LITTLE_ENDIAN' in os.hpp and data type definitions + in rartypes.hpp. + + if computer architecture does not allow not aligned data access, + you need to undefine ALLOW_NOT_ALIGNED_INT and define + STRICT_ALIGNMENT_REQUIRED in os.h. Note that it will increase memory + requirements. + + If you use Borland C++ makefile (makefile.bcc), you need to define + BASEPATHCC environment (or makefile) variable containing + the path to Borland C++ installation. + + Makefile.unix contains numerous compiler option sets. + GCC Linux is selected by default. If you need to compile Unrar + for other platforms, uncomment corresponding lines. + + + 2. Unrar binaries + + If you compiled Unrar for OS, which is not present in "Downloads" + and "RAR extras" on www.rarlab.com, we will appreciate if you send + us the compiled executable to place it to our site. + + + 3. Acknowledgements + + This source includes parts of code written by the following authors: + + Dmitry Shkarin PPMII v.H text compression + Dmitry Subbotin Carryless rangecoder + Szymon Stefanek AES encryption + Brian Gladman AES encryption + Steve Reid SHA-1 hash function + Marcus Herbert makefile.unix file + Tomasz Klim fixes for libunrar.so + Robert Riebisch makefile.dj and patches for DJGPP + + + 4. Legal stuff + + Unrar source may be used in any software to handle RAR archives + without limitations free of charge, but cannot be used to re-create + the RAR compression algorithm, which is proprietary. Distribution + of modified Unrar source in separate form or as a part of other + software is permitted, provided that it is clearly stated in + the documentation and source comments that the code may not be used + to develop a RAR (WinRAR) compatible archiver. + + More detailed license text is available in license.txt. diff --git a/snesreader/unrar/suballoc.cpp b/snesreader/unrar/suballoc.cpp new file mode 100644 index 00000000..66d49d55 --- /dev/null +++ b/snesreader/unrar/suballoc.cpp @@ -0,0 +1,261 @@ +/**************************************************************************** + * This file is part of PPMd project * + * Written and distributed to public domain by Dmitry Shkarin 1997, * + * 1999-2000 * + * Contents: memory allocation routines * + ****************************************************************************/ + +// #included by unpack.cpp +#ifdef RAR_COMMON_HPP +SubAllocator::SubAllocator() +{ + Clean(); +} + + +void SubAllocator::Clean() +{ + SubAllocatorSize=0; +} + + +inline void SubAllocator::InsertNode(void* p,int indx) +{ + ((RAR_NODE*) p)->next=FreeList[indx].next; + FreeList[indx].next=(RAR_NODE*) p; +} + + +inline void* SubAllocator::RemoveNode(int indx) +{ + RAR_NODE* RetVal=FreeList[indx].next; + FreeList[indx].next=RetVal->next; + return RetVal; +} + + +inline uint SubAllocator::U2B(int NU) +{ + return /*8*NU+4*NU*/UNIT_SIZE*NU; +} + + +/* + calculate RAR_MEM_BLK + Items address. Real RAR_MEM_BLK size must be + equal to UNIT_SIZE, so we cannot just add Items to RAR_MEM_BLK address +*/ +inline RAR_MEM_BLK* SubAllocator::MBPtr(RAR_MEM_BLK *BasePtr,int Items) +{ + return((RAR_MEM_BLK*)( ((byte *)(BasePtr))+U2B(Items) )); +} + + +inline void SubAllocator::SplitBlock(void* pv,int OldIndx,int NewIndx) +{ + int i, UDiff=Indx2Units[OldIndx]-Indx2Units[NewIndx]; + byte* p=((byte*) pv)+U2B(Indx2Units[NewIndx]); + if (Indx2Units[i=Units2Indx[UDiff-1]] != UDiff) + { + InsertNode(p,--i); + p += U2B(i=Indx2Units[i]); + UDiff -= i; + } + InsertNode(p,Units2Indx[UDiff-1]); +} + + + + +void SubAllocator::StopSubAllocator() +{ + if ( SubAllocatorSize ) + { + SubAllocatorSize=0; + rarfree(HeapStart); + } +} + + +bool SubAllocator::StartSubAllocator(int SASize) +{ + uint t=SASize << 20; + if (SubAllocatorSize == t) + return true; + StopSubAllocator(); + uint AllocSize=t/FIXED_UNIT_SIZE*UNIT_SIZE+UNIT_SIZE; +#ifdef STRICT_ALIGNMENT_REQUIRED + AllocSize+=UNIT_SIZE; +#endif + if ((HeapStart=(byte *)rarmalloc(AllocSize)) == NULL) + { + ErrHandler->MemoryError(); + return false; + } + HeapEnd=HeapStart+AllocSize-UNIT_SIZE; + SubAllocatorSize=t; + return true; +} + + +void SubAllocator::InitSubAllocator() +{ + int i, k; + memset(FreeList,0,sizeof(FreeList)); + pText=HeapStart; + uint Size2=FIXED_UNIT_SIZE*(SubAllocatorSize/8/FIXED_UNIT_SIZE*7); + uint RealSize2=Size2/FIXED_UNIT_SIZE*UNIT_SIZE; + uint Size1=SubAllocatorSize-Size2; + uint RealSize1=Size1/FIXED_UNIT_SIZE*UNIT_SIZE+Size1%FIXED_UNIT_SIZE; +#ifdef STRICT_ALIGNMENT_REQUIRED + if (Size1%FIXED_UNIT_SIZE!=0) + RealSize1+=UNIT_SIZE-Size1%FIXED_UNIT_SIZE; +#endif + HiUnit=HeapStart+SubAllocatorSize; + LoUnit=UnitsStart=HeapStart+RealSize1; + FakeUnitsStart=HeapStart+Size1; + HiUnit=LoUnit+RealSize2; + for (i=0,k=1;i < N1 ;i++,k += 1) + Indx2Units[i]=k; + for (k++;i < N1+N2 ;i++,k += 2) + Indx2Units[i]=k; + for (k++;i < N1+N2+N3 ;i++,k += 3) + Indx2Units[i]=k; + for (k++;i < N1+N2+N3+N4;i++,k += 4) + Indx2Units[i]=k; + for (GlueCount=k=i=0;k < 128;k++) + { + i += (Indx2Units[i] < k+1); + Units2Indx[k]=i; + } +} + + +inline void SubAllocator::GlueFreeBlocks() +{ + RAR_MEM_BLK s0, * p, * p1; + int i, k, sz; + if (LoUnit != HiUnit) + *LoUnit=0; + for (i=0, s0.next=s0.prev=&s0;i < N_INDEXES;i++) + while ( FreeList[i].next ) + { + p=(RAR_MEM_BLK*)RemoveNode(i); + p->insertAt(&s0); + p->Stamp=0xFFFF; + p->NU=Indx2Units[i]; + } + for (p=s0.next;p != &s0;p=p->next) + while ((p1=MBPtr(p,p->NU))->Stamp == 0xFFFF && int(p->NU)+p1->NU < 0x10000) + { + p1->remove(); + p->NU += p1->NU; + } + while ((p=s0.next) != &s0) + { + for (p->remove(), sz=p->NU;sz > 128;sz -= 128, p=MBPtr(p,128)) + InsertNode(p,N_INDEXES-1); + if (Indx2Units[i=Units2Indx[sz-1]] != sz) + { + k=sz-Indx2Units[--i]; + InsertNode(MBPtr(p,sz-k),k-1); + } + InsertNode(p,i); + } +} + +void* SubAllocator::AllocUnitsRare(int indx) +{ + if ( !GlueCount ) + { + GlueCount = 255; + GlueFreeBlocks(); + if ( FreeList[indx].next ) + return RemoveNode(indx); + } + int i=indx; + do + { + if (++i == N_INDEXES) + { + GlueCount--; + i=U2B(Indx2Units[indx]); + int j=FIXED_UNIT_SIZE*Indx2Units[indx]; + if (FakeUnitsStart-pText > j) + { + FakeUnitsStart-=j; + UnitsStart -= i; + return(UnitsStart); + } + return(NULL); + } + } while ( !FreeList[i].next ); + void* RetVal=RemoveNode(i); + SplitBlock(RetVal,i,indx); + return RetVal; +} + + +inline void* SubAllocator::AllocUnits(int NU) +{ + int indx=Units2Indx[NU-1]; + if ( FreeList[indx].next ) + return RemoveNode(indx); + void* RetVal=LoUnit; + LoUnit += U2B(Indx2Units[indx]); + if (LoUnit <= HiUnit) + return RetVal; + LoUnit -= U2B(Indx2Units[indx]); + return AllocUnitsRare(indx); +} + + +void* SubAllocator::AllocContext() +{ + if (HiUnit != LoUnit) + return (HiUnit -= UNIT_SIZE); + if ( FreeList->next ) + return RemoveNode(0); + return AllocUnitsRare(0); +} + + +void* SubAllocator::ExpandUnits(void* OldPtr,int OldNU) +{ + int i0=Units2Indx[OldNU-1], i1=Units2Indx[OldNU-1+1]; + if (i0 == i1) + return OldPtr; + void* ptr=AllocUnits(OldNU+1); + if ( ptr ) + { + memcpy(ptr,OldPtr,U2B(OldNU)); + InsertNode(OldPtr,i0); + } + return ptr; +} + + +void* SubAllocator::ShrinkUnits(void* OldPtr,int OldNU,int NewNU) +{ + int i0=Units2Indx[OldNU-1], i1=Units2Indx[NewNU-1]; + if (i0 == i1) + return OldPtr; + if ( FreeList[i1].next ) + { + void* ptr=RemoveNode(i1); + memcpy(ptr,OldPtr,U2B(NewNU)); + InsertNode(OldPtr,i0); + return ptr; + } + else + { + SplitBlock(OldPtr,i0,i1); + return OldPtr; + } +} + + +void SubAllocator::FreeUnits(void* ptr,int OldNU) +{ + InsertNode(ptr,Units2Indx[OldNU-1]); +} +#endif diff --git a/snesreader/unrar/suballoc.hpp b/snesreader/unrar/suballoc.hpp new file mode 100644 index 00000000..1ea9f217 --- /dev/null +++ b/snesreader/unrar/suballoc.hpp @@ -0,0 +1,88 @@ +/**************************************************************************** + * This file is part of PPMd project * + * Written and distributed to public domain by Dmitry Shkarin 1997, * + * 1999-2000 * + * Contents: interface to memory allocation routines * + ****************************************************************************/ +#if !defined(_SUBALLOC_H_) +#define _SUBALLOC_H_ + +const int N1=4, N2=4, N3=4, N4=(128+3-1*N1-2*N2-3*N3)/4; +const int N_INDEXES=N1+N2+N3+N4; + +#if defined(__GNUC__) && !defined(STRICT_ALIGNMENT_REQUIRED) +#define _PACK_ATTR __attribute__ ((packed)) +#else +#define _PACK_ATTR +#endif /* defined(__GNUC__) */ + +#ifndef STRICT_ALIGNMENT_REQUIRED +#pragma pack(1) +#endif + +struct RAR_MEM_BLK +{ + ushort Stamp, NU; + RAR_MEM_BLK* next, * prev; + void insertAt(RAR_MEM_BLK* p) + { + next=(prev=p)->next; + p->next=next->prev=this; + } + void remove() + { + prev->next=next; + next->prev=prev; + } +} _PACK_ATTR; + +#ifndef STRICT_ALIGNMENT_REQUIRED +#ifdef _AIX +#pragma pack(pop) +#else +#pragma pack() +#endif +#endif + + +struct RAR_NODE +{ + RAR_NODE* next; +}; + +class SubAllocator +{ + private: + inline void InsertNode(void* p,int indx); + inline void* RemoveNode(int indx); + inline uint U2B(int NU); + inline void SplitBlock(void* pv,int OldIndx,int NewIndx); + uint GetUsedMemory(); + inline void GlueFreeBlocks(); + void* AllocUnitsRare(int indx); + inline RAR_MEM_BLK* MBPtr(RAR_MEM_BLK *BasePtr,int Items); + + long SubAllocatorSize; + byte Indx2Units[N_INDEXES], Units2Indx[128], GlueCount; + byte *HeapStart,*LoUnit, *HiUnit; + struct RAR_NODE FreeList[N_INDEXES]; + public: + Rar_Error_Handler* ErrHandler; + SubAllocator(); + ~SubAllocator() {StopSubAllocator();} + void Clean(); + bool StartSubAllocator(int SASize); + void StopSubAllocator(); + void InitSubAllocator(); + inline void* AllocContext(); + inline void* AllocUnits(int NU); + inline void* ExpandUnits(void* ptr,int OldNU); + inline void* ShrinkUnits(void* ptr,int OldNU,int NewNU); + inline void FreeUnits(void* ptr,int OldNU); + long GetAllocatedMemory() {return(SubAllocatorSize);}; + + byte *pText, *UnitsStart,*HeapEnd,*FakeUnitsStart; +}; + + +#endif /* !defined(_SUBALLOC_H_) */ diff --git a/snesreader/unrar/technote.txt b/snesreader/unrar/technote.txt new file mode 100644 index 00000000..15e57593 --- /dev/null +++ b/snesreader/unrar/technote.txt @@ -0,0 +1,275 @@ + + RAR version 3.80 - Technical information + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + THE ARCHIVE FORMAT DESCRIBED BELOW IS ONLY VALID FOR VERSIONS SINCE 1.50 + + ========================================================================== + RAR archive file format + ========================================================================== + + Archive file consists of variable length blocks. The order of these +blocks may vary, but the first block must be a marker block followed by +an archive header block. + + Each block begins with the following fields: + +HEAD_CRC 2 bytes CRC of total block or block part +HEAD_TYPE 1 byte Block type +HEAD_FLAGS 2 bytes Block flags +HEAD_SIZE 2 bytes Block size +ADD_SIZE 4 bytes Optional field - added block size + + Field ADD_SIZE present only if (HEAD_FLAGS & 0x8000) != 0 + + Total block size is HEAD_SIZE if (HEAD_FLAGS & 0x8000) == 0 +and HEAD_SIZE+ADD_SIZE if the field ADD_SIZE is present - when +(HEAD_FLAGS & 0x8000) != 0. + + In each block the followings bits in HEAD_FLAGS have the same meaning: + + 0x4000 - if set, older RAR versions will ignore the block + and remove it when the archive is updated. + if clear, the block is copied to the new archive + file when the archive is updated; + + 0x8000 - if set, ADD_SIZE field is present and the full block + size is HEAD_SIZE+ADD_SIZE. + + Declared block types: + +HEAD_TYPE=0x72 marker block +HEAD_TYPE=0x73 archive header +HEAD_TYPE=0x74 file header +HEAD_TYPE=0x75 old style comment header +HEAD_TYPE=0x76 old style authenticity information +HEAD_TYPE=0x77 old style subblock +HEAD_TYPE=0x78 old style recovery record +HEAD_TYPE=0x79 old style authenticity information +HEAD_TYPE=0x7a subblock + + Comment block is actually used only within other blocks and doesn't +exist separately. + + Archive processing is made in the following manner: + +1. Read and check marker block +2. Read archive header +3. Read or skip HEAD_SIZE-sizeof(MAIN_HEAD) bytes +4. If end of archive encountered then terminate archive processing, + else read 7 bytes into fields HEAD_CRC, HEAD_TYPE, HEAD_FLAGS, + HEAD_SIZE. +5. Check HEAD_TYPE. + if HEAD_TYPE==0x74 + read file header ( first 7 bytes already read ) + read or skip HEAD_SIZE-sizeof(FILE_HEAD) bytes + if (HEAD_FLAGS & 0x100) + read or skip HIGH_PACK_SIZE*0x100000000+PACK_SIZE bytes + else + read or skip PACK_SIZE bytes + else + read corresponding HEAD_TYPE block: + read HEAD_SIZE-7 bytes + if (HEAD_FLAGS & 0x8000) + read ADD_SIZE bytes +6. go to 4. + + + ========================================================================== + Block Formats + ========================================================================== + + + Marker block ( MARK_HEAD ) + + +HEAD_CRC Always 0x6152 +2 bytes + +HEAD_TYPE Header type: 0x72 +1 byte + +HEAD_FLAGS Always 0x1a21 +2 bytes + +HEAD_SIZE Block size = 0x0007 +2 bytes + + The marker block is actually considered as a fixed byte +sequence: 0x52 0x61 0x72 0x21 0x1a 0x07 0x00 + + + + Archive header ( MAIN_HEAD ) + + +HEAD_CRC CRC of fields HEAD_TYPE to RESERVED2 +2 bytes + +HEAD_TYPE Header type: 0x73 +1 byte + +HEAD_FLAGS Bit flags: +2 bytes + 0x0001 - Volume attribute (archive volume) + 0x0002 - Archive comment present + RAR 3.x uses the separate comment block + and does not set this flag. + + 0x0004 - Archive lock attribute + 0x0008 - Solid attribute (solid archive) + 0x0010 - New volume naming scheme ('volname.partN.rar') + 0x0020 - Authenticity information present + RAR 3.x does not set this flag. + + 0x0040 - Recovery record present + 0x0080 - Block headers are encrypted + 0x0100 - First volume (set only by RAR 3.0 and later) + + other bits in HEAD_FLAGS are reserved for + internal use + +HEAD_SIZE Archive header total size including archive comments +2 bytes + +RESERVED1 Reserved +2 bytes + +RESERVED2 Reserved +4 bytes + + + + File header (File in archive) + + +HEAD_CRC CRC of fields from HEAD_TYPE to FILEATTR +2 bytes and file name + +HEAD_TYPE Header type: 0x74 +1 byte + +HEAD_FLAGS Bit flags: +2 bytes + 0x01 - file continued from previous volume + 0x02 - file continued in next volume + 0x04 - file encrypted with password + + 0x08 - file comment present + RAR 3.x uses the separate comment block + and does not set this flag. + + 0x10 - information from previous files is used (solid flag) + (for RAR 2.0 and later) + + bits 7 6 5 (for RAR 2.0 and later) + + 0 0 0 - dictionary size 64 KB + 0 0 1 - dictionary size 128 KB + 0 1 0 - dictionary size 256 KB + 0 1 1 - dictionary size 512 KB + 1 0 0 - dictionary size 1024 KB + 1 0 1 - dictionary size 2048 KB + 1 1 0 - dictionary size 4096 KB + 1 1 1 - file is directory + + 0x100 - HIGH_PACK_SIZE and HIGH_UNP_SIZE fields + are present. These fields are used to archive + only very large files (larger than 2Gb), + for smaller files these fields are absent. + + 0x200 - FILE_NAME contains both usual and encoded + Unicode name separated by zero. In this case + NAME_SIZE field is equal to the length + of usual name plus encoded Unicode name plus 1. + + If this flag is present, but FILE_NAME does not + contain zero bytes, it means that file name + is encoded using UTF-8. + + 0x400 - the header contains additional 8 bytes + after the file name, which are required to + increase encryption security (so called 'salt'). + + 0x800 - Version flag. It is an old file version, + a version number is appended to file name as ';n'. + + 0x1000 - Extended time field present. + + 0x8000 - this bit always is set, so the complete + block size is HEAD_SIZE + PACK_SIZE + (and plus HIGH_PACK_SIZE, if bit 0x100 is set) + +HEAD_SIZE File header full size including file name and comments +2 bytes + +PACK_SIZE Compressed file size +4 bytes + +UNP_SIZE Uncompressed file size +4 bytes + +HOST_OS Operating system used for archiving +1 byte 0 - MS DOS + 1 - OS/2 + 2 - Win32 + 3 - Unix + 4 - Mac OS + 5 - BeOS + +FILE_CRC File CRC +4 bytes + +FTIME Date and time in standard MS DOS format +4 bytes + +UNP_VER RAR version needed to extract file +1 byte + Version number is encoded as + 10 * Major version + minor version. + +METHOD Packing method +1 byte + 0x30 - storing + 0x31 - fastest compression + 0x32 - fast compression + 0x33 - normal compression + 0x34 - good compression + 0x35 - best compression + +NAME_SIZE File name size +2 bytes + +ATTR File attributes +4 bytes + +HIGH_PACK_SIZE High 4 bytes of 64 bit value of compressed file size. +4 bytes Optional value, presents only if bit 0x100 in HEAD_FLAGS + is set. + +HIGH_UNP_SIZE High 4 bytes of 64 bit value of uncompressed file size. +4 bytes Optional value, presents only if bit 0x100 in HEAD_FLAGS + is set. + +FILE_NAME File name - string of NAME_SIZE bytes size + +SALT present if (HEAD_FLAGS & 0x400) != 0 +8 bytes + +EXT_TIME present if (HEAD_FLAGS & 0x1000) != 0 +variable size + +other new fields may appear here. + + + ========================================================================== + Application notes + ========================================================================== + + 1. To process an SFX archive you need to skip the SFX module searching +for the marker block in the archive. There is no marker block sequence (0x52 +0x61 0x72 0x21 0x1a 0x07 0x00) in the SFX module itself. + + 2. The CRC is calculated using the standard polynomial 0xEDB88320. In +case the size of the CRC is less than 4 bytes, only the low order bytes +are used. diff --git a/snesreader/unrar/unicode.cpp b/snesreader/unrar/unicode.cpp new file mode 100644 index 00000000..3853752c --- /dev/null +++ b/snesreader/unrar/unicode.cpp @@ -0,0 +1,106 @@ +#include "rar.hpp" + +#include "unicode.hpp" + +bool WideToChar(const wchar *Src,char *Dest,int DestSize) +{ + bool RetCode=true; +#ifdef _WIN_32 + if (WideCharToMultiByte(CP_ACP,0,Src,-1,Dest,DestSize,NULL,NULL)==0) + RetCode=false; +#else +#ifdef _APPLE + WideToUtf(Src,Dest,DestSize); +#else +#ifdef MBFUNCTIONS + + size_t ResultingSize=wcstombs(Dest,Src,DestSize); + if (ResultingSize==(size_t)-1) + RetCode=false; + if (ResultingSize==0 && *Src!=0) + RetCode=false; + + if ((!RetCode || *Dest==0 && *Src!=0) && DestSize>NM && strlenw(Src)>5)==6) + { + if ((*Src&0xc0)!=0x80) + break; + d=((c&0x1f)<<6)|(*Src&0x3f); + Src++; + } + else + if ((c>>4)==14) + { + if ((Src[0]&0xc0)!=0x80 || (Src[1]&0xc0)!=0x80) + break; + d=((c&0xf)<<12)|((Src[0]&0x3f)<<6)|(Src[1]&0x3f); + Src+=2; + } + else + if ((c>>3)==30) + { + if ((Src[0]&0xc0)!=0x80 || (Src[1]&0xc0)!=0x80 || (Src[2]&0xc0)!=0x80) + break; + d=((c&7)<<18)|((Src[0]&0x3f)<<12)|((Src[1]&0x3f)<<6)|(Src[2]&0x3f); + Src+=3; + } + else + break; + if (--DestSize<0) + break; + if (d>0xffff) + { + if (--DestSize<0 || d>0x10ffff) + break; + *(Dest++)=((d-0x10000)>>10)+0xd800; + *(Dest++)=(d&0x3ff)+0xdc00; + } + else + *(Dest++)=d; + } + *Dest=0; +} + + +// strfn.cpp +void ExtToInt(const char *Src,char *Dest) +{ +#if defined(_WIN_32) + CharToOem(Src,Dest); +#else + if (Dest!=Src) + strcpy(Dest,Src); +#endif +} diff --git a/snesreader/unrar/unicode.hpp b/snesreader/unrar/unicode.hpp new file mode 100644 index 00000000..2ed90e6a --- /dev/null +++ b/snesreader/unrar/unicode.hpp @@ -0,0 +1,10 @@ +#ifndef _RAR_UNICODE_ +#define _RAR_UNICODE_ + +bool WideToChar(const wchar *Src,char *Dest,int DestSize=0x1000000); +void UtfToWide(const char *Src,wchar *Dest,int DestSize); + +// strfn.cpp +void ExtToInt(const char *Src,char *Dest); + +#endif diff --git a/snesreader/unrar/unpack.cpp b/snesreader/unrar/unpack.cpp new file mode 100644 index 00000000..3d9bcf84 --- /dev/null +++ b/snesreader/unrar/unpack.cpp @@ -0,0 +1,1065 @@ +#include "rar.hpp" + +#include "coder.cpp" +#include "suballoc.cpp" +#include "model.cpp" +#ifndef SFX_MODULE +#include "unpack15.cpp" +#include "unpack20.cpp" +#endif + +Unpack::Unpack(ComprDataIO *DataIO) + : VMCode( DataIO ), Filters( DataIO ), PrgStack( DataIO ), OldFilterLengths( DataIO ), ErrHandler( *DataIO ) +{ + PPM.SubAlloc.ErrHandler = DataIO; + LastStackFilter = NULL; + UnpIO=DataIO; + Window=NULL; + ExternalWindow=false; + UnpAllBuf=false; + UnpSomeRead=false; +} + + +Unpack::~Unpack() +{ + if (Window!=NULL && !ExternalWindow) + rarfree( Window ); + InitFilters(); +} + + +void Unpack::Init(byte *Window) +{ + if (Window==NULL) + { + Unpack::Window = (byte*) rarmalloc( MAXWINSIZE ); + if (Unpack::Window==NULL) + ErrHandler.MemoryError(); + } + else + { + Unpack::Window=Window; + ExternalWindow=true; + } + UnpInitData(false); + BitInput::handle_mem_error( ErrHandler ); + Inp.handle_mem_error( ErrHandler ); + + // Only check BitInput, as VM's memory isn't allocated yet + VM.BitInput::handle_mem_error( ErrHandler ); + +#ifndef SFX_MODULE + // RAR 1.5 decompression initialization + OldUnpInitData(false); + InitHuff(); +#endif +} + + +void Unpack::DoUnpack(int Method,bool Solid) +{ + switch(Method) + { +#ifndef SFX_MODULE + case 15: // rar 1.5 compression + Unpack15(Solid); + break; + case 20: // rar 2.x compression + case 26: // files larger than 2GB + Unpack20(Solid); + break; +#endif + case 29: // rar 3.x compression + case 36: // alternative hash + Unpack29(Solid); + break; + } +} + + +inline void Unpack::InsertOldDist(unsigned int Distance) +{ + OldDist[3]=OldDist[2]; + OldDist[2]=OldDist[1]; + OldDist[1]=OldDist[0]; + OldDist[0]=Distance; +} + + +inline void Unpack::InsertLastMatch(unsigned int Length,unsigned int Distance) +{ + LastDist=Distance; + LastLength=Length; +} + + +// These optimizations give 22% speedup on x86, 44% speedup on PowerPC +void Unpack::CopyString(unsigned int Length,unsigned int Distance) +{ + unsigned UnpPtr = this->UnpPtr; // cache in register + byte* const Window = this->Window; // cache in register + + unsigned int DestPtr=UnpPtr-Distance; + if (UnpPtrUnpPtr += Length; + if ( Distance < Length ) // can't use memcpy when source and dest overlap + { + // Length always >= 1 + do + { + Window[UnpPtr++]=Window[DestPtr++]; + } + while (--Length>0) + ; + } + else + { + memcpy( &Window[UnpPtr], &Window[DestPtr], Length ); + } + } + else + { + while (Length--) + { + Window[UnpPtr]=Window[DestPtr++ & MAXWINMASK]; + UnpPtr=(UnpPtr+1) & MAXWINMASK; + } + + this->UnpPtr = UnpPtr; + } +} + + +int Unpack::DecodeNumber(struct Decode *Dec) +{ + unsigned int Bits; + unsigned int BitField=getbits() & 0xfffe; + if (BitFieldDecodeLen[8]) + if (BitFieldDecodeLen[4]) + if (BitFieldDecodeLen[2]) + if (BitFieldDecodeLen[1]) + Bits=1; + else + Bits=2; + else + if (BitFieldDecodeLen[3]) + Bits=3; + else + Bits=4; + else + if (BitFieldDecodeLen[6]) + if (BitFieldDecodeLen[5]) + Bits=5; + else + Bits=6; + else + if (BitFieldDecodeLen[7]) + Bits=7; + else + Bits=8; + else + if (BitFieldDecodeLen[12]) + if (BitFieldDecodeLen[10]) + if (BitFieldDecodeLen[9]) + Bits=9; + else + Bits=10; + else + if (BitFieldDecodeLen[11]) + Bits=11; + else + Bits=12; + else + if (BitFieldDecodeLen[14]) + if (BitFieldDecodeLen[13]) + Bits=13; + else + Bits=14; + else + Bits=15; + + unsigned int N=Dec->DecodePos[Bits]+((BitField-Dec->DecodeLen[Bits-1])>>(16-Bits)); + if (N>=Dec->MaxNum) + N=0; + // do after reading values, to allow better instruction scheduling + addbits(Bits); + return(Dec->DecodeNum[N]); +} + +const +static unsigned char LDecode[]={0,1,2,3,4,5,6,7,8,10,12,14,16,20,24,28,32,40,48,56,64,80,96,112,128,160,192,224}; +const +static unsigned char LBits[]= {0,0,0,0,0,0,0,0,1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5}; +static int DDecode[DC]; +static byte DBits[DC]; +const +static int DBitLengthCounts[]= {4,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,14,0,12}; +const +static unsigned char SDDecode[]={0,4,8,16,32,64,128,192}; +const +static unsigned char SDBits[]= {2,2,3, 4, 5, 6, 6, 6}; + +void Unpack::init_tables() +{ + if (DDecode[1]==0) + { + int Dist=0,BitLength=0,Slot=0; + for (int I=0;IReadBorder) + { + if (!UnpReadBuf()) + break; + } + if (((WrPtr-UnpPtr) & MAXWINMASK)<260 && WrPtr!=UnpPtr) + { + UnpWriteBuf(); + if (WrittenFileSize>DestUnpSize) + return; + if (Suspended) + { + FileExtracted=false; + return; + } + } + if (UnpBlockType==BLOCK_PPM) + { + int Ch=PPM.DecodeChar(); + if (Ch==-1) + { + PPM.CleanUp(); + + // turn off PPM compression mode in case of error, so UnRAR will + // call PPM.DecodeInit in case it needs to turn it on back later. + UnpBlockType=BLOCK_LZ; + break; + } + if (Ch==PPMEscChar) + { + int NextCh=PPM.DecodeChar(); + if (NextCh==0) + { + if (!ReadTables()) + break; + continue; + } + if (NextCh==2 || NextCh==-1) + break; + if (NextCh==3) + { + if (!ReadVMCodePPM()) + break; + continue; + } + if (NextCh==4) + { + unsigned int Distance=0,Length; + Length = 0; // avoids warning + bool Failed=false; + for (int I=0;I<4 && !Failed;I++) + { + int Ch=PPM.DecodeChar(); + if (Ch==-1) + Failed=true; + else + if (I==3) + Length=(byte)Ch; + else + Distance=(Distance<<8)+(byte)Ch; + } + if (Failed) + break; + +#ifdef _MSC_VER + // avoid a warning about uninitialized 'Length' variable + #pragma warning( disable : 4701 ) +#endif + CopyString(Length+32,Distance+2); + continue; + } + if (NextCh==5) + { + int Length=PPM.DecodeChar(); + if (Length==-1) + break; + CopyString(Length+4,1); + continue; + } + } + Window[UnpPtr++]=Ch; + continue; + } + + int Number=DecodeNumber((struct Decode *)&LD); + if (Number<256) + { + Window[UnpPtr++]=(byte)Number; + continue; + } + if (Number>=271) + { + int Length=LDecode[Number-=271]+3; + if ((Bits=LBits[Number])>0) + { + Length+=getbits()>>(16-Bits); + addbits(Bits); + } + + int DistNumber=DecodeNumber((struct Decode *)&DD); + unsigned int Distance=DDecode[DistNumber]+1; + if ((Bits=DBits[DistNumber])>0) + { + if (DistNumber>9) + { + if (Bits>4) + { + Distance+=((getbits()>>(20-Bits))<<4); + addbits(Bits-4); + } + if (LowDistRepCount>0) + { + LowDistRepCount--; + Distance+=PrevLowDist; + } + else + { + int LowDist=DecodeNumber((struct Decode *)&LDD); + if (LowDist==16) + { + LowDistRepCount=LOW_DIST_REP_COUNT-1; + Distance+=PrevLowDist; + } + else + { + Distance+=LowDist; + PrevLowDist=LowDist; + } + } + } + else + { + Distance+=getbits()>>(16-Bits); + addbits(Bits); + } + } + + if (Distance>=0x2000) + { + Length++; + if (Distance>=0x40000L) + Length++; + } + + InsertOldDist(Distance); + InsertLastMatch(Length,Distance); + CopyString(Length,Distance); + continue; + } + if (Number==256) + { + if (!ReadEndOfBlock()) + break; + continue; + } + if (Number==257) + { + if (!ReadVMCode()) + break; + continue; + } + if (Number==258) + { + if (LastLength!=0) + CopyString(LastLength,LastDist); + continue; + } + if (Number<263) + { + int DistNum=Number-259; + unsigned int Distance=OldDist[DistNum]; + for (int I=DistNum;I>0;I--) + OldDist[I]=OldDist[I-1]; + OldDist[0]=Distance; + + int LengthNumber=DecodeNumber((struct Decode *)&RD); + int Length=LDecode[LengthNumber]+2; + if ((Bits=LBits[LengthNumber])>0) + { + Length+=getbits()>>(16-Bits); + addbits(Bits); + } + InsertLastMatch(Length,Distance); + CopyString(Length,Distance); + continue; + } + if (Number<272) + { + unsigned int Distance=SDDecode[Number-=263]+1; + if ((Bits=SDBits[Number])>0) + { + Distance+=getbits()>>(16-Bits); + addbits(Bits); + } + InsertOldDist(Distance); + InsertLastMatch(2,Distance); + CopyString(2,Distance); + continue; + } + } + UnpWriteBuf(); +} + + +bool Unpack::ReadEndOfBlock() +{ + unsigned int BitField=getbits(); + bool NewTable,NewFile=false; + if (BitField & 0x8000) + { + NewTable=true; + addbits(1); + } + else + { + NewFile=true; + NewTable=(BitField & 0x4000); + addbits(2); + } + TablesRead=!NewTable; + return !(NewFile || NewTable && !ReadTables()); +} + + +bool Unpack::ReadVMCode() +{ + unsigned int FirstByte=getbits()>>8; + addbits(8); + int Length=(FirstByte & 7)+1; + if (Length==7) + { + Length=(getbits()>>8)+7; + addbits(8); + } + else + if (Length==8) + { + Length=getbits(); + addbits(16); + } + VMCode.Alloc( Length ); + for (int I=0;I=ReadTop-1 && !UnpReadBuf() && I>8; + addbits(8); + } + return(AddVMCode(FirstByte,&VMCode[0],Length)); +} + + +bool Unpack::ReadVMCodePPM() +{ + unsigned int FirstByte=PPM.DecodeChar(); + if ((int)FirstByte==-1) + return(false); + int Length=(FirstByte & 7)+1; + if (Length==7) + { + int B1=PPM.DecodeChar(); + if (B1==-1) + return(false); + Length=B1+7; + } + else + if (Length==8) + { + int B1=PPM.DecodeChar(); + if (B1==-1) + return(false); + int B2=PPM.DecodeChar(); + if (B2==-1) + return(false); + Length=B1*256+B2; + } + VMCode.Alloc( Length ); + for (int I=0;IFilters.Size() || FiltPos>OldFilterLengths.Size()) + return(false); + LastFilter=FiltPos; + bool NewFilter=(FiltPos==Filters.Size()); + + delete LastStackFilter; + LastStackFilter = NULL; + UnpackFilter *StackFilter=new UnpackFilter(&ErrHandler); + LastStackFilter = StackFilter; + if ( !StackFilter ) + ErrHandler.MemoryError(); + + UnpackFilter *Filter; + if (NewFilter) // new filter code, never used before since VM reset + { + // too many different filters, corrupt archive + if (FiltPos>1024) + return(false); + + Filters.Add(1); + Filters[Filters.Size()-1]=Filter=new UnpackFilter(&ErrHandler); + if ( !Filter ) + ErrHandler.MemoryError(); + StackFilter->ParentFilter=Filters.Size()-1; + OldFilterLengths.Add(1); + Filter->ExecCount=0; + } + else // filter was used in the past + { + Filter=Filters[FiltPos]; + StackFilter->ParentFilter=FiltPos; + Filter->ExecCount++; + } + + int EmptyCount=0; + { + for (int I=0;I0) + PrgStack[I]=NULL; + } + } + if (EmptyCount==0) + { + PrgStack.Add(1); + EmptyCount=1; + } + int StackPos=PrgStack.Size()-EmptyCount; + PrgStack[StackPos]=StackFilter; + LastStackFilter = NULL; + StackFilter->ExecCount=Filter->ExecCount; + + uint BlockStart=RarVM::ReadData(Inp); + if (FirstByte & 0x40) + BlockStart+=258; + StackFilter->BlockStart=(BlockStart+UnpPtr)&MAXWINMASK; + if (FirstByte & 0x20) + StackFilter->BlockLength=RarVM::ReadData(Inp); + else + StackFilter->BlockLength=FiltPosNextWindow=WrPtr!=UnpPtr && ((WrPtr-UnpPtr)&MAXWINMASK)<=BlockStart; + +// DebugLog("\nNextWindow: UnpPtr=%08x WrPtr=%08x BlockStart=%08x",UnpPtr,WrPtr,BlockStart); + + OldFilterLengths[FiltPos]=StackFilter->BlockLength; + + memset(StackFilter->Prg.InitR,0,sizeof(StackFilter->Prg.InitR)); + StackFilter->Prg.InitR[3]=VM_GLOBALMEMADDR; + StackFilter->Prg.InitR[4]=StackFilter->BlockLength; + StackFilter->Prg.InitR[5]=StackFilter->ExecCount; + + if (FirstByte & 0x10) // set registers to optional parameters if any + { + unsigned int InitMask=Inp.fgetbits()>>9; + Inp.faddbits(7); + for (int I=0;I<7;I++) + if (InitMask & (1<Prg.InitR[I]=RarVM::ReadData(Inp); + } + + if (NewFilter) + { + uint VMCodeSize=RarVM::ReadData(Inp); + if (VMCodeSize>=0x10000 || VMCodeSize==0) + return(false); + VMCode.Alloc( VMCodeSize ); + for (int I=0;I>8; + Inp.faddbits(8); + } + VM.Prepare(&VMCode[0],VMCodeSize,&Filter->Prg); + VMCode.Reset(); + } + StackFilter->Prg.AltCmd=&Filter->Prg.Cmd[0]; + StackFilter->Prg.CmdCount=Filter->Prg.CmdCount; + + int StaticDataSize=Filter->Prg.StaticData.Size(); + if (StaticDataSize>0 && StaticDataSizePrg.StaticData.Add(StaticDataSize); + memcpy(&StackFilter->Prg.StaticData[0],&Filter->Prg.StaticData[0],StaticDataSize); + } + + if (StackFilter->Prg.GlobalData.Size()Prg.GlobalData.Reset(); + StackFilter->Prg.GlobalData.Add(VM_FIXEDGLOBALSIZE); + } + byte *GlobalData=&StackFilter->Prg.GlobalData[0]; + for (int I=0;I<7;I++) + VM.SetLowEndianValue((uint *)&GlobalData[I*4],StackFilter->Prg.InitR[I]); + VM.SetLowEndianValue((uint *)&GlobalData[0x1c],StackFilter->BlockLength); + VM.SetLowEndianValue((uint *)&GlobalData[0x20],0); + VM.SetLowEndianValue((uint *)&GlobalData[0x2c],StackFilter->ExecCount); + memset(&GlobalData[0x30],0,16); + + if (FirstByte & 8) // put data block passed as parameter if any + { + if (Inp.Overflow(3)) + return(false); + uint DataSize=RarVM::ReadData(Inp); + if (DataSize>VM_GLOBALMEMSIZE-VM_FIXEDGLOBALSIZE) + return(false); + unsigned int CurSize=StackFilter->Prg.GlobalData.Size(); + if (CurSizePrg.GlobalData.Add(DataSize+VM_FIXEDGLOBALSIZE-CurSize); + byte *GlobalData=&StackFilter->Prg.GlobalData[VM_FIXEDGLOBALSIZE]; + for (int I=0;I>8; + Inp.faddbits(8); + } + } + Inp.InitBitInput(); + return(true); +} + + +bool Unpack::UnpReadBuf() +{ + int DataSize=ReadTop-InAddr; + if (DataSize<0) + return(false); + if (InAddr>BitInput::MAX_SIZE/2) + { + if (DataSize>0) + memmove(InBuf,InBuf+InAddr,DataSize); + InAddr=0; + ReadTop=DataSize; + } + else + DataSize=ReadTop; + int ReadCode=UnpIO->UnpRead(InBuf+DataSize,(BitInput::MAX_SIZE-DataSize)&~0xf); + if (ReadCode>0) + ReadTop+=ReadCode; + ReadBorder=ReadTop-30; + return(ReadCode!=-1); +} + + +void Unpack::UnpWriteBuf() +{ + unsigned int WrittenBorder=WrPtr; + unsigned int WriteSize=(UnpPtr-WrittenBorder)&MAXWINMASK; + for (int I=0;INextWindow) + { + flt->NextWindow=false; + continue; + } + unsigned int BlockStart=flt->BlockStart; + unsigned int BlockLength=flt->BlockLength; + if (((BlockStart-WrittenBorder)&MAXWINMASK)ParentFilter]->Prg; + VM_PreparedProgram *Prg=&flt->Prg; + + if (ParentPrg->GlobalData.Size()>VM_FIXEDGLOBALSIZE) + { + // copy global data from previous script execution if any + Prg->GlobalData.Alloc(ParentPrg->GlobalData.Size()); + memcpy(&Prg->GlobalData[VM_FIXEDGLOBALSIZE],&ParentPrg->GlobalData[VM_FIXEDGLOBALSIZE],ParentPrg->GlobalData.Size()-VM_FIXEDGLOBALSIZE); + } + + ExecuteCode(Prg); + + if (Prg->GlobalData.Size()>VM_FIXEDGLOBALSIZE) + { + // save global data for next script execution + if (ParentPrg->GlobalData.Size()GlobalData.Size()) + ParentPrg->GlobalData.Alloc(Prg->GlobalData.Size()); + memcpy(&ParentPrg->GlobalData[VM_FIXEDGLOBALSIZE],&Prg->GlobalData[VM_FIXEDGLOBALSIZE],Prg->GlobalData.Size()-VM_FIXEDGLOBALSIZE); + } + else + ParentPrg->GlobalData.Reset(); + + byte *FilteredData=Prg->FilteredData; + unsigned int FilteredDataSize=Prg->FilteredDataSize; + + delete PrgStack[I]; + PrgStack[I]=NULL; + while (I+1BlockStart!=BlockStart || + NextFilter->BlockLength!=FilteredDataSize || NextFilter->NextWindow) + break; + + // apply several filters to same data block + + VM.SetMemory(0,FilteredData,FilteredDataSize); + + VM_PreparedProgram *ParentPrg=&Filters[NextFilter->ParentFilter]->Prg; + VM_PreparedProgram *NextPrg=&NextFilter->Prg; + + if (ParentPrg->GlobalData.Size()>VM_FIXEDGLOBALSIZE) + { + // copy global data from previous script execution if any + NextPrg->GlobalData.Alloc(ParentPrg->GlobalData.Size()); + memcpy(&NextPrg->GlobalData[VM_FIXEDGLOBALSIZE],&ParentPrg->GlobalData[VM_FIXEDGLOBALSIZE],ParentPrg->GlobalData.Size()-VM_FIXEDGLOBALSIZE); + } + + ExecuteCode(NextPrg); + + if (NextPrg->GlobalData.Size()>VM_FIXEDGLOBALSIZE) + { + // save global data for next script execution + if (ParentPrg->GlobalData.Size()GlobalData.Size()) + ParentPrg->GlobalData.Alloc(NextPrg->GlobalData.Size()); + memcpy(&ParentPrg->GlobalData[VM_FIXEDGLOBALSIZE],&NextPrg->GlobalData[VM_FIXEDGLOBALSIZE],NextPrg->GlobalData.Size()-VM_FIXEDGLOBALSIZE); + } + else + ParentPrg->GlobalData.Reset(); + + FilteredData=NextPrg->FilteredData; + FilteredDataSize=NextPrg->FilteredDataSize; + I++; + delete PrgStack[I]; + PrgStack[I]=NULL; + } + UnpIO->UnpWrite(FilteredData,FilteredDataSize); + UnpSomeRead=true; + WrittenFileSize+=FilteredDataSize; + WrittenBorder=BlockEnd; + WriteSize=(UnpPtr-WrittenBorder)&MAXWINMASK; + } + else + { + for (int J=I;JNextWindow) + flt->NextWindow=false; + } + WrPtr=WrittenBorder; + return; + } + } + } + + UnpWriteArea(WrittenBorder,UnpPtr); + WrPtr=UnpPtr; +} + + +void Unpack::ExecuteCode(VM_PreparedProgram *Prg) +{ + if (Prg->GlobalData.Size()>0) + { + Prg->InitR[6]=int64to32(WrittenFileSize); + VM.SetLowEndianValue((uint *)&Prg->GlobalData[0x24],int64to32(WrittenFileSize)); + VM.SetLowEndianValue((uint *)&Prg->GlobalData[0x28],int64to32(WrittenFileSize>>31>>1)); + VM.Execute(Prg); + } +} + + +void Unpack::UnpWriteArea(unsigned int StartPtr,unsigned int EndPtr) +{ + if (EndPtr!=StartPtr) + UnpSomeRead=true; + if (EndPtr=DestUnpSize) + return; + int WriteSize=Size; + Int64 LeftToWrite=DestUnpSize-WrittenFileSize; + if (WriteSize>LeftToWrite) + WriteSize=int64to32(LeftToWrite); + UnpIO->UnpWrite(Data,WriteSize); + WrittenFileSize+=Size; +} + + +bool Unpack::ReadTables() +{ + byte BitLength[BC]; + unsigned char Table[HUFF_TABLE_SIZE]; + if (InAddr>ReadTop-25) + if (!UnpReadBuf()) + return(false); + faddbits((8-InBit)&7); + unsigned int BitField=fgetbits(); + if (BitField & 0x8000) + { + UnpBlockType=BLOCK_PPM; + return(PPM.DecodeInit(this,PPMEscChar)); + } + UnpBlockType=BLOCK_LZ; + + PrevLowDist=0; + LowDistRepCount=0; + + if (!(BitField & 0x4000)) + memset(UnpOldTable,0,sizeof(UnpOldTable)); + faddbits(2); + { + for (int I=0;I> 12); + faddbits(4); + if (Length==15) + { + int ZeroCount=(byte)(fgetbits() >> 12); + faddbits(4); + if (ZeroCount==0) + BitLength[I]=15; + else + { + ZeroCount+=2; + while (ZeroCount-- > 0 && IReadTop-5) + if (!UnpReadBuf()) + return(false); + int Number=DecodeNumber((struct Decode *)&BD); + if (Number<16) + { + Table[I]=(Number+UnpOldTable[I]) & 0xf; + I++; + } + else + if (Number<18) + { + int N; + if (Number==16) + { + N=(fgetbits() >> 13)+3; + faddbits(3); + } + else + { + N=(fgetbits() >> 9)+11; + faddbits(7); + } + while (N-- > 0 && I> 13)+3; + faddbits(3); + } + else + { + N=(fgetbits() >> 9)+11; + faddbits(7); + } + while (N-- > 0 && IReadTop) + return(false); + MakeDecodeTables(&Table[0],(struct Decode *)&LD,NC); + MakeDecodeTables(&Table[NC],(struct Decode *)&DD,DC); + MakeDecodeTables(&Table[NC+DC],(struct Decode *)&LDD,LDC); + MakeDecodeTables(&Table[NC+DC+LDC],(struct Decode *)&RD,RC); + memcpy(UnpOldTable,Table,sizeof(UnpOldTable)); + return(true); +} + + +void Unpack::UnpInitData(int Solid) +{ + if (!Solid) + { + TablesRead=false; + memset(OldDist,0,sizeof(OldDist)); + OldDistPtr=0; + LastDist=LastLength=0; +// memset(Window,0,MAXWINSIZE); + memset(UnpOldTable,0,sizeof(UnpOldTable)); + memset(&LD,0,sizeof(LD)); + memset(&DD,0,sizeof(DD)); + memset(&LDD,0,sizeof(LDD)); + memset(&RD,0,sizeof(RD)); + memset(&BD,0,sizeof(BD)); + UnpPtr=WrPtr=0; + PPMEscChar=2; + UnpBlockType=BLOCK_LZ; + + InitFilters(); + } + InitBitInput(); + WrittenFileSize=0; + ReadTop=0; + ReadBorder=0; +#ifndef SFX_MODULE + UnpInitData20(Solid); +#endif +} + + +void Unpack::InitFilters() +{ + delete LastStackFilter; + LastStackFilter = NULL; + + OldFilterLengths.Reset(); + LastFilter=0; + { + for (int I=0;IDecodeNum,0,Size*sizeof(*Dec->DecodeNum)); + for (I=0;IDecodePos[0]=Dec->DecodeLen[0]=0,N=0,I=1;I<16;I++) + { + N=2*(N+LenCount[I]); + M=N<<(15-I); + if (M>0xFFFF) + M=0xFFFF; + Dec->DecodeLen[I]=(unsigned int)M; + TmpPos[I]=Dec->DecodePos[I]=Dec->DecodePos[I-1]+LenCount[I-1]; + } + + for (I=0;IDecodeNum[TmpPos[LenTab[I] & 0xF]++]=I; + Dec->MaxNum=Size; +} diff --git a/snesreader/unrar/unpack.hpp b/snesreader/unrar/unpack.hpp new file mode 100644 index 00000000..918fdb6c --- /dev/null +++ b/snesreader/unrar/unpack.hpp @@ -0,0 +1,227 @@ +#ifndef _RAR_UNPACK_ +#define _RAR_UNPACK_ + +enum BLOCK_TYPES {BLOCK_LZ,BLOCK_PPM}; + +struct Decode +{ + unsigned int MaxNum; + unsigned int DecodeLen[16]; + unsigned int DecodePos[16]; + unsigned int DecodeNum[2]; +}; + +struct LitDecode +{ + unsigned int MaxNum; + unsigned int DecodeLen[16]; + unsigned int DecodePos[16]; + unsigned int DecodeNum[NC]; +}; + +struct DistDecode +{ + unsigned int MaxNum; + unsigned int DecodeLen[16]; + unsigned int DecodePos[16]; + unsigned int DecodeNum[DC]; +}; + +struct LowDistDecode +{ + unsigned int MaxNum; + unsigned int DecodeLen[16]; + unsigned int DecodePos[16]; + unsigned int DecodeNum[LDC]; +}; + +struct RepDecode +{ + unsigned int MaxNum; + unsigned int DecodeLen[16]; + unsigned int DecodePos[16]; + unsigned int DecodeNum[RC]; +}; + +struct BitDecode +{ + unsigned int MaxNum; + unsigned int DecodeLen[16]; + unsigned int DecodePos[16]; + unsigned int DecodeNum[BC]; +}; + +struct UnpackFilter + : Rar_Allocator +{ + unsigned int BlockStart; + unsigned int BlockLength; + unsigned int ExecCount; + bool NextWindow; + + // position of parent filter in Filters array used as prototype for filter + // in PrgStack array. Not defined for filters in Filters array. + unsigned int ParentFilter; + + VM_PreparedProgram Prg; + UnpackFilter( Rar_Error_Handler* eh ) : Prg( eh ) { } +}; + +/***************************** Unpack v 2.0 *********************************/ +struct MultDecode +{ + unsigned int MaxNum; + unsigned int DecodeLen[16]; + unsigned int DecodePos[16]; + unsigned int DecodeNum[MC20]; +}; + +struct AudioVariables +{ + int K1,K2,K3,K4,K5; + int D1,D2,D3,D4; + int LastDelta; + unsigned int Dif[11]; + unsigned int ByteCount; + int LastChar; +}; +/***************************** Unpack v 2.0 *********************************/ + + +// public so operator new/delete will be accessible, argh +class Unpack:public BitInput +{ +private: + friend class Pack; + + void Unpack29(bool Solid); + bool UnpReadBuf(); + void UnpWriteBuf(); + void ExecuteCode(VM_PreparedProgram *Prg); + void UnpWriteArea(unsigned int StartPtr,unsigned int EndPtr); + void UnpWriteData(byte *Data,int Size); + bool ReadTables(); + void MakeDecodeTables(unsigned char *LenTab,struct Decode *Dec,int Size); + int DecodeNumber(struct Decode *Dec); + void CopyString(); + inline void InsertOldDist(unsigned int Distance); + inline void InsertLastMatch(unsigned int Length,unsigned int Distance); + void UnpInitData(int Solid); + void CopyString(unsigned int Length,unsigned int Distance); + bool ReadEndOfBlock(); + bool ReadVMCode(); + bool ReadVMCodePPM(); + bool AddVMCode(unsigned int FirstByte,byte *Code,int CodeSize); + void InitFilters(); + + ComprDataIO *UnpIO; + ModelPPM PPM; + int PPMEscChar; + + Array VMCode; // here to avoid leaks + BitInput Inp; // here to avoid leaks + + RarVM VM; + + UnpackFilter* LastStackFilter; // avoids leak for stack-based filter + + /* Filters code, one entry per filter */ + Array Filters; + + /* Filters stack, several entrances of same filter are possible */ + Array PrgStack; + + /* lengths of preceding blocks, one length per filter. Used to reduce + size required to write block length if lengths are repeating */ + Array OldFilterLengths; + + int LastFilter; + + bool TablesRead; + struct LitDecode LD; + struct DistDecode DD; + struct LowDistDecode LDD; + struct RepDecode RD; + struct BitDecode BD; + + unsigned int OldDist[4],OldDistPtr; + unsigned int LastDist,LastLength; + + unsigned int UnpPtr,WrPtr; + + int ReadTop; + int ReadBorder; + + unsigned char UnpOldTable[HUFF_TABLE_SIZE]; + + int UnpBlockType; + + byte *Window; + bool ExternalWindow; + + + Int64 DestUnpSize; + + enum { Suspended = false }; // original source could never set to true + bool UnpAllBuf; + bool UnpSomeRead; + Int64 WrittenFileSize; + bool FileExtracted; + + int PrevLowDist,LowDistRepCount; + + /***************************** Unpack v 1.5 *********************************/ + void Unpack15(bool Solid); + void ShortLZ(); + void LongLZ(); + void HuffDecode(); + void GetFlagsBuf(); + void OldUnpInitData(int Solid); + void InitHuff(); + void CorrHuff(unsigned int *CharSet,unsigned int *NumToPlace); + void OldCopyString(unsigned int Distance,unsigned int Length); + unsigned int DecodeNum(int Num,unsigned int StartPos, + const unsigned int *DecTab,const unsigned int *PosTab); + void OldUnpWriteBuf(); + + unsigned int ChSet[256],ChSetA[256],ChSetB[256],ChSetC[256]; + unsigned int Place[256],PlaceA[256],PlaceB[256],PlaceC[256]; + unsigned int NToPl[256],NToPlB[256],NToPlC[256]; + unsigned int FlagBuf,AvrPlc,AvrPlcB,AvrLn1,AvrLn2,AvrLn3; + int Buf60,NumHuf,StMode,LCount,FlagsCnt; + unsigned int Nhfb,Nlzb,MaxDist3; + /***************************** Unpack v 1.5 *********************************/ + + /***************************** Unpack v 2.0 *********************************/ + void Unpack20(bool Solid); + struct MultDecode MD[4]; + unsigned char UnpOldTable20[MC20*4]; + int UnpAudioBlock,UnpChannels,UnpCurChannel,UnpChannelDelta; + void CopyString20(unsigned int Length,unsigned int Distance); + bool ReadTables20(); + void UnpInitData20(int Solid); + void ReadLastTables(); + byte DecodeAudio(int Delta); + struct AudioVariables AudV[4]; + /***************************** Unpack v 2.0 *********************************/ + +public: + Rar_Error_Handler& ErrHandler; + byte const* window_wrptr() const { return &Window [WrPtr & MAXWINMASK]; } + + static void init_tables(); + Unpack(ComprDataIO *DataIO); + ~Unpack(); + void Init(byte *Window=NULL); + void DoUnpack(int Method,bool Solid); + void SetDestSize(Int64 DestSize) {DestUnpSize=DestSize;FileExtracted=false;} + + unsigned int GetChar() + { + if (InAddr>BitInput::MAX_SIZE-30) + UnpReadBuf(); + return(InBuf[InAddr++]); + } +}; + +#endif diff --git a/snesreader/unrar/unpack15.cpp b/snesreader/unrar/unpack15.cpp new file mode 100644 index 00000000..b2a63c05 --- /dev/null +++ b/snesreader/unrar/unpack15.cpp @@ -0,0 +1,532 @@ +// #included by unpack.cpp +#ifdef RAR_COMMON_HPP +#define STARTL1 2 +const +static unsigned int DecL1[]={0x8000,0xa000,0xc000,0xd000,0xe000,0xea00, + 0xee00,0xf000,0xf200,0xf200,0xffff}; +const +static unsigned int PosL1[]={0,0,0,2,3,5,7,11,16,20,24,32,32}; + +#define STARTL2 3 +const +static unsigned int DecL2[]={0xa000,0xc000,0xd000,0xe000,0xea00,0xee00, + 0xf000,0xf200,0xf240,0xffff}; +const +static unsigned int PosL2[]={0,0,0,0,5,7,9,13,18,22,26,34,36}; + +#define STARTHF0 4 +const +static unsigned int DecHf0[]={0x8000,0xc000,0xe000,0xf200,0xf200,0xf200, + 0xf200,0xf200,0xffff}; +const +static unsigned int PosHf0[]={0,0,0,0,0,8,16,24,33,33,33,33,33}; + + +#define STARTHF1 5 +const +static unsigned int DecHf1[]={0x2000,0xc000,0xe000,0xf000,0xf200,0xf200, + 0xf7e0,0xffff}; +const +static unsigned int PosHf1[]={0,0,0,0,0,0,4,44,60,76,80,80,127}; + + +#define STARTHF2 5 +const +static unsigned int DecHf2[]={0x1000,0x2400,0x8000,0xc000,0xfa00,0xffff, + 0xffff,0xffff}; +const +static unsigned int PosHf2[]={0,0,0,0,0,0,2,7,53,117,233,0,0}; + + +#define STARTHF3 6 +const +static unsigned int DecHf3[]={0x800,0x2400,0xee00,0xfe80,0xffff,0xffff, + 0xffff}; +const +static unsigned int PosHf3[]={0,0,0,0,0,0,0,2,16,218,251,0,0}; + + +#define STARTHF4 8 +const +static unsigned int DecHf4[]={0xff00,0xffff,0xffff,0xffff,0xffff,0xffff}; +const +static unsigned int PosHf4[]={0,0,0,0,0,0,0,0,0,255,0,0,0}; + + +void Unpack::Unpack15(bool Solid) +{ + if (Suspended) + UnpPtr=WrPtr; + else + { + UnpInitData(Solid); + OldUnpInitData(Solid); + UnpReadBuf(); + if (!Solid) + { + InitHuff(); + UnpPtr=0; + } + else + UnpPtr=WrPtr; + --DestUnpSize; + } + if (DestUnpSize>=0) + { + GetFlagsBuf(); + FlagsCnt=8; + } + + while (DestUnpSize>=0) + { + UnpPtr&=MAXWINMASK; + + if (InAddr>ReadTop-30 && !UnpReadBuf()) + break; + if (((WrPtr-UnpPtr) & MAXWINMASK)<270 && WrPtr!=UnpPtr) + { + OldUnpWriteBuf(); + if (Suspended) + return; + } + if (StMode) + { + HuffDecode(); + continue; + } + + if (--FlagsCnt < 0) + { + GetFlagsBuf(); + FlagsCnt=7; + } + + if (FlagBuf & 0x80) + { + FlagBuf<<=1; + if (Nlzb > Nhfb) + LongLZ(); + else + HuffDecode(); + } + else + { + FlagBuf<<=1; + if (--FlagsCnt < 0) + { + GetFlagsBuf(); + FlagsCnt=7; + } + if (FlagBuf & 0x80) + { + FlagBuf<<=1; + if (Nlzb > Nhfb) + HuffDecode(); + else + LongLZ(); + } + else + { + FlagBuf<<=1; + ShortLZ(); + } + } + } + OldUnpWriteBuf(); +} + + +void Unpack::OldUnpWriteBuf() +{ + if (UnpPtr!=WrPtr) + UnpSomeRead=true; + if (UnpPtrUnpWrite(&Window[WrPtr],-WrPtr & MAXWINMASK); + UnpIO->UnpWrite(Window,UnpPtr); + UnpAllBuf=true; + } + else + UnpIO->UnpWrite(&Window[WrPtr],UnpPtr-WrPtr); + WrPtr=UnpPtr; +} + + +#define GetShortLen1(pos) ((pos)==1 ? Buf60+3:ShortLen1[pos]) +#define GetShortLen2(pos) ((pos)==3 ? Buf60+3:ShortLen2[pos]) + +void Unpack::ShortLZ() +{ + const + static unsigned int ShortLen1[]={1,3,4,4,5,6,7,8,8,4,4,5,6,6,4,0}; + const + static unsigned int ShortXor1[]={0,0xa0,0xd0,0xe0,0xf0,0xf8,0xfc,0xfe, + 0xff,0xc0,0x80,0x90,0x98,0x9c,0xb0}; + const + static unsigned int ShortLen2[]={2,3,3,3,4,4,5,6,6,4,4,5,6,6,4,0}; + const + static unsigned int ShortXor2[]={0,0x40,0x60,0xa0,0xd0,0xe0,0xf0,0xf8, + 0xfc,0xc0,0x80,0x90,0x98,0x9c,0xb0}; + + + unsigned int Length,SaveLength; + unsigned int LastDistance; + unsigned int Distance; + int DistancePlace; + NumHuf=0; + + unsigned int BitField=fgetbits(); + if (LCount==2) + { + faddbits(1); + if (BitField >= 0x8000) + { + OldCopyString((unsigned int)LastDist,LastLength); + return; + } + BitField <<= 1; + LCount=0; + } + + BitField>>=8; + +// not thread safe, replaced by GetShortLen1 and GetShortLen2 macro +// ShortLen1[1]=ShortLen2[3]=Buf60+3; + + if (AvrLn1<37) + { + for (Length=0;;Length++) + if (((BitField^ShortXor1[Length]) & (~(0xff>>GetShortLen1(Length))))==0) + break; + faddbits(GetShortLen1(Length)); + } + else + { + for (Length=0;;Length++) + if (((BitField^ShortXor2[Length]) & (~(0xff>>GetShortLen2(Length))))==0) + break; + faddbits(GetShortLen2(Length)); + } + + if (Length >= 9) + { + if (Length == 9) + { + LCount++; + OldCopyString((unsigned int)LastDist,LastLength); + return; + } + if (Length == 14) + { + LCount=0; + Length=DecodeNum(fgetbits(),STARTL2,DecL2,PosL2)+5; + Distance=(fgetbits()>>1) | 0x8000; + faddbits(15); + LastLength=Length; + LastDist=Distance; + OldCopyString(Distance,Length); + return; + } + + LCount=0; + SaveLength=Length; + Distance=OldDist[(OldDistPtr-(Length-9)) & 3]; + Length=DecodeNum(fgetbits(),STARTL1,DecL1,PosL1)+2; + if (Length==0x101 && SaveLength==10) + { + Buf60 ^= 1; + return; + } + if (Distance > 256) + Length++; + if (Distance >= MaxDist3) + Length++; + + OldDist[OldDistPtr++]=Distance; + OldDistPtr = OldDistPtr & 3; + LastLength=Length; + LastDist=Distance; + OldCopyString(Distance,Length); + return; + } + + LCount=0; + AvrLn1 += Length; + AvrLn1 -= AvrLn1 >> 4; + + DistancePlace=DecodeNum(fgetbits(),STARTHF2,DecHf2,PosHf2) & 0xff; + Distance=ChSetA[DistancePlace]; + if (--DistancePlace != -1) + { + PlaceA[Distance]--; + LastDistance=ChSetA[DistancePlace]; + PlaceA[LastDistance]++; + ChSetA[DistancePlace+1]=LastDistance; + ChSetA[DistancePlace]=Distance; + } + Length+=2; + OldDist[OldDistPtr++] = ++Distance; + OldDistPtr = OldDistPtr & 3; + LastLength=Length; + LastDist=Distance; + OldCopyString(Distance,Length); +} + + +void Unpack::LongLZ() +{ + unsigned int Length; + unsigned int Distance; + unsigned int DistancePlace,NewDistancePlace; + unsigned int OldAvr2,OldAvr3; + + NumHuf=0; + Nlzb+=16; + if (Nlzb > 0xff) + { + Nlzb=0x90; + Nhfb >>= 1; + } + OldAvr2=AvrLn2; + + unsigned int BitField=fgetbits(); + if (AvrLn2 >= 122) + Length=DecodeNum(BitField,STARTL2,DecL2,PosL2); + else + if (AvrLn2 >= 64) + Length=DecodeNum(BitField,STARTL1,DecL1,PosL1); + else + if (BitField < 0x100) + { + Length=BitField; + faddbits(16); + } + else + { + for (Length=0;((BitField<> 5; + + BitField=fgetbits(); + if (AvrPlcB > 0x28ff) + DistancePlace=DecodeNum(BitField,STARTHF2,DecHf2,PosHf2); + else + if (AvrPlcB > 0x6ff) + DistancePlace=DecodeNum(BitField,STARTHF1,DecHf1,PosHf1); + else + DistancePlace=DecodeNum(BitField,STARTHF0,DecHf0,PosHf0); + + AvrPlcB += DistancePlace; + AvrPlcB -= AvrPlcB >> 8; + while (1) + { + Distance = ChSetB[DistancePlace & 0xff]; + NewDistancePlace = NToPlB[Distance++ & 0xff]++; + if (!(Distance & 0xff)) + CorrHuff(ChSetB,NToPlB); + else + break; + } + + ChSetB[DistancePlace]=ChSetB[NewDistancePlace]; + ChSetB[NewDistancePlace]=Distance; + + Distance=((Distance & 0xff00) | (fgetbits() >> 8)) >> 1; + faddbits(7); + + OldAvr3=AvrLn3; + if (Length!=1 && Length!=4) + if (Length==0 && Distance <= MaxDist3) + { + AvrLn3++; + AvrLn3 -= AvrLn3 >> 8; + } + else + if (AvrLn3 > 0) + AvrLn3--; + Length+=3; + if (Distance >= MaxDist3) + Length++; + if (Distance <= 256) + Length+=8; + if (OldAvr3 > 0xb0 || AvrPlc >= 0x2a00 && OldAvr2 < 0x40) + MaxDist3=0x7f00; + else + MaxDist3=0x2001; + OldDist[OldDistPtr++]=Distance; + OldDistPtr = OldDistPtr & 3; + LastLength=Length; + LastDist=Distance; + OldCopyString(Distance,Length); +} + + +void Unpack::HuffDecode() +{ + unsigned int CurByte,NewBytePlace; + unsigned int Length; + unsigned int Distance; + int BytePlace; + + unsigned int BitField=fgetbits(); + + if (AvrPlc > 0x75ff) + BytePlace=DecodeNum(BitField,STARTHF4,DecHf4,PosHf4); + else + if (AvrPlc > 0x5dff) + BytePlace=DecodeNum(BitField,STARTHF3,DecHf3,PosHf3); + else + if (AvrPlc > 0x35ff) + BytePlace=DecodeNum(BitField,STARTHF2,DecHf2,PosHf2); + else + if (AvrPlc > 0x0dff) + BytePlace=DecodeNum(BitField,STARTHF1,DecHf1,PosHf1); + else + BytePlace=DecodeNum(BitField,STARTHF0,DecHf0,PosHf0); + BytePlace&=0xff; + if (StMode) + { + if (BytePlace==0 && BitField > 0xfff) + BytePlace=0x100; + if (--BytePlace==-1) + { + BitField=fgetbits(); + faddbits(1); + if (BitField & 0x8000) + { + NumHuf=StMode=0; + return; + } + else + { + Length = (BitField & 0x4000) ? 4 : 3; + faddbits(1); + Distance=DecodeNum(fgetbits(),STARTHF2,DecHf2,PosHf2); + Distance = (Distance << 5) | (fgetbits() >> 11); + faddbits(5); + OldCopyString(Distance,Length); + return; + } + } + } + else + if (NumHuf++ >= 16 && FlagsCnt==0) + StMode=1; + AvrPlc += BytePlace; + AvrPlc -= AvrPlc >> 8; + Nhfb+=16; + if (Nhfb > 0xff) + { + Nhfb=0x90; + Nlzb >>= 1; + } + + Window[UnpPtr++]=(byte)(ChSet[BytePlace]>>8); + --DestUnpSize; + + while (1) + { + CurByte=ChSet[BytePlace]; + NewBytePlace=NToPl[CurByte++ & 0xff]++; + if ((CurByte & 0xff) > 0xa1) + CorrHuff(ChSet,NToPl); + else + break; + } + + ChSet[BytePlace]=ChSet[NewBytePlace]; + ChSet[NewBytePlace]=CurByte; +} + + +void Unpack::GetFlagsBuf() +{ + unsigned int Flags,NewFlagsPlace; + unsigned int FlagsPlace=DecodeNum(fgetbits(),STARTHF2,DecHf2,PosHf2); + + while (1) + { + Flags=ChSetC[FlagsPlace]; + FlagBuf=Flags>>8; + NewFlagsPlace=NToPlC[Flags++ & 0xff]++; + if ((Flags & 0xff) != 0) + break; + CorrHuff(ChSetC,NToPlC); + } + + ChSetC[FlagsPlace]=ChSetC[NewFlagsPlace]; + ChSetC[NewFlagsPlace]=Flags; +} + + +void Unpack::OldUnpInitData(int Solid) +{ + if (!Solid) + { + AvrPlcB=AvrLn1=AvrLn2=AvrLn3=NumHuf=Buf60=0; + AvrPlc=0x3500; + MaxDist3=0x2001; + Nhfb=Nlzb=0x80; + } + FlagsCnt=0; + FlagBuf=0; + StMode=0; + LCount=0; + ReadTop=0; +} + + +void Unpack::InitHuff() +{ + for (unsigned int I=0;I<256;I++) + { + Place[I]=PlaceA[I]=PlaceB[I]=I; + PlaceC[I]=(~I+1) & 0xff; + ChSet[I]=ChSetB[I]=I<<8; + ChSetA[I]=I; + ChSetC[I]=((~I+1) & 0xff)<<8; + } + memset(NToPl,0,sizeof(NToPl)); + memset(NToPlB,0,sizeof(NToPlB)); + memset(NToPlC,0,sizeof(NToPlC)); + CorrHuff(ChSetB,NToPlB); +} + + +void Unpack::CorrHuff(unsigned int *CharSet,unsigned int *NumToPlace) +{ + int I,J; + for (I=7;I>=0;I--) + for (J=0;J<32;J++,CharSet++) + *CharSet=(*CharSet & ~0xff) | I; + memset(NumToPlace,0,sizeof(NToPl)); + for (I=6;I>=0;I--) + NumToPlace[I]=(7-I)*32; +} + + +void Unpack::OldCopyString(unsigned int Distance,unsigned int Length) +{ + DestUnpSize-=Length; + while (Length--) + { + Window[UnpPtr]=Window[(UnpPtr-Distance) & MAXWINMASK]; + UnpPtr=(UnpPtr+1) & MAXWINMASK; + } +} + + +unsigned int Unpack::DecodeNum(int Num,unsigned int StartPos, + const unsigned int *DecTab,const unsigned int *PosTab) +{ + int I; + for (Num&=0xfff0,I=0;DecTab[I]<=Num;I++) + StartPos++; + faddbits(StartPos); + return(((Num-(I ? DecTab[I-1]:0))>>(16-StartPos))+PosTab[StartPos]); +} +#endif diff --git a/snesreader/unrar/unpack20.cpp b/snesreader/unrar/unpack20.cpp new file mode 100644 index 00000000..0896d1ce --- /dev/null +++ b/snesreader/unrar/unpack20.cpp @@ -0,0 +1,394 @@ +// #included by unpack.cpp +#ifdef RAR_COMMON_HPP +#include "rar.hpp" + +// Presumably these optimizations give similar speedup as those for CopyString in unpack.cpp +void Unpack::CopyString20(unsigned int Length,unsigned int Distance) +{ + LastDist=OldDist[OldDistPtr++ & 3]=Distance; + LastLength=Length; + DestUnpSize-=Length; + + unsigned UnpPtr = this->UnpPtr; // cache in register + byte* const Window = this->Window; // cache in register + + unsigned int DestPtr=UnpPtr-Distance; + if (UnpPtrUnpPtr += Length; + if ( Distance < Length ) // can't use memcpy when source and dest overlap + { + Window[UnpPtr++]=Window[DestPtr++]; + Window[UnpPtr++]=Window[DestPtr++]; + while (Length>2) + { + Length--; + Window[UnpPtr++]=Window[DestPtr++]; + } + } + else + { + memcpy( &Window[UnpPtr], &Window[DestPtr], Length ); + } + } + else + { + while (Length--) + { + Window[UnpPtr]=Window[DestPtr++ & MAXWINMASK]; + UnpPtr=(UnpPtr+1) & MAXWINMASK; + } + this->UnpPtr = UnpPtr; + } +} + + +void Unpack::Unpack20(bool Solid) +{ + const + static unsigned char LDecode[]={0,1,2,3,4,5,6,7,8,10,12,14,16,20,24,28,32,40,48,56,64,80,96,112,128,160,192,224}; + const + static unsigned char LBits[]= {0,0,0,0,0,0,0,0,1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5}; + const + static int DDecode[]={0,1,2,3,4,6,8,12,16,24,32,48,64,96,128,192,256,384,512,768,1024,1536,2048,3072,4096,6144,8192,12288,16384,24576,32768U,49152U,65536,98304,131072,196608,262144,327680,393216,458752,524288,589824,655360,720896,786432,851968,917504,983040}; + const + static unsigned char DBits[]= {0,0,0,0,1,1,2, 2, 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, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16}; + const + static unsigned char SDDecode[]={0,4,8,16,32,64,128,192}; + const + static unsigned char SDBits[]= {2,2,3, 4, 5, 6, 6, 6}; + unsigned int Bits; + + if (Suspended) + UnpPtr=WrPtr; + else + { + UnpInitData(Solid); + if (!UnpReadBuf()) + return; + if (!Solid) + if (!ReadTables20()) + return; + --DestUnpSize; + } + + while (is64plus(DestUnpSize)) + { + UnpPtr&=MAXWINMASK; + + if (InAddr>ReadTop-30) + if (!UnpReadBuf()) + break; + if (((WrPtr-UnpPtr) & MAXWINMASK)<270 && WrPtr!=UnpPtr) + { + OldUnpWriteBuf(); + if (Suspended) + return; + } + if (UnpAudioBlock) + { + int AudioNumber=DecodeNumber((struct Decode *)&MD[UnpCurChannel]); + + if (AudioNumber==256) + { + if (!ReadTables20()) + break; + continue; + } + Window[UnpPtr++]=DecodeAudio(AudioNumber); + if (++UnpCurChannel==UnpChannels) + UnpCurChannel=0; + --DestUnpSize; + continue; + } + + int Number=DecodeNumber((struct Decode *)&LD); + if (Number<256) + { + Window[UnpPtr++]=(byte)Number; + --DestUnpSize; + continue; + } + if (Number>269) + { + int Length=LDecode[Number-=270]+3; + if ((Bits=LBits[Number])>0) + { + Length+=getbits()>>(16-Bits); + addbits(Bits); + } + + int DistNumber=DecodeNumber((struct Decode *)&DD); + unsigned int Distance=DDecode[DistNumber]+1; + if ((Bits=DBits[DistNumber])>0) + { + Distance+=getbits()>>(16-Bits); + addbits(Bits); + } + + if (Distance>=0x2000) + { + Length++; + if (Distance>=0x40000L) + Length++; + } + + CopyString20(Length,Distance); + continue; + } + if (Number==269) + { + if (!ReadTables20()) + break; + continue; + } + if (Number==256) + { + CopyString20(LastLength,LastDist); + continue; + } + if (Number<261) + { + unsigned int Distance=OldDist[(OldDistPtr-(Number-256)) & 3]; + int LengthNumber=DecodeNumber((struct Decode *)&RD); + int Length=LDecode[LengthNumber]+2; + if ((Bits=LBits[LengthNumber])>0) + { + Length+=getbits()>>(16-Bits); + addbits(Bits); + } + if (Distance>=0x101) + { + Length++; + if (Distance>=0x2000) + { + Length++; + if (Distance>=0x40000) + Length++; + } + } + CopyString20(Length,Distance); + continue; + } + if (Number<270) + { + unsigned int Distance=SDDecode[Number-=261]+1; + if ((Bits=SDBits[Number])>0) + { + Distance+=getbits()>>(16-Bits); + addbits(Bits); + } + CopyString20(2,Distance); + continue; + } + } + ReadLastTables(); + OldUnpWriteBuf(); +} + + +bool Unpack::ReadTables20() +{ + byte BitLength[BC20]; + unsigned char Table[MC20*4]; + int TableSize,N,I; + if (InAddr>ReadTop-25) + if (!UnpReadBuf()) + return(false); + unsigned int BitField=getbits(); + UnpAudioBlock=(BitField & 0x8000); + + if (!(BitField & 0x4000)) + memset(UnpOldTable20,0,sizeof(UnpOldTable20)); + addbits(2); + + if (UnpAudioBlock) + { + UnpChannels=((BitField>>12) & 3)+1; + if (UnpCurChannel>=UnpChannels) + UnpCurChannel=0; + addbits(2); + TableSize=MC20*UnpChannels; + } + else + TableSize=NC20+DC20+RC20; + + for (I=0;I> 12); + addbits(4); + } + MakeDecodeTables(BitLength,(struct Decode *)&BD,BC20); + I=0; + while (IReadTop-5) + if (!UnpReadBuf()) + return(false); + int Number=DecodeNumber((struct Decode *)&BD); + if (Number<16) + { + Table[I]=(Number+UnpOldTable20[I]) & 0xf; + I++; + } + else + if (Number==16) + { + N=(getbits() >> 14)+3; + addbits(2); + while (N-- > 0 && I> 13)+3; + addbits(3); + } + else + { + N=(getbits() >> 9)+11; + addbits(7); + } + while (N-- > 0 && IReadTop) + return(true); + if (UnpAudioBlock) + for (I=0;I=InAddr+5) + if (UnpAudioBlock) + { + if (DecodeNumber((struct Decode *)&MD[UnpCurChannel])==256) + ReadTables20(); + } + else + if (DecodeNumber((struct Decode *)&LD)==269) + ReadTables20(); +} + + +void Unpack::UnpInitData20(int Solid) +{ + if (!Solid) + { + UnpAudioBlock=UnpChannelDelta=UnpCurChannel=0; + UnpChannels=1; + + memset(AudV,0,sizeof(AudV)); + memset(UnpOldTable20,0,sizeof(UnpOldTable20)); + memset(MD,0,sizeof(MD)); + } +} + + +byte Unpack::DecodeAudio(int Delta) +{ + struct AudioVariables *V=&AudV[UnpCurChannel]; + V->ByteCount++; + V->D4=V->D3; + V->D3=V->D2; + V->D2=V->LastDelta-V->D1; + V->D1=V->LastDelta; + int PCh=8*V->LastChar+V->K1*V->D1+V->K2*V->D2+V->K3*V->D3+V->K4*V->D4+V->K5*UnpChannelDelta; + PCh=(PCh>>3) & 0xFF; + + unsigned int Ch=PCh-Delta; + + int D=((signed char)Delta)<<3; + + V->Dif[0]+=abs(D); + V->Dif[1]+=abs(D-V->D1); + V->Dif[2]+=abs(D+V->D1); + V->Dif[3]+=abs(D-V->D2); + V->Dif[4]+=abs(D+V->D2); + V->Dif[5]+=abs(D-V->D3); + V->Dif[6]+=abs(D+V->D3); + V->Dif[7]+=abs(D-V->D4); + V->Dif[8]+=abs(D+V->D4); + V->Dif[9]+=abs(D-UnpChannelDelta); + V->Dif[10]+=abs(D+UnpChannelDelta); + + UnpChannelDelta=V->LastDelta=(signed char)(Ch-V->LastChar); + V->LastChar=Ch; + + if ((V->ByteCount & 0x1F)==0) + { + unsigned int MinDif=V->Dif[0],NumMinDif=0; + V->Dif[0]=0; + for (int I=1;IDif)/sizeof(V->Dif[0]);I++) + { + if (V->Dif[I]Dif[I]; + NumMinDif=I; + } + V->Dif[I]=0; + } + switch(NumMinDif) + { + case 1: + if (V->K1>=-16) + V->K1--; + break; + case 2: + if (V->K1<16) + V->K1++; + break; + case 3: + if (V->K2>=-16) + V->K2--; + break; + case 4: + if (V->K2<16) + V->K2++; + break; + case 5: + if (V->K3>=-16) + V->K3--; + break; + case 6: + if (V->K3<16) + V->K3++; + break; + case 7: + if (V->K4>=-16) + V->K4--; + break; + case 8: + if (V->K4<16) + V->K4++; + break; + case 9: + if (V->K5>=-16) + V->K5--; + break; + case 10: + if (V->K5<16) + V->K5++; + break; + } + } + return((byte)Ch); +} +#endif diff --git a/snesreader/unrar/unrar.cpp b/snesreader/unrar/unrar.cpp new file mode 100644 index 00000000..2c3baa7b --- /dev/null +++ b/snesreader/unrar/unrar.cpp @@ -0,0 +1,350 @@ +// unrar_core 3.8.5. http://www.slack.net/~ant/ + +#include "unrar.h" + +#include "rar.hpp" +#include + +// This source code is a heavily modified version based on the unrar package. +// It may not be used to develop a RAR (WinRAR) compatible archiver. +// See unrar/license.txt for copyright and licensing. + +// Same as printf when debugging, otherwise 0 +#ifndef debug_printf + #define debug_printf 1 ? (void)0 : (void) +#endif + +// If expr != unrar_ok, returns its value +#define RETURN_ERR( expr ) \ + do {\ + unrar_err_t err_;\ + if ( (err_ = (expr)) != unrar_ok )\ + return err_;\ + } while ( 0 ) + + +// Receives errors reported from deep within library. +// MUST be macro. +#define NONLOCAL_ERROR( p ) \ + setjmp( p->Arc.jmp_env ) + +void Rar_Error_Handler::ReportError( unrar_err_t err ) +{ + if ( err ) + longjmp( jmp_env, err ); +} + +void Rar_Error_Handler::MemoryError() +{ + ReportError( unrar_err_memory ); +} + + +//// Internal + +unrar_t::unrar_t() : + Buffer( &Arc ) +{ + Arc.user_read = NULL; + Arc.user_write = NULL; + Arc.Tell_ = 0; + Arc.write_error = unrar_ok; + data_ = NULL; + own_data_ = NULL; + close_file = NULL; + FileCount = 0; + Unp = NULL; + + unrar_init(); +} + +unrar_t::~unrar_t() +{ + if ( Arc.write_error ) { } + + if ( close_file ) + close_file( Arc.user_read_data ); + + delete Unp; + + free( own_data_ ); +} + +// True if current file is compressed in way that affects solid extraction state +static inline bool solid_file( const unrar_t* p ) +{ + return p->Arc.Solid && + p->Arc.NewLhd.Method != 0x30 && + p->Arc.NewLhd.FullPackSize != 0; +} + +static void update_solid_pos( unrar_t* p ) +{ + if ( p->solid_pos == p->Arc.CurBlockPos ) + p->solid_pos = p->Arc.NextBlockPos; +} + +static unrar_err_t extract_( unrar_t* p, unrar_write_func user_write, void* user_data ) +{ + assert( !p->done ); + assert( !solid_file( p ) || p->solid_pos == p->Arc.CurBlockPos ); + + if ( p->Arc.write_error ) { } + p->Arc.write_error = unrar_ok; + p->Arc.user_write = user_write; + p->Arc.user_write_data = user_data; + RETURN_ERR( p->ExtractCurrentFile( user_write == NULL ) ); + p->Arc.user_write = NULL; + RETURN_ERR( p->Arc.write_error ); + + update_solid_pos( p ); + + return unrar_ok; +} + +static unrar_err_t skip_solid( unrar_t* p ) +{ + if ( !solid_file( p ) ) + { + update_solid_pos( p ); + return unrar_ok; + } + + return extract_( p, NULL, NULL ); +} + +static inline bool IsLink(uint Attr) +{ + return((Attr & 0xF000)==0xA000); +} + +static unrar_err_t next_( unrar_t* p, bool skipping_solid ) +{ + if ( p->done ) + return unrar_err_arc_eof; + + free( p->own_data_ ); + p->own_data_ = NULL; + p->data_ = NULL; + + for (;;) + { + p->Arc.SeekToNext(); + unrar_err_t const err = p->Arc.ReadHeader(); + if ( err != unrar_err_arc_eof ) + RETURN_ERR( err ); + //else + // debug_printf( "unrar: Didn't end with ENDARC_HEAD\n" ); // rar -en causes this + + HEADER_TYPE const type = (HEADER_TYPE) p->Arc.GetHeaderType(); + + if ( err != unrar_ok || type == ENDARC_HEAD ) + { + p->done = true; + break; + } + + if ( type != FILE_HEAD ) + { + // Skip non-files + if ( type != NEWSUB_HEAD && type != PROTECT_HEAD && type != SIGN_HEAD && type != SUB_HEAD ) + debug_printf( "unrar: Skipping unknown block type: %X\n", (unsigned) type ); + + update_solid_pos( p ); + } + else + { + // Update even for non-solid files, in case it's not extracted + if ( !solid_file( p ) ) + update_solid_pos( p ); + + if ( p->Arc.IsArcLabel() ) + { + // Ignore labels + } + else if ( IsLink( p->Arc.NewLhd.FileAttr ) ) + { + // Ignore links + + p->update_first_file_pos(); + p->FileCount++; // Links are treated as files + } + else if ( p->Arc.IsArcDir() ) + { + // Ignore directories + } + else + { + p->info.size = p->Arc.NewLhd.UnpSize; + p->info.name = p->Arc.NewLhd.FileName; + p->info.name_w = p->Arc.NewLhd.FileNameW; + p->info.is_unicode = (p->Arc.NewLhd.Flags & LHD_UNICODE) != 0; + p->info.dos_date = p->Arc.NewLhd.mtime.time; + p->info.crc = p->Arc.NewLhd.FileCRC; + p->info.is_crc32 = !p->Arc.OldFormat; + + // Stop for files + break; + } + + // Original code assumed that non-file items were never solid compressed + check( !solid_file( p ) ); + + // Skip non-file solid-compressed items (original code assumed there were none) + if ( skipping_solid ) + RETURN_ERR( skip_solid( p ) ); + } + } + + return unrar_ok; +} + +static unrar_err_t open_( unrar_t* p, unrar_read_func read, void* user_data ) +{ + p->Arc.user_read = read; + p->Arc.user_read_data = user_data; + + RETURN_ERR( p->Arc.IsArchive() ); + + p->begin_pos = p->Arc.NextBlockPos; + p->solid_pos = p->Arc.NextBlockPos; + p->first_file_pos = INT_MAX; + p->done = false; + + return unrar_ok; +} + + +//// Interface + + // Needed when user read throws exception + struct unrar_ptr { + unrar_t* p; + unrar_ptr() { p = NULL; } + ~unrar_ptr() { delete p; } + }; + +unrar_err_t unrar_open_custom( unrar_t** impl_out, unrar_read_func read, void* user_data ) +{ + *impl_out = NULL; + + unrar_ptr ptr; + ptr.p = new unrar_t; + if ( !ptr.p ) + return unrar_err_memory; + + RETURN_ERR( NONLOCAL_ERROR( ptr.p ) ); + RETURN_ERR( open_( ptr.p, read, user_data ) ); + RETURN_ERR( next_( ptr.p, false ) ); + + *impl_out = ptr.p; + ptr.p = NULL; + + //delete ptr.p; // done automatically at end of function + + return unrar_ok; +} + +void unrar_close( unrar_t* ar ) +{ + delete ar; +} + +unrar_bool unrar_done( const unrar_t* p ) +{ + return p->done; +} + +unrar_err_t unrar_next( unrar_t* p ) +{ + assert( !unrar_done( p ) ); + + RETURN_ERR( NONLOCAL_ERROR( p ) ); + return next_( p, false ); +} + +const unrar_info_t* unrar_info( unrar_t const* p ) +{ + assert( !unrar_done( p ) ); + + return &p->info; +} + +unrar_pos_t unrar_tell( const unrar_t* p ) +{ + return p->Arc.CurBlockPos; +} + +unrar_err_t unrar_seek( unrar_t* p, unrar_pos_t n ) +{ + p->Arc.NextBlockPos = n; + p->done = false; + p->FileCount = (n <= p->first_file_pos ? 0 : 1); + + return unrar_next( p ); +} + +unrar_err_t unrar_rewind( unrar_t* p ) +{ + return unrar_seek( p, p->begin_pos ); +} + +unrar_err_t unrar_try_extract( const unrar_t* p ) +{ + assert( !unrar_done( p ) ); + + return ((unrar_t*) p)->ExtractCurrentFile( true, true ); +} + + static unrar_err_t reopen( unrar_t* p ) + { + // Save and restore archive reader + unrar_read_func read = p->Arc.user_read; + void* user_data = p->Arc.user_read_data; + + void (*close_file)( void* ) = p->close_file; + p->close_file = NULL; + + p->~unrar_t(); + new (p) unrar_t; + + p->close_file = close_file; + + return open_( p, read, user_data ); + } + +unrar_err_t unrar_extract_custom( unrar_t* p, unrar_write_func user_write, void* user_data ) +{ + assert( !unrar_done( p ) ); + + RETURN_ERR( NONLOCAL_ERROR( p ) ); + + if ( solid_file( p ) ) + { + unrar_pos_t pos = p->Arc.CurBlockPos; + if ( p->solid_pos != pos ) + { + // Next file to solid extract isn't current one + + if ( p->solid_pos > pos ) + RETURN_ERR( reopen( p ) ); + else + p->Arc.NextBlockPos = p->solid_pos; + + RETURN_ERR( next_( p, true ) ); + + // Keep extracting until solid position is at desired file + while ( !p->done && p->solid_pos < pos ) + { + RETURN_ERR( skip_solid( p ) ); + RETURN_ERR( next_( p, true ) ); + } + + // Be sure we're at right file + if ( p->solid_pos != pos || p->Arc.CurBlockPos != pos ) + return unrar_err_corrupt; + } + } + + return extract_( p, user_write, user_data ); +} diff --git a/snesreader/unrar/unrar.h b/snesreader/unrar/unrar.h new file mode 100644 index 00000000..470bc146 --- /dev/null +++ b/snesreader/unrar/unrar.h @@ -0,0 +1,164 @@ +/** RAR archive scanning and extraction \file */ + +/* unrar_core 3.8.5 */ +#ifndef UNRAR_H +#define UNRAR_H + +#include +#include + +#if !defined (UNRAR_NO_LONG_LONG) && defined (LLONG_MAX) + typedef long long unrar_long_long; +#else + typedef long unrar_long_long; +#endif + +#ifdef __cplusplus + extern "C" { +#endif + + +/** Error code, or 0 if function was successful. See Errors for more. Except +where noted, once an operation returns an error, that archive should not be +used any further, other than with unrar_close(). */ +#ifndef unrar_err_t /* (#ifndef allows better testing of library) */ + typedef int unrar_err_t; +#endif + +/** First parameter of most functions is unrar_t*, or const unrar_t* if nothing +is changed. */ +typedef struct unrar_t unrar_t; + +/** File position */ +typedef unrar_long_long unrar_pos_t; + +/** Boolean, where 0 is false and 1 is true */ +typedef int unrar_bool; + + +/******** Open/close ********/ + +/** Initializes static tables used by library. Automatically called by +unrar_open(). OK to call more than once. */ +void unrar_init( void ); + +/** Opens archive and points *out at it. If error, sets *out to NULL. */ +unrar_err_t unrar_open( unrar_t** out, const char path [] ); + +/** User archive read callback. When called, user_data is a copy of that passed +to unrar_open_custom(). Callback must do the following: Read avail bytes from +file at offset pos and set *count to avail, where avail is the lesser of *count +and file_size-pos. Put read bytes into *out and return unrar_ok. If fewer than +avail bytes could be read successfully, return a non-zero error code. */ +typedef unrar_err_t (*unrar_read_func)( void* user_data, + void* out, int* count, unrar_pos_t pos ); + +/** Same as unrar_open(), except data is read using supplied function rather +than from file. */ +unrar_err_t unrar_open_custom( unrar_t** unrar_out, + unrar_read_func, void* user_data ); + +/** Closes archive and frees memory. OK to pass NULL. */ +void unrar_close( unrar_t* ); + + +/******** Scanning ********/ + +/** True if at end of archive. Must be called after unrar_open() or +unrar_rewind(), as an archive might contain no files. */ +unrar_bool unrar_done( const unrar_t* ); + +/** Goes to next file in archive. If there are no more files, unrar_done() will +now return true. */ +unrar_err_t unrar_next( unrar_t* ); + +/** Goes back to first file in archive, as if it were just opened with +unrar_open(). */ +unrar_err_t unrar_rewind( unrar_t* ); + +/** Position of current file in archive. Will never return zero. */ +unrar_pos_t unrar_tell( const unrar_t* ); + +/** Returns to file at previously-saved position. */ +unrar_err_t unrar_seek( unrar_t*, unrar_pos_t ); + + +/**** Info ****/ + +/** Information about current file */ +typedef struct unrar_info_t +{ + unrar_pos_t size; /**< Uncompressed size */ + const char* name; /**< Name, in Unicode if is_unicode is true */ + const wchar_t* name_w; /**< Name in Unicode, "" if unavailable */ + unrar_bool is_unicode; /**< True if name is Unicode (UTF-8) */ + unsigned int dos_date; /**< Date in DOS-style format, 0 if unavailable */ + unsigned int crc; /**< Checksum; algorithm depends on archive */ + unrar_bool is_crc32; /**< True if crc is CRC-32 */ +} unrar_info_t; + +/** Information about current file. Pointer is valid until unrar_next(), +unrar_rewind(), unrar_seek(), or unrar_close(). */ +const unrar_info_t* unrar_info( const unrar_t* ); + + +/**** Extraction ****/ + +/** Returns unrar_ok if current file can be extracted, otherwise error +indicating why it can't be extracted (too new/old compression algorithm, +encrypted, segmented). Archive is still usable if this returns error, +just the current file can't be extracted. */ +unrar_err_t unrar_try_extract( const unrar_t* ); + +/** Extracts at most size bytes from current file into out. If file is larger, +discards excess bytes. If file is smaller, only writes unrar_size() bytes. */ +unrar_err_t unrar_extract( unrar_t*, void* out, unrar_pos_t size ); + +/** Extracts data to memory and returns pointer to it in *out. Pointer is +valid until unrar_next(), unrar_rewind(), unrar_seek(), or unrar_close(). OK to +call more than once for same file. Optimized to avoid allocating memory when +entire file will already be kept in internal window. */ +unrar_err_t unrar_extract_mem( unrar_t* p, void const** out ); + +/** User extracted data write callback. When called, user_data is a copy of +that passed to unrar_extract_custom(). Callback must do the following: Write +count bytes from *in to wherever extracted data goes and return unrar_ok. If +data cannot be written successfully, return a non-zero error code. */ +typedef unrar_err_t (*unrar_write_func)( void* user_data, + const void* in, int count ); + +/** Extracts current file and writes data using supplied function. Any error +it returns will be returned by this function, and archive will still be +usable. */ +unrar_err_t unrar_extract_custom( unrar_t*, + unrar_write_func, void* user_data ); + + +/******** Errors ********/ + +/** Error string associated with unrar error code. Always returns valid +pointer to a C string; never returns NULL. Returns "" for unrar_ok. */ +const char* unrar_err_str( unrar_err_t ); + +enum { + unrar_ok = 0,/**< No error; success. Guaranteed to be zero. */ + unrar_err_memory = 1,/**< Out of memory */ + unrar_err_open = 2,/**< Couldn't open file (not found/permissions) */ + unrar_err_not_arc = 3,/**< Not a RAR archive */ + unrar_err_corrupt = 4,/**< Archive is corrupt */ + unrar_err_io = 5,/**< Read failed */ + unrar_err_arc_eof = 6,/**< At end of archive; no more files */ + unrar_err_encrypted = 7,/**< Encryption not supported */ + unrar_err_segmented = 8,/**< Segmentation not supported */ + unrar_err_huge = 9,/**< Huge (2GB+) archives not supported */ + unrar_err_old_algo = 10,/**< Compressed with unsupported old algorithm */ + unrar_err_new_algo = 11,/**< Compressed with unsupported new algorithm */ + unrar_next_err = 100/**< Errors range from 0 to unrar_next_err-1 */ +}; + + +#ifdef __cplusplus + } +#endif + +#endif diff --git a/snesreader/unrar/unrar_misc.cpp b/snesreader/unrar/unrar_misc.cpp new file mode 100644 index 00000000..a0a6551a --- /dev/null +++ b/snesreader/unrar/unrar_misc.cpp @@ -0,0 +1,170 @@ +// Misc functions outside the core interface + +#include "unrar.h" + +#include "rar.hpp" +#include + +// This source code is a heavily modified version based on the unrar package. +// It may not be used to develop a RAR (WinRAR) compatible archiver. +// See unrar/license.txt for copyright and licensing. + +void unrar_init() +{ + if (CRCTab[1]==0) + InitCRC(); + + Unpack::init_tables(); +} + +struct unrar_extract_mem_t +{ + char* out; + char* end; +}; + +extern "C" { + static unrar_err_t extract_write( void* user_data, const void* in, int count ) + { + unrar_extract_mem_t* p = (unrar_extract_mem_t*) user_data; + + unrar_pos_t remain = p->end - p->out; + if ( remain > 0 ) + { + if ( count > remain ) + count = remain; + + memcpy( p->out, in, count ); + p->out += count; + } + + return unrar_ok; + } +} + +unrar_err_t unrar_extract( unrar_t* p, void* out, unrar_pos_t size ) +{ + assert( !unrar_done( p ) ); + + unrar_extract_mem_t m; + m.out = (char*) out; + m.end = m.out + size; + return unrar_extract_custom( p, &extract_write, &m ); +} + +inline +static bool is_entire_file( const unrar_t* p, const void* in, int count ) +{ + return (count == p->Arc.NewLhd.UnpSize && p->Unp && in == p->Unp->window_wrptr()); +} + +extern "C" { + static unrar_err_t extract_mem( void* data, void const* in, int count ) + { + unrar_t* p = (unrar_t*) data; + + // We might have pointer to entire file + if ( !p->data_ && is_entire_file( p, in, count ) ) + { + p->data_ = in; + return unrar_ok; + } + + // We don't have it, so allocate memory to read entire file into + if ( !p->own_data_ ) + { + assert( !p->data_ ); + + unrar_pos_t size = unrar_info( p )->size; + p->own_data_ = malloc( size ? size : 1 ); + if ( !p->own_data_ ) + return unrar_err_memory; + + p->data_ = p->own_data_; + } + + memcpy( (void*) p->data_, in, count ); + p->data_ = (char*) p->data_ + count; + + return unrar_ok; + } +} + +unrar_err_t unrar_extract_mem( unrar_t* p, void const** out ) +{ + assert( !unrar_done( p ) ); + + *out = NULL; + + if ( !p->data_ ) + { + unrar_err_t err = unrar_extract_custom( p, &extract_mem, p ); + if ( err ) + return err; + } + + *out = (p->own_data_ ? p->own_data_ : p->data_); + return unrar_ok; +} + +const char* unrar_err_str( unrar_err_t err ) +{ + switch ( err ) + { + case unrar_ok: return ""; + case unrar_err_memory: return "out of memory"; + case unrar_err_open: return "couldn't open RAR archive"; + case unrar_err_not_arc: return "not a RAR archive"; + case unrar_err_corrupt: return "RAR archive is corrupt"; + case unrar_err_io: return "couldn't read/write"; + case unrar_err_arc_eof: return "unexpected end of archive"; + case unrar_err_encrypted: return "encryption not supported"; + case unrar_err_segmented: return "segmentation not supported"; + case unrar_err_huge: return "huge (2GB+) archives are not supported"; + case unrar_err_old_algo: return "compressed using older algorithm than supported"; + case unrar_err_new_algo: return "compressed using newer algorithm than supported"; + } + + assert( false ); + return "problem with RAR"; +} + +int ComprDataIO::Read( void* p, int n ) +{ + unrar_err_t err = user_read( user_read_data, p, &n, Tell_ ); + if ( err ) + ReportError( err ); + + Tell_ += n; + if ( Tell_ < 0 ) + ReportError( unrar_err_huge ); + + return n; +} + +void ComprDataIO::UnpWrite( byte* out, uint count ) +{ + if ( !SkipUnpCRC ) + { + if ( write_error == unrar_ok ) + write_error = user_write( user_write_data, out, count ); + + if ( OldFormat ) + UnpFileCRC = OldCRC( (ushort) UnpFileCRC, out, count ); + else + UnpFileCRC = CRC( UnpFileCRC, out, count ); + } +} + +int ComprDataIO::UnpRead( byte* out, uint count ) +{ + if ( count <= 0 ) + return 0; + + if ( count > (uint) UnpPackedSize ) + count = UnpPackedSize; + + int result = Read( out, count ); + UnpPackedSize -= result; + return result; +} diff --git a/snesreader/unrar/unrar_open.cpp b/snesreader/unrar/unrar_open.cpp new file mode 100644 index 00000000..f9b0c40d --- /dev/null +++ b/snesreader/unrar/unrar_open.cpp @@ -0,0 +1,45 @@ +// Separate file to avoid linking to f* functions unless user calls unrar_open_file() + +#include "unrar.h" +#include "rar.hpp" +#include + +extern "C" { + static unrar_err_t unrar_read_file( void* user_data, void* out, int* count, unrar_pos_t pos ) + { + FILE* file = (FILE*) user_data; + + // most of the time, seeking won't be necessary + if ( pos != ftell( file ) && fseek( file, pos, SEEK_SET ) != 0 ) + return unrar_err_corrupt; + + *count = (int) fread( out, 1, *count, file ); + + if ( ferror( file ) != 0 ) + return unrar_err_io; + + return unrar_ok; + } +} + +static void unrar_close_file( void* user_data ) +{ + fclose( (FILE*) user_data ); +} + +unrar_err_t unrar_open( unrar_t** arc_out, const char path [] ) +{ + *arc_out = NULL; + + FILE* file = fopen( path, "rb" ); + if ( file == NULL ) + return unrar_err_open; + + unrar_err_t err = unrar_open_custom( arc_out, &unrar_read_file, file ); + if ( err != unrar_ok ) + fclose( file ); + else + (*arc_out)->close_file = &unrar_close_file; + + return err; +} diff --git a/snesreader/unrar/whatsnew.txt b/snesreader/unrar/whatsnew.txt new file mode 100644 index 00000000..38012e9a --- /dev/null +++ b/snesreader/unrar/whatsnew.txt @@ -0,0 +1,267 @@ + + + WinRAR - What's new in the latest version + + + Version 3.80 + + 1. Added support for ZIP archives containing Unicode file names + in UTF-8 format. When creating ZIP archive, WinRAR stores + names in Unicode only if they cannot be stored correctly using + the current single byte character set. + + 2. Added decompression support for WinZip AES encrypted ZIP archives. + + 3. Improved Unicode support for RAR and ZIP archive names. + + 4. "Ask before overwrite" and "Skip existing files" update modes + are now available in archiving dialog. They allow to specify + WinRAR behavior when updating already existing files in archive. + Unlike already available "Fresh existing files only" and + "Add and update files", these new modes ignore file date + and compare only file names. + + Command line equivalents of these modes are: + + a) switch -o enables "Ask before overwrite" archiving mode; + + b) switch -o- enables "Skip existing files" archiving mode; + + c) switch -o+ enables "Overwrite all" mode (default for archiving). + + 5. New "Add to context menu" option in "Profile parameters" dialog. + If this option is on, the profile name will be displayed in Explorer + context menus allowing to activate a profile from context menu. + + 6. New -cp switch allows to select a compression profile + in command line mode. It is supported only by GUI WinRAR.exe, + not by rar.exe. + + 7. New "Options" page of archiving dialog contains the group of + settings modifying the behavior of "Delete files after archiving" + option from "General" page: + + a) Delete files. Delete files normally like in previous WinRAR + versions. + + b) Move files to Recycle Bin. Deleted files are placed to + Recycle Bin. + + Command line equivalent of this option is -dr switch. + + c) Wipe files. Before deleting file data are overwritten by + zero bytes to prevent recovery of deleted files. + + Command line equivalent of this option is -dw switch. + + All these options have an effect only if "Delete files + after archiving" is on. You can enable any of these options + in the default compression profile to change the default + behavior of "Delete files after archiving". + + 8. WinRAR "Extraction path and options" dialog is now resizable. + You can use the mouse to drag its border to the desired size + and provide more space for folder tree pane. WinRAR will store + new dimensions of this dialog. + + 9. New "Update" SFX script command and "Update mode" group + of options in "Update" page of "Advanced SFX options" dialog. + These command and options allow to check time and implement + file time based updating; + + 10. SFX script "Shortcut" command and "Add shortcut..." command + in "Advanced SFX options" dialog now allow to specify + an icon file containing an icon associated with shortcut. + + 11. New "Wipe temporary files" option in "Settings/Security" dialog + provides more secure, though slower, way to delete temporary + WinRAR files. + + 12. WinRAR and RAR display the total progress bar when unpacking + a multivolume RAR archive if all volumes are present + in the same folder. + + 13. WinRAR and RAR automatically expand names of environment + variables in list files. For example, a list file can contain + lines like: + + %windir%\*.exe + %USERPROFILE%\Desktop + + This feature is available only in Windows RAR version. + + 14. Added support of TAR archives with non-zero "extra field" data. + + 15. Added support of TAR archives, which does not contain + the end of archive entry consisting of 512 zero bytes. + + 16. Improved Unicode support when dragging files from WinRAR window. + + 17. Shift+Tab key combination can be used in main WinRAR window to + switch the input focus between interface elements (files, comment, + tree, address) in reverse order. In previous versions Shift+Tab + used the same order as Tab. + + 18. Corrected a possible WinRAR crash when opening truncated + UDF ISO files. + + + Version 3.71 + + 1. Archive names in rar.log error log file always include + the full path. + + 2. WinRAR tray icon is compatible with high DPI display modes. + + 3. If you modified a file in archive with encrypted names using + an external editor, WinRAR will not ask for archive password again + when prompting to update a file. It will use a password which + you entered when opening an archive, + + 4. Bugs fixed: + + a) switch -tl and "Set archive time to latest file time" option + could fail in previous version. Sometimes they set archive time + to current system time instead of latest file time; + + b) if -ag switch mask contained archive number, month and minute + characters, WinRAR placed 'I' character instead of minute value + into generated archive name for archive numbers exceeding 1; + + c) high ASCII names in ISO files using ISO 9660 format without + Joliet format extension were displayed incorrectly; + + d) WinRAR could crash when decompressing some of corrupt RAR archives; + + e) if "Turn PC off when done" option was set in "Convert archives" + command, WinRAR turned PC off after converting the first archive + in selected group instead of after converting the entire group; + + f) if user specified a non-existent destination path in SFX archive + in Vista, SFX could enter into infinite "create new SFX window" + loop; + + g) WinRAR could fail to unpack an individual file from subfolder + of ACE archive using the drag and drop. + + + Version 3.70 + + 1. Numerous Windows Vista compatibility changes: + + a) help format changed from old HLP to newer HTML based CHM; + + b) GUI self-extracting modules attempt to request for + administrator permissions if they cannot create destination + folder under current user account; + + c) Log file rar.log and WinRAR theme files are stored + in %APPDATA%\WinRAR folder instead of WinRAR program files folder. + + Exported settings file settings.reg is also stored + in %APPDATA%\WinRAR folder by default, but it is possible to + select another folder in "Save WinRAR settings" and "Load WinRAR + settings" dialogs. + + WinRAR searches for registration key and settings.reg + both in its program files folder and in %APPDATA%\WinRAR; + + It is possible to set the string value "AppData" in Registry key + HKEY_CURRENT_USER\Software\WinRAR\Paths to override the default + %appdata%\WinRAR path for WinRAR settings. + + For example, if you wish to store theme files in WinRAR folder, + set this value to "c:\Program Files\WinRAR". + + d) Vista compatibility changes in WinRAR shell integration; + + e) New "Request administrative access" option in "Advanced" page + of "Advanced SFX options" allows to create SFX archive, + which will request the administrative access when started + in Windows Vista. + + Command line equivalent of this option is -iadm switch. + + 2. Added support for ISO 13346 (UDF) file format. This format + is frequently used in ISO images of DVD disks. + + 3. Added Unicode support for ISO 9660 files, so WinRAR should + handle non-English file names in .iso files better. + + 4. Design changes in window displaying archiving and extraction + progress: + + a) it provides more space for file names, allowing lengthy names; + + b) it displays the current archive name in separate line, + allowing much longer archive names than before; + + c) when archiving, it displays the current compression ratio + in separate line; + + d) it can use both standard Windows and classic WinRAR progress bars. + Turn on "Windows progress bars" option in WinRAR "Settings/General" + dialog to use standard progress bars. By default this option is + on if some Windows visual style is active and off if Windows Classic + theme is selected. + + Windows progress bars are two color only, so they do not indicate + the current compression ratio. But now the ratio is displayed + in separate line; + + e) "Mode..." button moved to bottom of window. + + 5. GUI self-extracting modules support following command line + switches: + + -d set the destination path + -p specify a password + -s silent mode, hide all + -s1 same as -s + -s2 silent mode, hide start dialog + -sp specify parameters for setup program + + 6. GUI self-extracting modules do not pass the entire command line + to setup program like they did in previous versions. + If you need to get access to entire command line of SFX archive, + parse sfxcmd environment variable which contains this command line. + + 7. New switch -sc[objects] allowing to select character + sets for archive comments and list files. It replaces -fcu switch + introduced in RAR 3.60, which was removed from list of supported + switches. Now you need to specify -scuc instead of -fcu to use + Unicode comments. Unlike -fcu, -sc also supports OEM and ANSI charset. + + 8. New "Save archive copy as..." command in "File" menu. + This command may be useful if you opened an archive from Internet + directly in WinRAR and then decided to save it on local disk. + + 9. "Word wrap" command added to "View" menu of WinRAR internal viewer, + so you can change the wrapping mode of already opened viewer window. + + State of this option is not stored between viewing sessions. + If you need to change the default word wrap mode, use WinRAR + "Settings/Viewer" dialog. + + 10. Buttons "Up" and "Down" added to "Organize profiles" dialog. + Using these buttons you can change position of selected profile + in the list. + + 11. Operation progress is displayed when adding the recovery record. + + 12. If WinRAR is minimized to tray and mouse is over its icon, + WinRAR diplays a message about the current operation progress. + In previous versions it included only percent done, now it also + contains the time left information. + + 13. Console RAR displays "Calculating the control sum" message + when calculating CRC32 control sum for newly created RAR volume. + Previous versions also calculated the volume control sum, + but did it silently. + + 14. Archives history list in "File" menu allows Unicode names, + providing more reliable support for non-English archive names. + + 15. Stack overflow vulnerability has been corrected in password + processing module of console RAR and UnRAR. GUI WinRAR is not + affected. We are thankful to the iDEFENSE LABS for reporting this bug. diff --git a/snesreader/zlib/adler32.c b/snesreader/zlib/adler32.c new file mode 100644 index 00000000..007ba262 --- /dev/null +++ b/snesreader/zlib/adler32.c @@ -0,0 +1,149 @@ +/* adler32.c -- compute the Adler-32 checksum of a data stream + * Copyright (C) 1995-2004 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* @(#) $Id$ */ + +#define ZLIB_INTERNAL +#include "zlib.h" + +#define BASE 65521UL /* largest prime smaller than 65536 */ +#define NMAX 5552 +/* NMAX is the largest n such that 255n(n+1)/2 + (n+1)(BASE-1) <= 2^32-1 */ + +#define DO1(buf,i) {adler += (buf)[i]; sum2 += adler;} +#define DO2(buf,i) DO1(buf,i); DO1(buf,i+1); +#define DO4(buf,i) DO2(buf,i); DO2(buf,i+2); +#define DO8(buf,i) DO4(buf,i); DO4(buf,i+4); +#define DO16(buf) DO8(buf,0); DO8(buf,8); + +/* use NO_DIVIDE if your processor does not do division in hardware */ +#ifdef NO_DIVIDE +# define MOD(a) \ + do { \ + if (a >= (BASE << 16)) a -= (BASE << 16); \ + if (a >= (BASE << 15)) a -= (BASE << 15); \ + if (a >= (BASE << 14)) a -= (BASE << 14); \ + if (a >= (BASE << 13)) a -= (BASE << 13); \ + if (a >= (BASE << 12)) a -= (BASE << 12); \ + if (a >= (BASE << 11)) a -= (BASE << 11); \ + if (a >= (BASE << 10)) a -= (BASE << 10); \ + if (a >= (BASE << 9)) a -= (BASE << 9); \ + if (a >= (BASE << 8)) a -= (BASE << 8); \ + if (a >= (BASE << 7)) a -= (BASE << 7); \ + if (a >= (BASE << 6)) a -= (BASE << 6); \ + if (a >= (BASE << 5)) a -= (BASE << 5); \ + if (a >= (BASE << 4)) a -= (BASE << 4); \ + if (a >= (BASE << 3)) a -= (BASE << 3); \ + if (a >= (BASE << 2)) a -= (BASE << 2); \ + if (a >= (BASE << 1)) a -= (BASE << 1); \ + if (a >= BASE) a -= BASE; \ + } while (0) +# define MOD4(a) \ + do { \ + if (a >= (BASE << 4)) a -= (BASE << 4); \ + if (a >= (BASE << 3)) a -= (BASE << 3); \ + if (a >= (BASE << 2)) a -= (BASE << 2); \ + if (a >= (BASE << 1)) a -= (BASE << 1); \ + if (a >= BASE) a -= BASE; \ + } while (0) +#else +# define MOD(a) a %= BASE +# define MOD4(a) a %= BASE +#endif + +/* ========================================================================= */ +uLong ZEXPORT adler32(adler, buf, len) + uLong adler; + const Bytef *buf; + uInt len; +{ + unsigned long sum2; + unsigned n; + + /* split Adler-32 into component sums */ + sum2 = (adler >> 16) & 0xffff; + adler &= 0xffff; + + /* in case user likes doing a byte at a time, keep it fast */ + if (len == 1) { + adler += buf[0]; + if (adler >= BASE) + adler -= BASE; + sum2 += adler; + if (sum2 >= BASE) + sum2 -= BASE; + return adler | (sum2 << 16); + } + + /* initial Adler-32 value (deferred check for len == 1 speed) */ + if (buf == Z_NULL) + return 1L; + + /* in case short lengths are provided, keep it somewhat fast */ + if (len < 16) { + while (len--) { + adler += *buf++; + sum2 += adler; + } + if (adler >= BASE) + adler -= BASE; + MOD4(sum2); /* only added so many BASE's */ + return adler | (sum2 << 16); + } + + /* do length NMAX blocks -- requires just one modulo operation */ + while (len >= NMAX) { + len -= NMAX; + n = NMAX / 16; /* NMAX is divisible by 16 */ + do { + DO16(buf); /* 16 sums unrolled */ + buf += 16; + } while (--n); + MOD(adler); + MOD(sum2); + } + + /* do remaining bytes (less than NMAX, still just one modulo) */ + if (len) { /* avoid modulos if none remaining */ + while (len >= 16) { + len -= 16; + DO16(buf); + buf += 16; + } + while (len--) { + adler += *buf++; + sum2 += adler; + } + MOD(adler); + MOD(sum2); + } + + /* return recombined sums */ + return adler | (sum2 << 16); +} + +/* ========================================================================= */ +uLong ZEXPORT adler32_combine(adler1, adler2, len2) + uLong adler1; + uLong adler2; + z_off_t len2; +{ + unsigned long sum1; + unsigned long sum2; + unsigned rem; + + /* the derivation of this formula is left as an exercise for the reader */ + rem = (unsigned)(len2 % BASE); + sum1 = adler1 & 0xffff; + sum2 = rem * sum1; + MOD(sum2); + sum1 += (adler2 & 0xffff) + BASE - 1; + sum2 += ((adler1 >> 16) & 0xffff) + ((adler2 >> 16) & 0xffff) + BASE - rem; + if (sum1 > BASE) sum1 -= BASE; + if (sum1 > BASE) sum1 -= BASE; + if (sum2 > (BASE << 1)) sum2 -= (BASE << 1); + if (sum2 > BASE) sum2 -= BASE; + return sum1 | (sum2 << 16); +} diff --git a/snesreader/zlib/crc32.c b/snesreader/zlib/crc32.c new file mode 100644 index 00000000..f658a9ef --- /dev/null +++ b/snesreader/zlib/crc32.c @@ -0,0 +1,423 @@ +/* crc32.c -- compute the CRC-32 of a data stream + * Copyright (C) 1995-2005 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + * + * Thanks to Rodney Brown for his contribution of faster + * CRC methods: exclusive-oring 32 bits of data at a time, and pre-computing + * tables for updating the shift register in one step with three exclusive-ors + * instead of four steps with four exclusive-ors. This results in about a + * factor of two increase in speed on a Power PC G4 (PPC7455) using gcc -O3. + */ + +/* @(#) $Id$ */ + +/* + Note on the use of DYNAMIC_CRC_TABLE: there is no mutex or semaphore + protection on the static variables used to control the first-use generation + of the crc tables. Therefore, if you #define DYNAMIC_CRC_TABLE, you should + first call get_crc_table() to initialize the tables before allowing more than + one thread to use crc32(). + */ + +#ifdef MAKECRCH +# include +# ifndef DYNAMIC_CRC_TABLE +# define DYNAMIC_CRC_TABLE +# endif /* !DYNAMIC_CRC_TABLE */ +#endif /* MAKECRCH */ + +#include "zutil.h" /* for STDC and FAR definitions */ + +#define local static + +/* Find a four-byte integer type for crc32_little() and crc32_big(). */ +#ifndef NOBYFOUR +# ifdef STDC /* need ANSI C limits.h to determine sizes */ +# include +# define BYFOUR +# if (UINT_MAX == 0xffffffffUL) + typedef unsigned int u4; +# else +# if (ULONG_MAX == 0xffffffffUL) + typedef unsigned long u4; +# else +# if (USHRT_MAX == 0xffffffffUL) + typedef unsigned short u4; +# else +# undef BYFOUR /* can't find a four-byte integer type! */ +# endif +# endif +# endif +# endif /* STDC */ +#endif /* !NOBYFOUR */ + +/* Definitions for doing the crc four data bytes at a time. */ +#ifdef BYFOUR +# define REV(w) (((w)>>24)+(((w)>>8)&0xff00)+ \ + (((w)&0xff00)<<8)+(((w)&0xff)<<24)) + local unsigned long crc32_little OF((unsigned long, + const unsigned char FAR *, unsigned)); + local unsigned long crc32_big OF((unsigned long, + const unsigned char FAR *, unsigned)); +# define TBLS 8 +#else +# define TBLS 1 +#endif /* BYFOUR */ + +/* Local functions for crc concatenation */ +local unsigned long gf2_matrix_times OF((unsigned long *mat, + unsigned long vec)); +local void gf2_matrix_square OF((unsigned long *square, unsigned long *mat)); + +#ifdef DYNAMIC_CRC_TABLE + +local volatile int crc_table_empty = 1; +local unsigned long FAR crc_table[TBLS][256]; +local void make_crc_table OF((void)); +#ifdef MAKECRCH + local void write_table OF((FILE *, const unsigned long FAR *)); +#endif /* MAKECRCH */ +/* + Generate tables for a byte-wise 32-bit CRC calculation on the polynomial: + x^32+x^26+x^23+x^22+x^16+x^12+x^11+x^10+x^8+x^7+x^5+x^4+x^2+x+1. + + Polynomials over GF(2) are represented in binary, one bit per coefficient, + with the lowest powers in the most significant bit. Then adding polynomials + is just exclusive-or, and multiplying a polynomial by x is a right shift by + one. If we call the above polynomial p, and represent a byte as the + polynomial q, also with the lowest power in the most significant bit (so the + byte 0xb1 is the polynomial x^7+x^3+x+1), then the CRC is (q*x^32) mod p, + where a mod b means the remainder after dividing a by b. + + This calculation is done using the shift-register method of multiplying and + taking the remainder. The register is initialized to zero, and for each + incoming bit, x^32 is added mod p to the register if the bit is a one (where + x^32 mod p is p+x^32 = x^26+...+1), and the register is multiplied mod p by + x (which is shifting right by one and adding x^32 mod p if the bit shifted + out is a one). We start with the highest power (least significant bit) of + q and repeat for all eight bits of q. + + The first table is simply the CRC of all possible eight bit values. This is + all the information needed to generate CRCs on data a byte at a time for all + combinations of CRC register values and incoming bytes. The remaining tables + allow for word-at-a-time CRC calculation for both big-endian and little- + endian machines, where a word is four bytes. +*/ +local void make_crc_table() +{ + unsigned long c; + int n, k; + unsigned long poly; /* polynomial exclusive-or pattern */ + /* terms of polynomial defining this crc (except x^32): */ + static volatile int first = 1; /* flag to limit concurrent making */ + static const unsigned char p[] = {0,1,2,4,5,7,8,10,11,12,16,22,23,26}; + + /* See if another task is already doing this (not thread-safe, but better + than nothing -- significantly reduces duration of vulnerability in + case the advice about DYNAMIC_CRC_TABLE is ignored) */ + if (first) { + first = 0; + + /* make exclusive-or pattern from polynomial (0xedb88320UL) */ + poly = 0UL; + for (n = 0; n < sizeof(p)/sizeof(unsigned char); n++) + poly |= 1UL << (31 - p[n]); + + /* generate a crc for every 8-bit value */ + for (n = 0; n < 256; n++) { + c = (unsigned long)n; + for (k = 0; k < 8; k++) + c = c & 1 ? poly ^ (c >> 1) : c >> 1; + crc_table[0][n] = c; + } + +#ifdef BYFOUR + /* generate crc for each value followed by one, two, and three zeros, + and then the byte reversal of those as well as the first table */ + for (n = 0; n < 256; n++) { + c = crc_table[0][n]; + crc_table[4][n] = REV(c); + for (k = 1; k < 4; k++) { + c = crc_table[0][c & 0xff] ^ (c >> 8); + crc_table[k][n] = c; + crc_table[k + 4][n] = REV(c); + } + } +#endif /* BYFOUR */ + + crc_table_empty = 0; + } + else { /* not first */ + /* wait for the other guy to finish (not efficient, but rare) */ + while (crc_table_empty) + ; + } + +#ifdef MAKECRCH + /* write out CRC tables to crc32.h */ + { + FILE *out; + + out = fopen("crc32.h", "w"); + if (out == NULL) return; + fprintf(out, "/* crc32.h -- tables for rapid CRC calculation\n"); + fprintf(out, " * Generated automatically by crc32.c\n */\n\n"); + fprintf(out, "local const unsigned long FAR "); + fprintf(out, "crc_table[TBLS][256] =\n{\n {\n"); + write_table(out, crc_table[0]); +# ifdef BYFOUR + fprintf(out, "#ifdef BYFOUR\n"); + for (k = 1; k < 8; k++) { + fprintf(out, " },\n {\n"); + write_table(out, crc_table[k]); + } + fprintf(out, "#endif\n"); +# endif /* BYFOUR */ + fprintf(out, " }\n};\n"); + fclose(out); + } +#endif /* MAKECRCH */ +} + +#ifdef MAKECRCH +local void write_table(out, table) + FILE *out; + const unsigned long FAR *table; +{ + int n; + + for (n = 0; n < 256; n++) + fprintf(out, "%s0x%08lxUL%s", n % 5 ? "" : " ", table[n], + n == 255 ? "\n" : (n % 5 == 4 ? ",\n" : ", ")); +} +#endif /* MAKECRCH */ + +#else /* !DYNAMIC_CRC_TABLE */ +/* ======================================================================== + * Tables of CRC-32s of all single-byte values, made by make_crc_table(). + */ +#include "crc32.h" +#endif /* DYNAMIC_CRC_TABLE */ + +/* ========================================================================= + * This function can be used by asm versions of crc32() + */ +const unsigned long FAR * ZEXPORT get_crc_table() +{ +#ifdef DYNAMIC_CRC_TABLE + if (crc_table_empty) + make_crc_table(); +#endif /* DYNAMIC_CRC_TABLE */ + return (const unsigned long FAR *)crc_table; +} + +/* ========================================================================= */ +#define DO1 crc = crc_table[0][((int)crc ^ (*buf++)) & 0xff] ^ (crc >> 8) +#define DO8 DO1; DO1; DO1; DO1; DO1; DO1; DO1; DO1 + +/* ========================================================================= */ +unsigned long ZEXPORT crc32(crc, buf, len) + unsigned long crc; + const unsigned char FAR *buf; + unsigned len; +{ + if (buf == Z_NULL) return 0UL; + +#ifdef DYNAMIC_CRC_TABLE + if (crc_table_empty) + make_crc_table(); +#endif /* DYNAMIC_CRC_TABLE */ + +#ifdef BYFOUR + if (sizeof(void *) == sizeof(ptrdiff_t)) { + u4 endian; + + endian = 1; + if (*((unsigned char *)(&endian))) + return crc32_little(crc, buf, len); + else + return crc32_big(crc, buf, len); + } +#endif /* BYFOUR */ + crc = crc ^ 0xffffffffUL; + while (len >= 8) { + DO8; + len -= 8; + } + if (len) do { + DO1; + } while (--len); + return crc ^ 0xffffffffUL; +} + +#ifdef BYFOUR + +/* ========================================================================= */ +#define DOLIT4 c ^= *buf4++; \ + c = crc_table[3][c & 0xff] ^ crc_table[2][(c >> 8) & 0xff] ^ \ + crc_table[1][(c >> 16) & 0xff] ^ crc_table[0][c >> 24] +#define DOLIT32 DOLIT4; DOLIT4; DOLIT4; DOLIT4; DOLIT4; DOLIT4; DOLIT4; DOLIT4 + +/* ========================================================================= */ +local unsigned long crc32_little(crc, buf, len) + unsigned long crc; + const unsigned char FAR *buf; + unsigned len; +{ + register u4 c; + register const u4 FAR *buf4; + + c = (u4)crc; + c = ~c; + while (len && ((ptrdiff_t)buf & 3)) { + c = crc_table[0][(c ^ *buf++) & 0xff] ^ (c >> 8); + len--; + } + + buf4 = (const u4 FAR *)(const void FAR *)buf; + while (len >= 32) { + DOLIT32; + len -= 32; + } + while (len >= 4) { + DOLIT4; + len -= 4; + } + buf = (const unsigned char FAR *)buf4; + + if (len) do { + c = crc_table[0][(c ^ *buf++) & 0xff] ^ (c >> 8); + } while (--len); + c = ~c; + return (unsigned long)c; +} + +/* ========================================================================= */ +#define DOBIG4 c ^= *++buf4; \ + c = crc_table[4][c & 0xff] ^ crc_table[5][(c >> 8) & 0xff] ^ \ + crc_table[6][(c >> 16) & 0xff] ^ crc_table[7][c >> 24] +#define DOBIG32 DOBIG4; DOBIG4; DOBIG4; DOBIG4; DOBIG4; DOBIG4; DOBIG4; DOBIG4 + +/* ========================================================================= */ +local unsigned long crc32_big(crc, buf, len) + unsigned long crc; + const unsigned char FAR *buf; + unsigned len; +{ + register u4 c; + register const u4 FAR *buf4; + + c = REV((u4)crc); + c = ~c; + while (len && ((ptrdiff_t)buf & 3)) { + c = crc_table[4][(c >> 24) ^ *buf++] ^ (c << 8); + len--; + } + + buf4 = (const u4 FAR *)(const void FAR *)buf; + buf4--; + while (len >= 32) { + DOBIG32; + len -= 32; + } + while (len >= 4) { + DOBIG4; + len -= 4; + } + buf4++; + buf = (const unsigned char FAR *)buf4; + + if (len) do { + c = crc_table[4][(c >> 24) ^ *buf++] ^ (c << 8); + } while (--len); + c = ~c; + return (unsigned long)(REV(c)); +} + +#endif /* BYFOUR */ + +#define GF2_DIM 32 /* dimension of GF(2) vectors (length of CRC) */ + +/* ========================================================================= */ +local unsigned long gf2_matrix_times(mat, vec) + unsigned long *mat; + unsigned long vec; +{ + unsigned long sum; + + sum = 0; + while (vec) { + if (vec & 1) + sum ^= *mat; + vec >>= 1; + mat++; + } + return sum; +} + +/* ========================================================================= */ +local void gf2_matrix_square(square, mat) + unsigned long *square; + unsigned long *mat; +{ + int n; + + for (n = 0; n < GF2_DIM; n++) + square[n] = gf2_matrix_times(mat, mat[n]); +} + +/* ========================================================================= */ +uLong ZEXPORT crc32_combine(crc1, crc2, len2) + uLong crc1; + uLong crc2; + z_off_t len2; +{ + int n; + unsigned long row; + unsigned long even[GF2_DIM]; /* even-power-of-two zeros operator */ + unsigned long odd[GF2_DIM]; /* odd-power-of-two zeros operator */ + + /* degenerate case */ + if (len2 == 0) + return crc1; + + /* put operator for one zero bit in odd */ + odd[0] = 0xedb88320L; /* CRC-32 polynomial */ + row = 1; + for (n = 1; n < GF2_DIM; n++) { + odd[n] = row; + row <<= 1; + } + + /* put operator for two zero bits in even */ + gf2_matrix_square(even, odd); + + /* put operator for four zero bits in odd */ + gf2_matrix_square(odd, even); + + /* apply len2 zeros to crc1 (first square will put the operator for one + zero byte, eight zero bits, in even) */ + do { + /* apply zeros operator for this bit of len2 */ + gf2_matrix_square(even, odd); + if (len2 & 1) + crc1 = gf2_matrix_times(even, crc1); + len2 >>= 1; + + /* if no more bits set, then done */ + if (len2 == 0) + break; + + /* another iteration of the loop with odd and even swapped */ + gf2_matrix_square(odd, even); + if (len2 & 1) + crc1 = gf2_matrix_times(odd, crc1); + len2 >>= 1; + + /* if no more bits set, then done */ + } while (len2 != 0); + + /* return combined crc */ + crc1 ^= crc2; + return crc1; +} diff --git a/snesreader/zlib/crc32.h b/snesreader/zlib/crc32.h new file mode 100644 index 00000000..8053b611 --- /dev/null +++ b/snesreader/zlib/crc32.h @@ -0,0 +1,441 @@ +/* crc32.h -- tables for rapid CRC calculation + * Generated automatically by crc32.c + */ + +local const unsigned long FAR crc_table[TBLS][256] = +{ + { + 0x00000000UL, 0x77073096UL, 0xee0e612cUL, 0x990951baUL, 0x076dc419UL, + 0x706af48fUL, 0xe963a535UL, 0x9e6495a3UL, 0x0edb8832UL, 0x79dcb8a4UL, + 0xe0d5e91eUL, 0x97d2d988UL, 0x09b64c2bUL, 0x7eb17cbdUL, 0xe7b82d07UL, + 0x90bf1d91UL, 0x1db71064UL, 0x6ab020f2UL, 0xf3b97148UL, 0x84be41deUL, + 0x1adad47dUL, 0x6ddde4ebUL, 0xf4d4b551UL, 0x83d385c7UL, 0x136c9856UL, + 0x646ba8c0UL, 0xfd62f97aUL, 0x8a65c9ecUL, 0x14015c4fUL, 0x63066cd9UL, + 0xfa0f3d63UL, 0x8d080df5UL, 0x3b6e20c8UL, 0x4c69105eUL, 0xd56041e4UL, + 0xa2677172UL, 0x3c03e4d1UL, 0x4b04d447UL, 0xd20d85fdUL, 0xa50ab56bUL, + 0x35b5a8faUL, 0x42b2986cUL, 0xdbbbc9d6UL, 0xacbcf940UL, 0x32d86ce3UL, + 0x45df5c75UL, 0xdcd60dcfUL, 0xabd13d59UL, 0x26d930acUL, 0x51de003aUL, + 0xc8d75180UL, 0xbfd06116UL, 0x21b4f4b5UL, 0x56b3c423UL, 0xcfba9599UL, + 0xb8bda50fUL, 0x2802b89eUL, 0x5f058808UL, 0xc60cd9b2UL, 0xb10be924UL, + 0x2f6f7c87UL, 0x58684c11UL, 0xc1611dabUL, 0xb6662d3dUL, 0x76dc4190UL, + 0x01db7106UL, 0x98d220bcUL, 0xefd5102aUL, 0x71b18589UL, 0x06b6b51fUL, + 0x9fbfe4a5UL, 0xe8b8d433UL, 0x7807c9a2UL, 0x0f00f934UL, 0x9609a88eUL, + 0xe10e9818UL, 0x7f6a0dbbUL, 0x086d3d2dUL, 0x91646c97UL, 0xe6635c01UL, + 0x6b6b51f4UL, 0x1c6c6162UL, 0x856530d8UL, 0xf262004eUL, 0x6c0695edUL, + 0x1b01a57bUL, 0x8208f4c1UL, 0xf50fc457UL, 0x65b0d9c6UL, 0x12b7e950UL, + 0x8bbeb8eaUL, 0xfcb9887cUL, 0x62dd1ddfUL, 0x15da2d49UL, 0x8cd37cf3UL, + 0xfbd44c65UL, 0x4db26158UL, 0x3ab551ceUL, 0xa3bc0074UL, 0xd4bb30e2UL, + 0x4adfa541UL, 0x3dd895d7UL, 0xa4d1c46dUL, 0xd3d6f4fbUL, 0x4369e96aUL, + 0x346ed9fcUL, 0xad678846UL, 0xda60b8d0UL, 0x44042d73UL, 0x33031de5UL, + 0xaa0a4c5fUL, 0xdd0d7cc9UL, 0x5005713cUL, 0x270241aaUL, 0xbe0b1010UL, + 0xc90c2086UL, 0x5768b525UL, 0x206f85b3UL, 0xb966d409UL, 0xce61e49fUL, + 0x5edef90eUL, 0x29d9c998UL, 0xb0d09822UL, 0xc7d7a8b4UL, 0x59b33d17UL, + 0x2eb40d81UL, 0xb7bd5c3bUL, 0xc0ba6cadUL, 0xedb88320UL, 0x9abfb3b6UL, + 0x03b6e20cUL, 0x74b1d29aUL, 0xead54739UL, 0x9dd277afUL, 0x04db2615UL, + 0x73dc1683UL, 0xe3630b12UL, 0x94643b84UL, 0x0d6d6a3eUL, 0x7a6a5aa8UL, + 0xe40ecf0bUL, 0x9309ff9dUL, 0x0a00ae27UL, 0x7d079eb1UL, 0xf00f9344UL, + 0x8708a3d2UL, 0x1e01f268UL, 0x6906c2feUL, 0xf762575dUL, 0x806567cbUL, + 0x196c3671UL, 0x6e6b06e7UL, 0xfed41b76UL, 0x89d32be0UL, 0x10da7a5aUL, + 0x67dd4accUL, 0xf9b9df6fUL, 0x8ebeeff9UL, 0x17b7be43UL, 0x60b08ed5UL, + 0xd6d6a3e8UL, 0xa1d1937eUL, 0x38d8c2c4UL, 0x4fdff252UL, 0xd1bb67f1UL, + 0xa6bc5767UL, 0x3fb506ddUL, 0x48b2364bUL, 0xd80d2bdaUL, 0xaf0a1b4cUL, + 0x36034af6UL, 0x41047a60UL, 0xdf60efc3UL, 0xa867df55UL, 0x316e8eefUL, + 0x4669be79UL, 0xcb61b38cUL, 0xbc66831aUL, 0x256fd2a0UL, 0x5268e236UL, + 0xcc0c7795UL, 0xbb0b4703UL, 0x220216b9UL, 0x5505262fUL, 0xc5ba3bbeUL, + 0xb2bd0b28UL, 0x2bb45a92UL, 0x5cb36a04UL, 0xc2d7ffa7UL, 0xb5d0cf31UL, + 0x2cd99e8bUL, 0x5bdeae1dUL, 0x9b64c2b0UL, 0xec63f226UL, 0x756aa39cUL, + 0x026d930aUL, 0x9c0906a9UL, 0xeb0e363fUL, 0x72076785UL, 0x05005713UL, + 0x95bf4a82UL, 0xe2b87a14UL, 0x7bb12baeUL, 0x0cb61b38UL, 0x92d28e9bUL, + 0xe5d5be0dUL, 0x7cdcefb7UL, 0x0bdbdf21UL, 0x86d3d2d4UL, 0xf1d4e242UL, + 0x68ddb3f8UL, 0x1fda836eUL, 0x81be16cdUL, 0xf6b9265bUL, 0x6fb077e1UL, + 0x18b74777UL, 0x88085ae6UL, 0xff0f6a70UL, 0x66063bcaUL, 0x11010b5cUL, + 0x8f659effUL, 0xf862ae69UL, 0x616bffd3UL, 0x166ccf45UL, 0xa00ae278UL, + 0xd70dd2eeUL, 0x4e048354UL, 0x3903b3c2UL, 0xa7672661UL, 0xd06016f7UL, + 0x4969474dUL, 0x3e6e77dbUL, 0xaed16a4aUL, 0xd9d65adcUL, 0x40df0b66UL, + 0x37d83bf0UL, 0xa9bcae53UL, 0xdebb9ec5UL, 0x47b2cf7fUL, 0x30b5ffe9UL, + 0xbdbdf21cUL, 0xcabac28aUL, 0x53b39330UL, 0x24b4a3a6UL, 0xbad03605UL, + 0xcdd70693UL, 0x54de5729UL, 0x23d967bfUL, 0xb3667a2eUL, 0xc4614ab8UL, + 0x5d681b02UL, 0x2a6f2b94UL, 0xb40bbe37UL, 0xc30c8ea1UL, 0x5a05df1bUL, + 0x2d02ef8dUL +#ifdef BYFOUR + }, + { + 0x00000000UL, 0x191b3141UL, 0x32366282UL, 0x2b2d53c3UL, 0x646cc504UL, + 0x7d77f445UL, 0x565aa786UL, 0x4f4196c7UL, 0xc8d98a08UL, 0xd1c2bb49UL, + 0xfaefe88aUL, 0xe3f4d9cbUL, 0xacb54f0cUL, 0xb5ae7e4dUL, 0x9e832d8eUL, + 0x87981ccfUL, 0x4ac21251UL, 0x53d92310UL, 0x78f470d3UL, 0x61ef4192UL, + 0x2eaed755UL, 0x37b5e614UL, 0x1c98b5d7UL, 0x05838496UL, 0x821b9859UL, + 0x9b00a918UL, 0xb02dfadbUL, 0xa936cb9aUL, 0xe6775d5dUL, 0xff6c6c1cUL, + 0xd4413fdfUL, 0xcd5a0e9eUL, 0x958424a2UL, 0x8c9f15e3UL, 0xa7b24620UL, + 0xbea97761UL, 0xf1e8e1a6UL, 0xe8f3d0e7UL, 0xc3de8324UL, 0xdac5b265UL, + 0x5d5daeaaUL, 0x44469febUL, 0x6f6bcc28UL, 0x7670fd69UL, 0x39316baeUL, + 0x202a5aefUL, 0x0b07092cUL, 0x121c386dUL, 0xdf4636f3UL, 0xc65d07b2UL, + 0xed705471UL, 0xf46b6530UL, 0xbb2af3f7UL, 0xa231c2b6UL, 0x891c9175UL, + 0x9007a034UL, 0x179fbcfbUL, 0x0e848dbaUL, 0x25a9de79UL, 0x3cb2ef38UL, + 0x73f379ffUL, 0x6ae848beUL, 0x41c51b7dUL, 0x58de2a3cUL, 0xf0794f05UL, + 0xe9627e44UL, 0xc24f2d87UL, 0xdb541cc6UL, 0x94158a01UL, 0x8d0ebb40UL, + 0xa623e883UL, 0xbf38d9c2UL, 0x38a0c50dUL, 0x21bbf44cUL, 0x0a96a78fUL, + 0x138d96ceUL, 0x5ccc0009UL, 0x45d73148UL, 0x6efa628bUL, 0x77e153caUL, + 0xbabb5d54UL, 0xa3a06c15UL, 0x888d3fd6UL, 0x91960e97UL, 0xded79850UL, + 0xc7cca911UL, 0xece1fad2UL, 0xf5facb93UL, 0x7262d75cUL, 0x6b79e61dUL, + 0x4054b5deUL, 0x594f849fUL, 0x160e1258UL, 0x0f152319UL, 0x243870daUL, + 0x3d23419bUL, 0x65fd6ba7UL, 0x7ce65ae6UL, 0x57cb0925UL, 0x4ed03864UL, + 0x0191aea3UL, 0x188a9fe2UL, 0x33a7cc21UL, 0x2abcfd60UL, 0xad24e1afUL, + 0xb43fd0eeUL, 0x9f12832dUL, 0x8609b26cUL, 0xc94824abUL, 0xd05315eaUL, + 0xfb7e4629UL, 0xe2657768UL, 0x2f3f79f6UL, 0x362448b7UL, 0x1d091b74UL, + 0x04122a35UL, 0x4b53bcf2UL, 0x52488db3UL, 0x7965de70UL, 0x607eef31UL, + 0xe7e6f3feUL, 0xfefdc2bfUL, 0xd5d0917cUL, 0xcccba03dUL, 0x838a36faUL, + 0x9a9107bbUL, 0xb1bc5478UL, 0xa8a76539UL, 0x3b83984bUL, 0x2298a90aUL, + 0x09b5fac9UL, 0x10aecb88UL, 0x5fef5d4fUL, 0x46f46c0eUL, 0x6dd93fcdUL, + 0x74c20e8cUL, 0xf35a1243UL, 0xea412302UL, 0xc16c70c1UL, 0xd8774180UL, + 0x9736d747UL, 0x8e2de606UL, 0xa500b5c5UL, 0xbc1b8484UL, 0x71418a1aUL, + 0x685abb5bUL, 0x4377e898UL, 0x5a6cd9d9UL, 0x152d4f1eUL, 0x0c367e5fUL, + 0x271b2d9cUL, 0x3e001cddUL, 0xb9980012UL, 0xa0833153UL, 0x8bae6290UL, + 0x92b553d1UL, 0xddf4c516UL, 0xc4eff457UL, 0xefc2a794UL, 0xf6d996d5UL, + 0xae07bce9UL, 0xb71c8da8UL, 0x9c31de6bUL, 0x852aef2aUL, 0xca6b79edUL, + 0xd37048acUL, 0xf85d1b6fUL, 0xe1462a2eUL, 0x66de36e1UL, 0x7fc507a0UL, + 0x54e85463UL, 0x4df36522UL, 0x02b2f3e5UL, 0x1ba9c2a4UL, 0x30849167UL, + 0x299fa026UL, 0xe4c5aeb8UL, 0xfdde9ff9UL, 0xd6f3cc3aUL, 0xcfe8fd7bUL, + 0x80a96bbcUL, 0x99b25afdUL, 0xb29f093eUL, 0xab84387fUL, 0x2c1c24b0UL, + 0x350715f1UL, 0x1e2a4632UL, 0x07317773UL, 0x4870e1b4UL, 0x516bd0f5UL, + 0x7a468336UL, 0x635db277UL, 0xcbfad74eUL, 0xd2e1e60fUL, 0xf9ccb5ccUL, + 0xe0d7848dUL, 0xaf96124aUL, 0xb68d230bUL, 0x9da070c8UL, 0x84bb4189UL, + 0x03235d46UL, 0x1a386c07UL, 0x31153fc4UL, 0x280e0e85UL, 0x674f9842UL, + 0x7e54a903UL, 0x5579fac0UL, 0x4c62cb81UL, 0x8138c51fUL, 0x9823f45eUL, + 0xb30ea79dUL, 0xaa1596dcUL, 0xe554001bUL, 0xfc4f315aUL, 0xd7626299UL, + 0xce7953d8UL, 0x49e14f17UL, 0x50fa7e56UL, 0x7bd72d95UL, 0x62cc1cd4UL, + 0x2d8d8a13UL, 0x3496bb52UL, 0x1fbbe891UL, 0x06a0d9d0UL, 0x5e7ef3ecUL, + 0x4765c2adUL, 0x6c48916eUL, 0x7553a02fUL, 0x3a1236e8UL, 0x230907a9UL, + 0x0824546aUL, 0x113f652bUL, 0x96a779e4UL, 0x8fbc48a5UL, 0xa4911b66UL, + 0xbd8a2a27UL, 0xf2cbbce0UL, 0xebd08da1UL, 0xc0fdde62UL, 0xd9e6ef23UL, + 0x14bce1bdUL, 0x0da7d0fcUL, 0x268a833fUL, 0x3f91b27eUL, 0x70d024b9UL, + 0x69cb15f8UL, 0x42e6463bUL, 0x5bfd777aUL, 0xdc656bb5UL, 0xc57e5af4UL, + 0xee530937UL, 0xf7483876UL, 0xb809aeb1UL, 0xa1129ff0UL, 0x8a3fcc33UL, + 0x9324fd72UL + }, + { + 0x00000000UL, 0x01c26a37UL, 0x0384d46eUL, 0x0246be59UL, 0x0709a8dcUL, + 0x06cbc2ebUL, 0x048d7cb2UL, 0x054f1685UL, 0x0e1351b8UL, 0x0fd13b8fUL, + 0x0d9785d6UL, 0x0c55efe1UL, 0x091af964UL, 0x08d89353UL, 0x0a9e2d0aUL, + 0x0b5c473dUL, 0x1c26a370UL, 0x1de4c947UL, 0x1fa2771eUL, 0x1e601d29UL, + 0x1b2f0bacUL, 0x1aed619bUL, 0x18abdfc2UL, 0x1969b5f5UL, 0x1235f2c8UL, + 0x13f798ffUL, 0x11b126a6UL, 0x10734c91UL, 0x153c5a14UL, 0x14fe3023UL, + 0x16b88e7aUL, 0x177ae44dUL, 0x384d46e0UL, 0x398f2cd7UL, 0x3bc9928eUL, + 0x3a0bf8b9UL, 0x3f44ee3cUL, 0x3e86840bUL, 0x3cc03a52UL, 0x3d025065UL, + 0x365e1758UL, 0x379c7d6fUL, 0x35dac336UL, 0x3418a901UL, 0x3157bf84UL, + 0x3095d5b3UL, 0x32d36beaUL, 0x331101ddUL, 0x246be590UL, 0x25a98fa7UL, + 0x27ef31feUL, 0x262d5bc9UL, 0x23624d4cUL, 0x22a0277bUL, 0x20e69922UL, + 0x2124f315UL, 0x2a78b428UL, 0x2bbade1fUL, 0x29fc6046UL, 0x283e0a71UL, + 0x2d711cf4UL, 0x2cb376c3UL, 0x2ef5c89aUL, 0x2f37a2adUL, 0x709a8dc0UL, + 0x7158e7f7UL, 0x731e59aeUL, 0x72dc3399UL, 0x7793251cUL, 0x76514f2bUL, + 0x7417f172UL, 0x75d59b45UL, 0x7e89dc78UL, 0x7f4bb64fUL, 0x7d0d0816UL, + 0x7ccf6221UL, 0x798074a4UL, 0x78421e93UL, 0x7a04a0caUL, 0x7bc6cafdUL, + 0x6cbc2eb0UL, 0x6d7e4487UL, 0x6f38fadeUL, 0x6efa90e9UL, 0x6bb5866cUL, + 0x6a77ec5bUL, 0x68315202UL, 0x69f33835UL, 0x62af7f08UL, 0x636d153fUL, + 0x612bab66UL, 0x60e9c151UL, 0x65a6d7d4UL, 0x6464bde3UL, 0x662203baUL, + 0x67e0698dUL, 0x48d7cb20UL, 0x4915a117UL, 0x4b531f4eUL, 0x4a917579UL, + 0x4fde63fcUL, 0x4e1c09cbUL, 0x4c5ab792UL, 0x4d98dda5UL, 0x46c49a98UL, + 0x4706f0afUL, 0x45404ef6UL, 0x448224c1UL, 0x41cd3244UL, 0x400f5873UL, + 0x4249e62aUL, 0x438b8c1dUL, 0x54f16850UL, 0x55330267UL, 0x5775bc3eUL, + 0x56b7d609UL, 0x53f8c08cUL, 0x523aaabbUL, 0x507c14e2UL, 0x51be7ed5UL, + 0x5ae239e8UL, 0x5b2053dfUL, 0x5966ed86UL, 0x58a487b1UL, 0x5deb9134UL, + 0x5c29fb03UL, 0x5e6f455aUL, 0x5fad2f6dUL, 0xe1351b80UL, 0xe0f771b7UL, + 0xe2b1cfeeUL, 0xe373a5d9UL, 0xe63cb35cUL, 0xe7fed96bUL, 0xe5b86732UL, + 0xe47a0d05UL, 0xef264a38UL, 0xeee4200fUL, 0xeca29e56UL, 0xed60f461UL, + 0xe82fe2e4UL, 0xe9ed88d3UL, 0xebab368aUL, 0xea695cbdUL, 0xfd13b8f0UL, + 0xfcd1d2c7UL, 0xfe976c9eUL, 0xff5506a9UL, 0xfa1a102cUL, 0xfbd87a1bUL, + 0xf99ec442UL, 0xf85cae75UL, 0xf300e948UL, 0xf2c2837fUL, 0xf0843d26UL, + 0xf1465711UL, 0xf4094194UL, 0xf5cb2ba3UL, 0xf78d95faUL, 0xf64fffcdUL, + 0xd9785d60UL, 0xd8ba3757UL, 0xdafc890eUL, 0xdb3ee339UL, 0xde71f5bcUL, + 0xdfb39f8bUL, 0xddf521d2UL, 0xdc374be5UL, 0xd76b0cd8UL, 0xd6a966efUL, + 0xd4efd8b6UL, 0xd52db281UL, 0xd062a404UL, 0xd1a0ce33UL, 0xd3e6706aUL, + 0xd2241a5dUL, 0xc55efe10UL, 0xc49c9427UL, 0xc6da2a7eUL, 0xc7184049UL, + 0xc25756ccUL, 0xc3953cfbUL, 0xc1d382a2UL, 0xc011e895UL, 0xcb4dafa8UL, + 0xca8fc59fUL, 0xc8c97bc6UL, 0xc90b11f1UL, 0xcc440774UL, 0xcd866d43UL, + 0xcfc0d31aUL, 0xce02b92dUL, 0x91af9640UL, 0x906dfc77UL, 0x922b422eUL, + 0x93e92819UL, 0x96a63e9cUL, 0x976454abUL, 0x9522eaf2UL, 0x94e080c5UL, + 0x9fbcc7f8UL, 0x9e7eadcfUL, 0x9c381396UL, 0x9dfa79a1UL, 0x98b56f24UL, + 0x99770513UL, 0x9b31bb4aUL, 0x9af3d17dUL, 0x8d893530UL, 0x8c4b5f07UL, + 0x8e0de15eUL, 0x8fcf8b69UL, 0x8a809decUL, 0x8b42f7dbUL, 0x89044982UL, + 0x88c623b5UL, 0x839a6488UL, 0x82580ebfUL, 0x801eb0e6UL, 0x81dcdad1UL, + 0x8493cc54UL, 0x8551a663UL, 0x8717183aUL, 0x86d5720dUL, 0xa9e2d0a0UL, + 0xa820ba97UL, 0xaa6604ceUL, 0xaba46ef9UL, 0xaeeb787cUL, 0xaf29124bUL, + 0xad6fac12UL, 0xacadc625UL, 0xa7f18118UL, 0xa633eb2fUL, 0xa4755576UL, + 0xa5b73f41UL, 0xa0f829c4UL, 0xa13a43f3UL, 0xa37cfdaaUL, 0xa2be979dUL, + 0xb5c473d0UL, 0xb40619e7UL, 0xb640a7beUL, 0xb782cd89UL, 0xb2cddb0cUL, + 0xb30fb13bUL, 0xb1490f62UL, 0xb08b6555UL, 0xbbd72268UL, 0xba15485fUL, + 0xb853f606UL, 0xb9919c31UL, 0xbcde8ab4UL, 0xbd1ce083UL, 0xbf5a5edaUL, + 0xbe9834edUL + }, + { + 0x00000000UL, 0xb8bc6765UL, 0xaa09c88bUL, 0x12b5afeeUL, 0x8f629757UL, + 0x37def032UL, 0x256b5fdcUL, 0x9dd738b9UL, 0xc5b428efUL, 0x7d084f8aUL, + 0x6fbde064UL, 0xd7018701UL, 0x4ad6bfb8UL, 0xf26ad8ddUL, 0xe0df7733UL, + 0x58631056UL, 0x5019579fUL, 0xe8a530faUL, 0xfa109f14UL, 0x42acf871UL, + 0xdf7bc0c8UL, 0x67c7a7adUL, 0x75720843UL, 0xcdce6f26UL, 0x95ad7f70UL, + 0x2d111815UL, 0x3fa4b7fbUL, 0x8718d09eUL, 0x1acfe827UL, 0xa2738f42UL, + 0xb0c620acUL, 0x087a47c9UL, 0xa032af3eUL, 0x188ec85bUL, 0x0a3b67b5UL, + 0xb28700d0UL, 0x2f503869UL, 0x97ec5f0cUL, 0x8559f0e2UL, 0x3de59787UL, + 0x658687d1UL, 0xdd3ae0b4UL, 0xcf8f4f5aUL, 0x7733283fUL, 0xeae41086UL, + 0x525877e3UL, 0x40edd80dUL, 0xf851bf68UL, 0xf02bf8a1UL, 0x48979fc4UL, + 0x5a22302aUL, 0xe29e574fUL, 0x7f496ff6UL, 0xc7f50893UL, 0xd540a77dUL, + 0x6dfcc018UL, 0x359fd04eUL, 0x8d23b72bUL, 0x9f9618c5UL, 0x272a7fa0UL, + 0xbafd4719UL, 0x0241207cUL, 0x10f48f92UL, 0xa848e8f7UL, 0x9b14583dUL, + 0x23a83f58UL, 0x311d90b6UL, 0x89a1f7d3UL, 0x1476cf6aUL, 0xaccaa80fUL, + 0xbe7f07e1UL, 0x06c36084UL, 0x5ea070d2UL, 0xe61c17b7UL, 0xf4a9b859UL, + 0x4c15df3cUL, 0xd1c2e785UL, 0x697e80e0UL, 0x7bcb2f0eUL, 0xc377486bUL, + 0xcb0d0fa2UL, 0x73b168c7UL, 0x6104c729UL, 0xd9b8a04cUL, 0x446f98f5UL, + 0xfcd3ff90UL, 0xee66507eUL, 0x56da371bUL, 0x0eb9274dUL, 0xb6054028UL, + 0xa4b0efc6UL, 0x1c0c88a3UL, 0x81dbb01aUL, 0x3967d77fUL, 0x2bd27891UL, + 0x936e1ff4UL, 0x3b26f703UL, 0x839a9066UL, 0x912f3f88UL, 0x299358edUL, + 0xb4446054UL, 0x0cf80731UL, 0x1e4da8dfUL, 0xa6f1cfbaUL, 0xfe92dfecUL, + 0x462eb889UL, 0x549b1767UL, 0xec277002UL, 0x71f048bbUL, 0xc94c2fdeUL, + 0xdbf98030UL, 0x6345e755UL, 0x6b3fa09cUL, 0xd383c7f9UL, 0xc1366817UL, + 0x798a0f72UL, 0xe45d37cbUL, 0x5ce150aeUL, 0x4e54ff40UL, 0xf6e89825UL, + 0xae8b8873UL, 0x1637ef16UL, 0x048240f8UL, 0xbc3e279dUL, 0x21e91f24UL, + 0x99557841UL, 0x8be0d7afUL, 0x335cb0caUL, 0xed59b63bUL, 0x55e5d15eUL, + 0x47507eb0UL, 0xffec19d5UL, 0x623b216cUL, 0xda874609UL, 0xc832e9e7UL, + 0x708e8e82UL, 0x28ed9ed4UL, 0x9051f9b1UL, 0x82e4565fUL, 0x3a58313aUL, + 0xa78f0983UL, 0x1f336ee6UL, 0x0d86c108UL, 0xb53aa66dUL, 0xbd40e1a4UL, + 0x05fc86c1UL, 0x1749292fUL, 0xaff54e4aUL, 0x322276f3UL, 0x8a9e1196UL, + 0x982bbe78UL, 0x2097d91dUL, 0x78f4c94bUL, 0xc048ae2eUL, 0xd2fd01c0UL, + 0x6a4166a5UL, 0xf7965e1cUL, 0x4f2a3979UL, 0x5d9f9697UL, 0xe523f1f2UL, + 0x4d6b1905UL, 0xf5d77e60UL, 0xe762d18eUL, 0x5fdeb6ebUL, 0xc2098e52UL, + 0x7ab5e937UL, 0x680046d9UL, 0xd0bc21bcUL, 0x88df31eaUL, 0x3063568fUL, + 0x22d6f961UL, 0x9a6a9e04UL, 0x07bda6bdUL, 0xbf01c1d8UL, 0xadb46e36UL, + 0x15080953UL, 0x1d724e9aUL, 0xa5ce29ffUL, 0xb77b8611UL, 0x0fc7e174UL, + 0x9210d9cdUL, 0x2aacbea8UL, 0x38191146UL, 0x80a57623UL, 0xd8c66675UL, + 0x607a0110UL, 0x72cfaefeUL, 0xca73c99bUL, 0x57a4f122UL, 0xef189647UL, + 0xfdad39a9UL, 0x45115eccUL, 0x764dee06UL, 0xcef18963UL, 0xdc44268dUL, + 0x64f841e8UL, 0xf92f7951UL, 0x41931e34UL, 0x5326b1daUL, 0xeb9ad6bfUL, + 0xb3f9c6e9UL, 0x0b45a18cUL, 0x19f00e62UL, 0xa14c6907UL, 0x3c9b51beUL, + 0x842736dbUL, 0x96929935UL, 0x2e2efe50UL, 0x2654b999UL, 0x9ee8defcUL, + 0x8c5d7112UL, 0x34e11677UL, 0xa9362eceUL, 0x118a49abUL, 0x033fe645UL, + 0xbb838120UL, 0xe3e09176UL, 0x5b5cf613UL, 0x49e959fdUL, 0xf1553e98UL, + 0x6c820621UL, 0xd43e6144UL, 0xc68bceaaUL, 0x7e37a9cfUL, 0xd67f4138UL, + 0x6ec3265dUL, 0x7c7689b3UL, 0xc4caeed6UL, 0x591dd66fUL, 0xe1a1b10aUL, + 0xf3141ee4UL, 0x4ba87981UL, 0x13cb69d7UL, 0xab770eb2UL, 0xb9c2a15cUL, + 0x017ec639UL, 0x9ca9fe80UL, 0x241599e5UL, 0x36a0360bUL, 0x8e1c516eUL, + 0x866616a7UL, 0x3eda71c2UL, 0x2c6fde2cUL, 0x94d3b949UL, 0x090481f0UL, + 0xb1b8e695UL, 0xa30d497bUL, 0x1bb12e1eUL, 0x43d23e48UL, 0xfb6e592dUL, + 0xe9dbf6c3UL, 0x516791a6UL, 0xccb0a91fUL, 0x740cce7aUL, 0x66b96194UL, + 0xde0506f1UL + }, + { + 0x00000000UL, 0x96300777UL, 0x2c610eeeUL, 0xba510999UL, 0x19c46d07UL, + 0x8ff46a70UL, 0x35a563e9UL, 0xa395649eUL, 0x3288db0eUL, 0xa4b8dc79UL, + 0x1ee9d5e0UL, 0x88d9d297UL, 0x2b4cb609UL, 0xbd7cb17eUL, 0x072db8e7UL, + 0x911dbf90UL, 0x6410b71dUL, 0xf220b06aUL, 0x4871b9f3UL, 0xde41be84UL, + 0x7dd4da1aUL, 0xebe4dd6dUL, 0x51b5d4f4UL, 0xc785d383UL, 0x56986c13UL, + 0xc0a86b64UL, 0x7af962fdUL, 0xecc9658aUL, 0x4f5c0114UL, 0xd96c0663UL, + 0x633d0ffaUL, 0xf50d088dUL, 0xc8206e3bUL, 0x5e10694cUL, 0xe44160d5UL, + 0x727167a2UL, 0xd1e4033cUL, 0x47d4044bUL, 0xfd850dd2UL, 0x6bb50aa5UL, + 0xfaa8b535UL, 0x6c98b242UL, 0xd6c9bbdbUL, 0x40f9bcacUL, 0xe36cd832UL, + 0x755cdf45UL, 0xcf0dd6dcUL, 0x593dd1abUL, 0xac30d926UL, 0x3a00de51UL, + 0x8051d7c8UL, 0x1661d0bfUL, 0xb5f4b421UL, 0x23c4b356UL, 0x9995bacfUL, + 0x0fa5bdb8UL, 0x9eb80228UL, 0x0888055fUL, 0xb2d90cc6UL, 0x24e90bb1UL, + 0x877c6f2fUL, 0x114c6858UL, 0xab1d61c1UL, 0x3d2d66b6UL, 0x9041dc76UL, + 0x0671db01UL, 0xbc20d298UL, 0x2a10d5efUL, 0x8985b171UL, 0x1fb5b606UL, + 0xa5e4bf9fUL, 0x33d4b8e8UL, 0xa2c90778UL, 0x34f9000fUL, 0x8ea80996UL, + 0x18980ee1UL, 0xbb0d6a7fUL, 0x2d3d6d08UL, 0x976c6491UL, 0x015c63e6UL, + 0xf4516b6bUL, 0x62616c1cUL, 0xd8306585UL, 0x4e0062f2UL, 0xed95066cUL, + 0x7ba5011bUL, 0xc1f40882UL, 0x57c40ff5UL, 0xc6d9b065UL, 0x50e9b712UL, + 0xeab8be8bUL, 0x7c88b9fcUL, 0xdf1ddd62UL, 0x492dda15UL, 0xf37cd38cUL, + 0x654cd4fbUL, 0x5861b24dUL, 0xce51b53aUL, 0x7400bca3UL, 0xe230bbd4UL, + 0x41a5df4aUL, 0xd795d83dUL, 0x6dc4d1a4UL, 0xfbf4d6d3UL, 0x6ae96943UL, + 0xfcd96e34UL, 0x468867adUL, 0xd0b860daUL, 0x732d0444UL, 0xe51d0333UL, + 0x5f4c0aaaUL, 0xc97c0dddUL, 0x3c710550UL, 0xaa410227UL, 0x10100bbeUL, + 0x86200cc9UL, 0x25b56857UL, 0xb3856f20UL, 0x09d466b9UL, 0x9fe461ceUL, + 0x0ef9de5eUL, 0x98c9d929UL, 0x2298d0b0UL, 0xb4a8d7c7UL, 0x173db359UL, + 0x810db42eUL, 0x3b5cbdb7UL, 0xad6cbac0UL, 0x2083b8edUL, 0xb6b3bf9aUL, + 0x0ce2b603UL, 0x9ad2b174UL, 0x3947d5eaUL, 0xaf77d29dUL, 0x1526db04UL, + 0x8316dc73UL, 0x120b63e3UL, 0x843b6494UL, 0x3e6a6d0dUL, 0xa85a6a7aUL, + 0x0bcf0ee4UL, 0x9dff0993UL, 0x27ae000aUL, 0xb19e077dUL, 0x44930ff0UL, + 0xd2a30887UL, 0x68f2011eUL, 0xfec20669UL, 0x5d5762f7UL, 0xcb676580UL, + 0x71366c19UL, 0xe7066b6eUL, 0x761bd4feUL, 0xe02bd389UL, 0x5a7ada10UL, + 0xcc4add67UL, 0x6fdfb9f9UL, 0xf9efbe8eUL, 0x43beb717UL, 0xd58eb060UL, + 0xe8a3d6d6UL, 0x7e93d1a1UL, 0xc4c2d838UL, 0x52f2df4fUL, 0xf167bbd1UL, + 0x6757bca6UL, 0xdd06b53fUL, 0x4b36b248UL, 0xda2b0dd8UL, 0x4c1b0aafUL, + 0xf64a0336UL, 0x607a0441UL, 0xc3ef60dfUL, 0x55df67a8UL, 0xef8e6e31UL, + 0x79be6946UL, 0x8cb361cbUL, 0x1a8366bcUL, 0xa0d26f25UL, 0x36e26852UL, + 0x95770cccUL, 0x03470bbbUL, 0xb9160222UL, 0x2f260555UL, 0xbe3bbac5UL, + 0x280bbdb2UL, 0x925ab42bUL, 0x046ab35cUL, 0xa7ffd7c2UL, 0x31cfd0b5UL, + 0x8b9ed92cUL, 0x1daede5bUL, 0xb0c2649bUL, 0x26f263ecUL, 0x9ca36a75UL, + 0x0a936d02UL, 0xa906099cUL, 0x3f360eebUL, 0x85670772UL, 0x13570005UL, + 0x824abf95UL, 0x147ab8e2UL, 0xae2bb17bUL, 0x381bb60cUL, 0x9b8ed292UL, + 0x0dbed5e5UL, 0xb7efdc7cUL, 0x21dfdb0bUL, 0xd4d2d386UL, 0x42e2d4f1UL, + 0xf8b3dd68UL, 0x6e83da1fUL, 0xcd16be81UL, 0x5b26b9f6UL, 0xe177b06fUL, + 0x7747b718UL, 0xe65a0888UL, 0x706a0fffUL, 0xca3b0666UL, 0x5c0b0111UL, + 0xff9e658fUL, 0x69ae62f8UL, 0xd3ff6b61UL, 0x45cf6c16UL, 0x78e20aa0UL, + 0xeed20dd7UL, 0x5483044eUL, 0xc2b30339UL, 0x612667a7UL, 0xf71660d0UL, + 0x4d476949UL, 0xdb776e3eUL, 0x4a6ad1aeUL, 0xdc5ad6d9UL, 0x660bdf40UL, + 0xf03bd837UL, 0x53aebca9UL, 0xc59ebbdeUL, 0x7fcfb247UL, 0xe9ffb530UL, + 0x1cf2bdbdUL, 0x8ac2bacaUL, 0x3093b353UL, 0xa6a3b424UL, 0x0536d0baUL, + 0x9306d7cdUL, 0x2957de54UL, 0xbf67d923UL, 0x2e7a66b3UL, 0xb84a61c4UL, + 0x021b685dUL, 0x942b6f2aUL, 0x37be0bb4UL, 0xa18e0cc3UL, 0x1bdf055aUL, + 0x8def022dUL + }, + { + 0x00000000UL, 0x41311b19UL, 0x82623632UL, 0xc3532d2bUL, 0x04c56c64UL, + 0x45f4777dUL, 0x86a75a56UL, 0xc796414fUL, 0x088ad9c8UL, 0x49bbc2d1UL, + 0x8ae8effaUL, 0xcbd9f4e3UL, 0x0c4fb5acUL, 0x4d7eaeb5UL, 0x8e2d839eUL, + 0xcf1c9887UL, 0x5112c24aUL, 0x1023d953UL, 0xd370f478UL, 0x9241ef61UL, + 0x55d7ae2eUL, 0x14e6b537UL, 0xd7b5981cUL, 0x96848305UL, 0x59981b82UL, + 0x18a9009bUL, 0xdbfa2db0UL, 0x9acb36a9UL, 0x5d5d77e6UL, 0x1c6c6cffUL, + 0xdf3f41d4UL, 0x9e0e5acdUL, 0xa2248495UL, 0xe3159f8cUL, 0x2046b2a7UL, + 0x6177a9beUL, 0xa6e1e8f1UL, 0xe7d0f3e8UL, 0x2483dec3UL, 0x65b2c5daUL, + 0xaaae5d5dUL, 0xeb9f4644UL, 0x28cc6b6fUL, 0x69fd7076UL, 0xae6b3139UL, + 0xef5a2a20UL, 0x2c09070bUL, 0x6d381c12UL, 0xf33646dfUL, 0xb2075dc6UL, + 0x715470edUL, 0x30656bf4UL, 0xf7f32abbUL, 0xb6c231a2UL, 0x75911c89UL, + 0x34a00790UL, 0xfbbc9f17UL, 0xba8d840eUL, 0x79dea925UL, 0x38efb23cUL, + 0xff79f373UL, 0xbe48e86aUL, 0x7d1bc541UL, 0x3c2ade58UL, 0x054f79f0UL, + 0x447e62e9UL, 0x872d4fc2UL, 0xc61c54dbUL, 0x018a1594UL, 0x40bb0e8dUL, + 0x83e823a6UL, 0xc2d938bfUL, 0x0dc5a038UL, 0x4cf4bb21UL, 0x8fa7960aUL, + 0xce968d13UL, 0x0900cc5cUL, 0x4831d745UL, 0x8b62fa6eUL, 0xca53e177UL, + 0x545dbbbaUL, 0x156ca0a3UL, 0xd63f8d88UL, 0x970e9691UL, 0x5098d7deUL, + 0x11a9ccc7UL, 0xd2fae1ecUL, 0x93cbfaf5UL, 0x5cd76272UL, 0x1de6796bUL, + 0xdeb55440UL, 0x9f844f59UL, 0x58120e16UL, 0x1923150fUL, 0xda703824UL, + 0x9b41233dUL, 0xa76bfd65UL, 0xe65ae67cUL, 0x2509cb57UL, 0x6438d04eUL, + 0xa3ae9101UL, 0xe29f8a18UL, 0x21cca733UL, 0x60fdbc2aUL, 0xafe124adUL, + 0xeed03fb4UL, 0x2d83129fUL, 0x6cb20986UL, 0xab2448c9UL, 0xea1553d0UL, + 0x29467efbUL, 0x687765e2UL, 0xf6793f2fUL, 0xb7482436UL, 0x741b091dUL, + 0x352a1204UL, 0xf2bc534bUL, 0xb38d4852UL, 0x70de6579UL, 0x31ef7e60UL, + 0xfef3e6e7UL, 0xbfc2fdfeUL, 0x7c91d0d5UL, 0x3da0cbccUL, 0xfa368a83UL, + 0xbb07919aUL, 0x7854bcb1UL, 0x3965a7a8UL, 0x4b98833bUL, 0x0aa99822UL, + 0xc9fab509UL, 0x88cbae10UL, 0x4f5def5fUL, 0x0e6cf446UL, 0xcd3fd96dUL, + 0x8c0ec274UL, 0x43125af3UL, 0x022341eaUL, 0xc1706cc1UL, 0x804177d8UL, + 0x47d73697UL, 0x06e62d8eUL, 0xc5b500a5UL, 0x84841bbcUL, 0x1a8a4171UL, + 0x5bbb5a68UL, 0x98e87743UL, 0xd9d96c5aUL, 0x1e4f2d15UL, 0x5f7e360cUL, + 0x9c2d1b27UL, 0xdd1c003eUL, 0x120098b9UL, 0x533183a0UL, 0x9062ae8bUL, + 0xd153b592UL, 0x16c5f4ddUL, 0x57f4efc4UL, 0x94a7c2efUL, 0xd596d9f6UL, + 0xe9bc07aeUL, 0xa88d1cb7UL, 0x6bde319cUL, 0x2aef2a85UL, 0xed796bcaUL, + 0xac4870d3UL, 0x6f1b5df8UL, 0x2e2a46e1UL, 0xe136de66UL, 0xa007c57fUL, + 0x6354e854UL, 0x2265f34dUL, 0xe5f3b202UL, 0xa4c2a91bUL, 0x67918430UL, + 0x26a09f29UL, 0xb8aec5e4UL, 0xf99fdefdUL, 0x3accf3d6UL, 0x7bfde8cfUL, + 0xbc6ba980UL, 0xfd5ab299UL, 0x3e099fb2UL, 0x7f3884abUL, 0xb0241c2cUL, + 0xf1150735UL, 0x32462a1eUL, 0x73773107UL, 0xb4e17048UL, 0xf5d06b51UL, + 0x3683467aUL, 0x77b25d63UL, 0x4ed7facbUL, 0x0fe6e1d2UL, 0xccb5ccf9UL, + 0x8d84d7e0UL, 0x4a1296afUL, 0x0b238db6UL, 0xc870a09dUL, 0x8941bb84UL, + 0x465d2303UL, 0x076c381aUL, 0xc43f1531UL, 0x850e0e28UL, 0x42984f67UL, + 0x03a9547eUL, 0xc0fa7955UL, 0x81cb624cUL, 0x1fc53881UL, 0x5ef42398UL, + 0x9da70eb3UL, 0xdc9615aaUL, 0x1b0054e5UL, 0x5a314ffcUL, 0x996262d7UL, + 0xd85379ceUL, 0x174fe149UL, 0x567efa50UL, 0x952dd77bUL, 0xd41ccc62UL, + 0x138a8d2dUL, 0x52bb9634UL, 0x91e8bb1fUL, 0xd0d9a006UL, 0xecf37e5eUL, + 0xadc26547UL, 0x6e91486cUL, 0x2fa05375UL, 0xe836123aUL, 0xa9070923UL, + 0x6a542408UL, 0x2b653f11UL, 0xe479a796UL, 0xa548bc8fUL, 0x661b91a4UL, + 0x272a8abdUL, 0xe0bccbf2UL, 0xa18dd0ebUL, 0x62defdc0UL, 0x23efe6d9UL, + 0xbde1bc14UL, 0xfcd0a70dUL, 0x3f838a26UL, 0x7eb2913fUL, 0xb924d070UL, + 0xf815cb69UL, 0x3b46e642UL, 0x7a77fd5bUL, 0xb56b65dcUL, 0xf45a7ec5UL, + 0x370953eeUL, 0x763848f7UL, 0xb1ae09b8UL, 0xf09f12a1UL, 0x33cc3f8aUL, + 0x72fd2493UL + }, + { + 0x00000000UL, 0x376ac201UL, 0x6ed48403UL, 0x59be4602UL, 0xdca80907UL, + 0xebc2cb06UL, 0xb27c8d04UL, 0x85164f05UL, 0xb851130eUL, 0x8f3bd10fUL, + 0xd685970dUL, 0xe1ef550cUL, 0x64f91a09UL, 0x5393d808UL, 0x0a2d9e0aUL, + 0x3d475c0bUL, 0x70a3261cUL, 0x47c9e41dUL, 0x1e77a21fUL, 0x291d601eUL, + 0xac0b2f1bUL, 0x9b61ed1aUL, 0xc2dfab18UL, 0xf5b56919UL, 0xc8f23512UL, + 0xff98f713UL, 0xa626b111UL, 0x914c7310UL, 0x145a3c15UL, 0x2330fe14UL, + 0x7a8eb816UL, 0x4de47a17UL, 0xe0464d38UL, 0xd72c8f39UL, 0x8e92c93bUL, + 0xb9f80b3aUL, 0x3cee443fUL, 0x0b84863eUL, 0x523ac03cUL, 0x6550023dUL, + 0x58175e36UL, 0x6f7d9c37UL, 0x36c3da35UL, 0x01a91834UL, 0x84bf5731UL, + 0xb3d59530UL, 0xea6bd332UL, 0xdd011133UL, 0x90e56b24UL, 0xa78fa925UL, + 0xfe31ef27UL, 0xc95b2d26UL, 0x4c4d6223UL, 0x7b27a022UL, 0x2299e620UL, + 0x15f32421UL, 0x28b4782aUL, 0x1fdeba2bUL, 0x4660fc29UL, 0x710a3e28UL, + 0xf41c712dUL, 0xc376b32cUL, 0x9ac8f52eUL, 0xada2372fUL, 0xc08d9a70UL, + 0xf7e75871UL, 0xae591e73UL, 0x9933dc72UL, 0x1c259377UL, 0x2b4f5176UL, + 0x72f11774UL, 0x459bd575UL, 0x78dc897eUL, 0x4fb64b7fUL, 0x16080d7dUL, + 0x2162cf7cUL, 0xa4748079UL, 0x931e4278UL, 0xcaa0047aUL, 0xfdcac67bUL, + 0xb02ebc6cUL, 0x87447e6dUL, 0xdefa386fUL, 0xe990fa6eUL, 0x6c86b56bUL, + 0x5bec776aUL, 0x02523168UL, 0x3538f369UL, 0x087faf62UL, 0x3f156d63UL, + 0x66ab2b61UL, 0x51c1e960UL, 0xd4d7a665UL, 0xe3bd6464UL, 0xba032266UL, + 0x8d69e067UL, 0x20cbd748UL, 0x17a11549UL, 0x4e1f534bUL, 0x7975914aUL, + 0xfc63de4fUL, 0xcb091c4eUL, 0x92b75a4cUL, 0xa5dd984dUL, 0x989ac446UL, + 0xaff00647UL, 0xf64e4045UL, 0xc1248244UL, 0x4432cd41UL, 0x73580f40UL, + 0x2ae64942UL, 0x1d8c8b43UL, 0x5068f154UL, 0x67023355UL, 0x3ebc7557UL, + 0x09d6b756UL, 0x8cc0f853UL, 0xbbaa3a52UL, 0xe2147c50UL, 0xd57ebe51UL, + 0xe839e25aUL, 0xdf53205bUL, 0x86ed6659UL, 0xb187a458UL, 0x3491eb5dUL, + 0x03fb295cUL, 0x5a456f5eUL, 0x6d2fad5fUL, 0x801b35e1UL, 0xb771f7e0UL, + 0xeecfb1e2UL, 0xd9a573e3UL, 0x5cb33ce6UL, 0x6bd9fee7UL, 0x3267b8e5UL, + 0x050d7ae4UL, 0x384a26efUL, 0x0f20e4eeUL, 0x569ea2ecUL, 0x61f460edUL, + 0xe4e22fe8UL, 0xd388ede9UL, 0x8a36abebUL, 0xbd5c69eaUL, 0xf0b813fdUL, + 0xc7d2d1fcUL, 0x9e6c97feUL, 0xa90655ffUL, 0x2c101afaUL, 0x1b7ad8fbUL, + 0x42c49ef9UL, 0x75ae5cf8UL, 0x48e900f3UL, 0x7f83c2f2UL, 0x263d84f0UL, + 0x115746f1UL, 0x944109f4UL, 0xa32bcbf5UL, 0xfa958df7UL, 0xcdff4ff6UL, + 0x605d78d9UL, 0x5737bad8UL, 0x0e89fcdaUL, 0x39e33edbUL, 0xbcf571deUL, + 0x8b9fb3dfUL, 0xd221f5ddUL, 0xe54b37dcUL, 0xd80c6bd7UL, 0xef66a9d6UL, + 0xb6d8efd4UL, 0x81b22dd5UL, 0x04a462d0UL, 0x33cea0d1UL, 0x6a70e6d3UL, + 0x5d1a24d2UL, 0x10fe5ec5UL, 0x27949cc4UL, 0x7e2adac6UL, 0x494018c7UL, + 0xcc5657c2UL, 0xfb3c95c3UL, 0xa282d3c1UL, 0x95e811c0UL, 0xa8af4dcbUL, + 0x9fc58fcaUL, 0xc67bc9c8UL, 0xf1110bc9UL, 0x740744ccUL, 0x436d86cdUL, + 0x1ad3c0cfUL, 0x2db902ceUL, 0x4096af91UL, 0x77fc6d90UL, 0x2e422b92UL, + 0x1928e993UL, 0x9c3ea696UL, 0xab546497UL, 0xf2ea2295UL, 0xc580e094UL, + 0xf8c7bc9fUL, 0xcfad7e9eUL, 0x9613389cUL, 0xa179fa9dUL, 0x246fb598UL, + 0x13057799UL, 0x4abb319bUL, 0x7dd1f39aUL, 0x3035898dUL, 0x075f4b8cUL, + 0x5ee10d8eUL, 0x698bcf8fUL, 0xec9d808aUL, 0xdbf7428bUL, 0x82490489UL, + 0xb523c688UL, 0x88649a83UL, 0xbf0e5882UL, 0xe6b01e80UL, 0xd1dadc81UL, + 0x54cc9384UL, 0x63a65185UL, 0x3a181787UL, 0x0d72d586UL, 0xa0d0e2a9UL, + 0x97ba20a8UL, 0xce0466aaUL, 0xf96ea4abUL, 0x7c78ebaeUL, 0x4b1229afUL, + 0x12ac6fadUL, 0x25c6adacUL, 0x1881f1a7UL, 0x2feb33a6UL, 0x765575a4UL, + 0x413fb7a5UL, 0xc429f8a0UL, 0xf3433aa1UL, 0xaafd7ca3UL, 0x9d97bea2UL, + 0xd073c4b5UL, 0xe71906b4UL, 0xbea740b6UL, 0x89cd82b7UL, 0x0cdbcdb2UL, + 0x3bb10fb3UL, 0x620f49b1UL, 0x55658bb0UL, 0x6822d7bbUL, 0x5f4815baUL, + 0x06f653b8UL, 0x319c91b9UL, 0xb48adebcUL, 0x83e01cbdUL, 0xda5e5abfUL, + 0xed3498beUL + }, + { + 0x00000000UL, 0x6567bcb8UL, 0x8bc809aaUL, 0xeeafb512UL, 0x5797628fUL, + 0x32f0de37UL, 0xdc5f6b25UL, 0xb938d79dUL, 0xef28b4c5UL, 0x8a4f087dUL, + 0x64e0bd6fUL, 0x018701d7UL, 0xb8bfd64aUL, 0xddd86af2UL, 0x3377dfe0UL, + 0x56106358UL, 0x9f571950UL, 0xfa30a5e8UL, 0x149f10faUL, 0x71f8ac42UL, + 0xc8c07bdfUL, 0xada7c767UL, 0x43087275UL, 0x266fcecdUL, 0x707fad95UL, + 0x1518112dUL, 0xfbb7a43fUL, 0x9ed01887UL, 0x27e8cf1aUL, 0x428f73a2UL, + 0xac20c6b0UL, 0xc9477a08UL, 0x3eaf32a0UL, 0x5bc88e18UL, 0xb5673b0aUL, + 0xd00087b2UL, 0x6938502fUL, 0x0c5fec97UL, 0xe2f05985UL, 0x8797e53dUL, + 0xd1878665UL, 0xb4e03addUL, 0x5a4f8fcfUL, 0x3f283377UL, 0x8610e4eaUL, + 0xe3775852UL, 0x0dd8ed40UL, 0x68bf51f8UL, 0xa1f82bf0UL, 0xc49f9748UL, + 0x2a30225aUL, 0x4f579ee2UL, 0xf66f497fUL, 0x9308f5c7UL, 0x7da740d5UL, + 0x18c0fc6dUL, 0x4ed09f35UL, 0x2bb7238dUL, 0xc518969fUL, 0xa07f2a27UL, + 0x1947fdbaUL, 0x7c204102UL, 0x928ff410UL, 0xf7e848a8UL, 0x3d58149bUL, + 0x583fa823UL, 0xb6901d31UL, 0xd3f7a189UL, 0x6acf7614UL, 0x0fa8caacUL, + 0xe1077fbeUL, 0x8460c306UL, 0xd270a05eUL, 0xb7171ce6UL, 0x59b8a9f4UL, + 0x3cdf154cUL, 0x85e7c2d1UL, 0xe0807e69UL, 0x0e2fcb7bUL, 0x6b4877c3UL, + 0xa20f0dcbUL, 0xc768b173UL, 0x29c70461UL, 0x4ca0b8d9UL, 0xf5986f44UL, + 0x90ffd3fcUL, 0x7e5066eeUL, 0x1b37da56UL, 0x4d27b90eUL, 0x284005b6UL, + 0xc6efb0a4UL, 0xa3880c1cUL, 0x1ab0db81UL, 0x7fd76739UL, 0x9178d22bUL, + 0xf41f6e93UL, 0x03f7263bUL, 0x66909a83UL, 0x883f2f91UL, 0xed589329UL, + 0x546044b4UL, 0x3107f80cUL, 0xdfa84d1eUL, 0xbacff1a6UL, 0xecdf92feUL, + 0x89b82e46UL, 0x67179b54UL, 0x027027ecUL, 0xbb48f071UL, 0xde2f4cc9UL, + 0x3080f9dbUL, 0x55e74563UL, 0x9ca03f6bUL, 0xf9c783d3UL, 0x176836c1UL, + 0x720f8a79UL, 0xcb375de4UL, 0xae50e15cUL, 0x40ff544eUL, 0x2598e8f6UL, + 0x73888baeUL, 0x16ef3716UL, 0xf8408204UL, 0x9d273ebcUL, 0x241fe921UL, + 0x41785599UL, 0xafd7e08bUL, 0xcab05c33UL, 0x3bb659edUL, 0x5ed1e555UL, + 0xb07e5047UL, 0xd519ecffUL, 0x6c213b62UL, 0x094687daUL, 0xe7e932c8UL, + 0x828e8e70UL, 0xd49eed28UL, 0xb1f95190UL, 0x5f56e482UL, 0x3a31583aUL, + 0x83098fa7UL, 0xe66e331fUL, 0x08c1860dUL, 0x6da63ab5UL, 0xa4e140bdUL, + 0xc186fc05UL, 0x2f294917UL, 0x4a4ef5afUL, 0xf3762232UL, 0x96119e8aUL, + 0x78be2b98UL, 0x1dd99720UL, 0x4bc9f478UL, 0x2eae48c0UL, 0xc001fdd2UL, + 0xa566416aUL, 0x1c5e96f7UL, 0x79392a4fUL, 0x97969f5dUL, 0xf2f123e5UL, + 0x05196b4dUL, 0x607ed7f5UL, 0x8ed162e7UL, 0xebb6de5fUL, 0x528e09c2UL, + 0x37e9b57aUL, 0xd9460068UL, 0xbc21bcd0UL, 0xea31df88UL, 0x8f566330UL, + 0x61f9d622UL, 0x049e6a9aUL, 0xbda6bd07UL, 0xd8c101bfUL, 0x366eb4adUL, + 0x53090815UL, 0x9a4e721dUL, 0xff29cea5UL, 0x11867bb7UL, 0x74e1c70fUL, + 0xcdd91092UL, 0xa8beac2aUL, 0x46111938UL, 0x2376a580UL, 0x7566c6d8UL, + 0x10017a60UL, 0xfeaecf72UL, 0x9bc973caUL, 0x22f1a457UL, 0x479618efUL, + 0xa939adfdUL, 0xcc5e1145UL, 0x06ee4d76UL, 0x6389f1ceUL, 0x8d2644dcUL, + 0xe841f864UL, 0x51792ff9UL, 0x341e9341UL, 0xdab12653UL, 0xbfd69aebUL, + 0xe9c6f9b3UL, 0x8ca1450bUL, 0x620ef019UL, 0x07694ca1UL, 0xbe519b3cUL, + 0xdb362784UL, 0x35999296UL, 0x50fe2e2eUL, 0x99b95426UL, 0xfcdee89eUL, + 0x12715d8cUL, 0x7716e134UL, 0xce2e36a9UL, 0xab498a11UL, 0x45e63f03UL, + 0x208183bbUL, 0x7691e0e3UL, 0x13f65c5bUL, 0xfd59e949UL, 0x983e55f1UL, + 0x2106826cUL, 0x44613ed4UL, 0xaace8bc6UL, 0xcfa9377eUL, 0x38417fd6UL, + 0x5d26c36eUL, 0xb389767cUL, 0xd6eecac4UL, 0x6fd61d59UL, 0x0ab1a1e1UL, + 0xe41e14f3UL, 0x8179a84bUL, 0xd769cb13UL, 0xb20e77abUL, 0x5ca1c2b9UL, + 0x39c67e01UL, 0x80fea99cUL, 0xe5991524UL, 0x0b36a036UL, 0x6e511c8eUL, + 0xa7166686UL, 0xc271da3eUL, 0x2cde6f2cUL, 0x49b9d394UL, 0xf0810409UL, + 0x95e6b8b1UL, 0x7b490da3UL, 0x1e2eb11bUL, 0x483ed243UL, 0x2d596efbUL, + 0xc3f6dbe9UL, 0xa6916751UL, 0x1fa9b0ccUL, 0x7ace0c74UL, 0x9461b966UL, + 0xf10605deUL +#endif + } +}; diff --git a/snesreader/zlib/inffast.c b/snesreader/zlib/inffast.c new file mode 100644 index 00000000..7a148eb7 --- /dev/null +++ b/snesreader/zlib/inffast.c @@ -0,0 +1,318 @@ +/* inffast.c -- fast decoding + * Copyright (C) 1995-2004 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +#include "zutil.h" +#include "inftrees.h" +#include "inflate.h" +#include "inffast.h" + +#ifndef ASMINF + +/* Allow machine dependent optimization for post-increment or pre-increment. + Based on testing to date, + Pre-increment preferred for: + - PowerPC G3 (Adler) + - MIPS R5000 (Randers-Pehrson) + Post-increment preferred for: + - none + No measurable difference: + - Pentium III (Anderson) + - M68060 (Nikl) + */ +#ifdef POSTINC +# define OFF 0 +# define PUP(a) *(a)++ +#else +# define OFF 1 +# define PUP(a) *++(a) +#endif + +/* + Decode literal, length, and distance codes and write out the resulting + literal and match bytes until either not enough input or output is + available, an end-of-block is encountered, or a data error is encountered. + When large enough input and output buffers are supplied to inflate(), for + example, a 16K input buffer and a 64K output buffer, more than 95% of the + inflate execution time is spent in this routine. + + Entry assumptions: + + state->mode == LEN + strm->avail_in >= 6 + strm->avail_out >= 258 + start >= strm->avail_out + state->bits < 8 + + On return, state->mode is one of: + + LEN -- ran out of enough output space or enough available input + TYPE -- reached end of block code, inflate() to interpret next block + BAD -- error in block data + + Notes: + + - The maximum input bits used by a length/distance pair is 15 bits for the + length code, 5 bits for the length extra, 15 bits for the distance code, + and 13 bits for the distance extra. This totals 48 bits, or six bytes. + Therefore if strm->avail_in >= 6, then there is enough input to avoid + checking for available input while decoding. + + - The maximum bytes that a single length/distance pair can output is 258 + bytes, which is the maximum length that can be coded. inflate_fast() + requires strm->avail_out >= 258 for each loop to avoid checking for + output space. + */ +void inflate_fast(strm, start) +z_streamp strm; +unsigned start; /* inflate()'s starting value for strm->avail_out */ +{ + struct inflate_state FAR *state; + unsigned char FAR *in; /* local strm->next_in */ + unsigned char FAR *last; /* while in < last, enough input available */ + unsigned char FAR *out; /* local strm->next_out */ + unsigned char FAR *beg; /* inflate()'s initial strm->next_out */ + unsigned char FAR *end; /* while out < end, enough space available */ +#ifdef INFLATE_STRICT + unsigned dmax; /* maximum distance from zlib header */ +#endif + unsigned wsize; /* window size or zero if not using window */ + unsigned whave; /* valid bytes in the window */ + unsigned write; /* window write index */ + unsigned char FAR *window; /* allocated sliding window, if wsize != 0 */ + unsigned long hold; /* local strm->hold */ + unsigned bits; /* local strm->bits */ + code const FAR *lcode; /* local strm->lencode */ + code const FAR *dcode; /* local strm->distcode */ + unsigned lmask; /* mask for first level of length codes */ + unsigned dmask; /* mask for first level of distance codes */ + code this; /* retrieved table entry */ + unsigned op; /* code bits, operation, extra bits, or */ + /* window position, window bytes to copy */ + unsigned len; /* match length, unused bytes */ + unsigned dist; /* match distance */ + unsigned char FAR *from; /* where to copy match from */ + + /* copy state to local variables */ + state = (struct inflate_state FAR *)strm->state; + in = strm->next_in - OFF; + last = in + (strm->avail_in - 5); + out = strm->next_out - OFF; + beg = out - (start - strm->avail_out); + end = out + (strm->avail_out - 257); +#ifdef INFLATE_STRICT + dmax = state->dmax; +#endif + wsize = state->wsize; + whave = state->whave; + write = state->write; + window = state->window; + hold = state->hold; + bits = state->bits; + lcode = state->lencode; + dcode = state->distcode; + lmask = (1U << state->lenbits) - 1; + dmask = (1U << state->distbits) - 1; + + /* decode literals and length/distances until end-of-block or not enough + input data or output space */ + do { + if (bits < 15) { + hold += (unsigned long)(PUP(in)) << bits; + bits += 8; + hold += (unsigned long)(PUP(in)) << bits; + bits += 8; + } + this = lcode[hold & lmask]; + dolen: + op = (unsigned)(this.bits); + hold >>= op; + bits -= op; + op = (unsigned)(this.op); + if (op == 0) { /* literal */ + Tracevv((stderr, this.val >= 0x20 && this.val < 0x7f ? + "inflate: literal '%c'\n" : + "inflate: literal 0x%02x\n", this.val)); + PUP(out) = (unsigned char)(this.val); + } + else if (op & 16) { /* length base */ + len = (unsigned)(this.val); + op &= 15; /* number of extra bits */ + if (op) { + if (bits < op) { + hold += (unsigned long)(PUP(in)) << bits; + bits += 8; + } + len += (unsigned)hold & ((1U << op) - 1); + hold >>= op; + bits -= op; + } + Tracevv((stderr, "inflate: length %u\n", len)); + if (bits < 15) { + hold += (unsigned long)(PUP(in)) << bits; + bits += 8; + hold += (unsigned long)(PUP(in)) << bits; + bits += 8; + } + this = dcode[hold & dmask]; + dodist: + op = (unsigned)(this.bits); + hold >>= op; + bits -= op; + op = (unsigned)(this.op); + if (op & 16) { /* distance base */ + dist = (unsigned)(this.val); + op &= 15; /* number of extra bits */ + if (bits < op) { + hold += (unsigned long)(PUP(in)) << bits; + bits += 8; + if (bits < op) { + hold += (unsigned long)(PUP(in)) << bits; + bits += 8; + } + } + dist += (unsigned)hold & ((1U << op) - 1); +#ifdef INFLATE_STRICT + if (dist > dmax) { + strm->msg = "invalid distance too far back"; + state->mode = BAD; + break; + } +#endif + hold >>= op; + bits -= op; + Tracevv((stderr, "inflate: distance %u\n", dist)); + op = (unsigned)(out - beg); /* max distance in output */ + if (dist > op) { /* see if copy from window */ + op = dist - op; /* distance back in window */ + if (op > whave) { + strm->msg = "invalid distance too far back"; + state->mode = BAD; + break; + } + from = window - OFF; + if (write == 0) { /* very common case */ + from += wsize - op; + if (op < len) { /* some from window */ + len -= op; + do { + PUP(out) = PUP(from); + } while (--op); + from = out - dist; /* rest from output */ + } + } + else if (write < op) { /* wrap around window */ + from += wsize + write - op; + op -= write; + if (op < len) { /* some from end of window */ + len -= op; + do { + PUP(out) = PUP(from); + } while (--op); + from = window - OFF; + if (write < len) { /* some from start of window */ + op = write; + len -= op; + do { + PUP(out) = PUP(from); + } while (--op); + from = out - dist; /* rest from output */ + } + } + } + else { /* contiguous in window */ + from += write - op; + if (op < len) { /* some from window */ + len -= op; + do { + PUP(out) = PUP(from); + } while (--op); + from = out - dist; /* rest from output */ + } + } + while (len > 2) { + PUP(out) = PUP(from); + PUP(out) = PUP(from); + PUP(out) = PUP(from); + len -= 3; + } + if (len) { + PUP(out) = PUP(from); + if (len > 1) + PUP(out) = PUP(from); + } + } + else { + from = out - dist; /* copy direct from output */ + do { /* minimum length is three */ + PUP(out) = PUP(from); + PUP(out) = PUP(from); + PUP(out) = PUP(from); + len -= 3; + } while (len > 2); + if (len) { + PUP(out) = PUP(from); + if (len > 1) + PUP(out) = PUP(from); + } + } + } + else if ((op & 64) == 0) { /* 2nd level distance code */ + this = dcode[this.val + (hold & ((1U << op) - 1))]; + goto dodist; + } + else { + strm->msg = "invalid distance code"; + state->mode = BAD; + break; + } + } + else if ((op & 64) == 0) { /* 2nd level length code */ + this = lcode[this.val + (hold & ((1U << op) - 1))]; + goto dolen; + } + else if (op & 32) { /* end-of-block */ + Tracevv((stderr, "inflate: end of block\n")); + state->mode = TYPE; + break; + } + else { + strm->msg = "invalid literal/length code"; + state->mode = BAD; + break; + } + } while (in < last && out < end); + + /* return unused bytes (on entry, bits < 8, so in won't go too far back) */ + len = bits >> 3; + in -= len; + bits -= len << 3; + hold &= (1U << bits) - 1; + + /* update state and return */ + strm->next_in = in + OFF; + strm->next_out = out + OFF; + strm->avail_in = (unsigned)(in < last ? 5 + (last - in) : 5 - (in - last)); + strm->avail_out = (unsigned)(out < end ? + 257 + (end - out) : 257 - (out - end)); + state->hold = hold; + state->bits = bits; + return; +} + +/* + inflate_fast() speedups that turned out slower (on a PowerPC G3 750CXe): + - Using bit fields for code structure + - Different op definition to avoid & for extra bits (do & for table bits) + - Three separate decoding do-loops for direct, window, and write == 0 + - Special case for distance > 1 copies to do overlapped load and store copy + - Explicit branch predictions (based on measured branch probabilities) + - Deferring match copy and interspersed it with decoding subsequent codes + - Swapping literal/length else + - Swapping window/direct else + - Larger unrolled copy loops (three is about right) + - Moving len -= 3 statement into middle of loop + */ + +#endif /* !ASMINF */ diff --git a/snesreader/zlib/inffast.h b/snesreader/zlib/inffast.h new file mode 100644 index 00000000..1e88d2d9 --- /dev/null +++ b/snesreader/zlib/inffast.h @@ -0,0 +1,11 @@ +/* inffast.h -- header to use inffast.c + * Copyright (C) 1995-2003 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* WARNING: this file should *not* be used by applications. It is + part of the implementation of the compression library and is + subject to change. Applications should only use zlib.h. + */ + +void inflate_fast OF((z_streamp strm, unsigned start)); diff --git a/snesreader/zlib/inffixed.h b/snesreader/zlib/inffixed.h new file mode 100644 index 00000000..75ed4b59 --- /dev/null +++ b/snesreader/zlib/inffixed.h @@ -0,0 +1,94 @@ + /* inffixed.h -- table for decoding fixed codes + * Generated automatically by makefixed(). + */ + + /* WARNING: this file should *not* be used by applications. It + is part of the implementation of the compression library and + is subject to change. Applications should only use zlib.h. + */ + + static const code lenfix[512] = { + {96,7,0},{0,8,80},{0,8,16},{20,8,115},{18,7,31},{0,8,112},{0,8,48}, + {0,9,192},{16,7,10},{0,8,96},{0,8,32},{0,9,160},{0,8,0},{0,8,128}, + {0,8,64},{0,9,224},{16,7,6},{0,8,88},{0,8,24},{0,9,144},{19,7,59}, + {0,8,120},{0,8,56},{0,9,208},{17,7,17},{0,8,104},{0,8,40},{0,9,176}, + {0,8,8},{0,8,136},{0,8,72},{0,9,240},{16,7,4},{0,8,84},{0,8,20}, + {21,8,227},{19,7,43},{0,8,116},{0,8,52},{0,9,200},{17,7,13},{0,8,100}, + {0,8,36},{0,9,168},{0,8,4},{0,8,132},{0,8,68},{0,9,232},{16,7,8}, + {0,8,92},{0,8,28},{0,9,152},{20,7,83},{0,8,124},{0,8,60},{0,9,216}, + {18,7,23},{0,8,108},{0,8,44},{0,9,184},{0,8,12},{0,8,140},{0,8,76}, + {0,9,248},{16,7,3},{0,8,82},{0,8,18},{21,8,163},{19,7,35},{0,8,114}, + {0,8,50},{0,9,196},{17,7,11},{0,8,98},{0,8,34},{0,9,164},{0,8,2}, + {0,8,130},{0,8,66},{0,9,228},{16,7,7},{0,8,90},{0,8,26},{0,9,148}, + {20,7,67},{0,8,122},{0,8,58},{0,9,212},{18,7,19},{0,8,106},{0,8,42}, + {0,9,180},{0,8,10},{0,8,138},{0,8,74},{0,9,244},{16,7,5},{0,8,86}, + {0,8,22},{64,8,0},{19,7,51},{0,8,118},{0,8,54},{0,9,204},{17,7,15}, + {0,8,102},{0,8,38},{0,9,172},{0,8,6},{0,8,134},{0,8,70},{0,9,236}, + {16,7,9},{0,8,94},{0,8,30},{0,9,156},{20,7,99},{0,8,126},{0,8,62}, + {0,9,220},{18,7,27},{0,8,110},{0,8,46},{0,9,188},{0,8,14},{0,8,142}, + {0,8,78},{0,9,252},{96,7,0},{0,8,81},{0,8,17},{21,8,131},{18,7,31}, + {0,8,113},{0,8,49},{0,9,194},{16,7,10},{0,8,97},{0,8,33},{0,9,162}, + {0,8,1},{0,8,129},{0,8,65},{0,9,226},{16,7,6},{0,8,89},{0,8,25}, + {0,9,146},{19,7,59},{0,8,121},{0,8,57},{0,9,210},{17,7,17},{0,8,105}, + {0,8,41},{0,9,178},{0,8,9},{0,8,137},{0,8,73},{0,9,242},{16,7,4}, + {0,8,85},{0,8,21},{16,8,258},{19,7,43},{0,8,117},{0,8,53},{0,9,202}, + {17,7,13},{0,8,101},{0,8,37},{0,9,170},{0,8,5},{0,8,133},{0,8,69}, + {0,9,234},{16,7,8},{0,8,93},{0,8,29},{0,9,154},{20,7,83},{0,8,125}, + {0,8,61},{0,9,218},{18,7,23},{0,8,109},{0,8,45},{0,9,186},{0,8,13}, + {0,8,141},{0,8,77},{0,9,250},{16,7,3},{0,8,83},{0,8,19},{21,8,195}, + {19,7,35},{0,8,115},{0,8,51},{0,9,198},{17,7,11},{0,8,99},{0,8,35}, + {0,9,166},{0,8,3},{0,8,131},{0,8,67},{0,9,230},{16,7,7},{0,8,91}, + {0,8,27},{0,9,150},{20,7,67},{0,8,123},{0,8,59},{0,9,214},{18,7,19}, + {0,8,107},{0,8,43},{0,9,182},{0,8,11},{0,8,139},{0,8,75},{0,9,246}, + {16,7,5},{0,8,87},{0,8,23},{64,8,0},{19,7,51},{0,8,119},{0,8,55}, + {0,9,206},{17,7,15},{0,8,103},{0,8,39},{0,9,174},{0,8,7},{0,8,135}, + {0,8,71},{0,9,238},{16,7,9},{0,8,95},{0,8,31},{0,9,158},{20,7,99}, + {0,8,127},{0,8,63},{0,9,222},{18,7,27},{0,8,111},{0,8,47},{0,9,190}, + {0,8,15},{0,8,143},{0,8,79},{0,9,254},{96,7,0},{0,8,80},{0,8,16}, + {20,8,115},{18,7,31},{0,8,112},{0,8,48},{0,9,193},{16,7,10},{0,8,96}, + {0,8,32},{0,9,161},{0,8,0},{0,8,128},{0,8,64},{0,9,225},{16,7,6}, + {0,8,88},{0,8,24},{0,9,145},{19,7,59},{0,8,120},{0,8,56},{0,9,209}, + {17,7,17},{0,8,104},{0,8,40},{0,9,177},{0,8,8},{0,8,136},{0,8,72}, + {0,9,241},{16,7,4},{0,8,84},{0,8,20},{21,8,227},{19,7,43},{0,8,116}, + {0,8,52},{0,9,201},{17,7,13},{0,8,100},{0,8,36},{0,9,169},{0,8,4}, + {0,8,132},{0,8,68},{0,9,233},{16,7,8},{0,8,92},{0,8,28},{0,9,153}, + {20,7,83},{0,8,124},{0,8,60},{0,9,217},{18,7,23},{0,8,108},{0,8,44}, + {0,9,185},{0,8,12},{0,8,140},{0,8,76},{0,9,249},{16,7,3},{0,8,82}, + {0,8,18},{21,8,163},{19,7,35},{0,8,114},{0,8,50},{0,9,197},{17,7,11}, + {0,8,98},{0,8,34},{0,9,165},{0,8,2},{0,8,130},{0,8,66},{0,9,229}, + {16,7,7},{0,8,90},{0,8,26},{0,9,149},{20,7,67},{0,8,122},{0,8,58}, + {0,9,213},{18,7,19},{0,8,106},{0,8,42},{0,9,181},{0,8,10},{0,8,138}, + {0,8,74},{0,9,245},{16,7,5},{0,8,86},{0,8,22},{64,8,0},{19,7,51}, + {0,8,118},{0,8,54},{0,9,205},{17,7,15},{0,8,102},{0,8,38},{0,9,173}, + {0,8,6},{0,8,134},{0,8,70},{0,9,237},{16,7,9},{0,8,94},{0,8,30}, + {0,9,157},{20,7,99},{0,8,126},{0,8,62},{0,9,221},{18,7,27},{0,8,110}, + {0,8,46},{0,9,189},{0,8,14},{0,8,142},{0,8,78},{0,9,253},{96,7,0}, + {0,8,81},{0,8,17},{21,8,131},{18,7,31},{0,8,113},{0,8,49},{0,9,195}, + {16,7,10},{0,8,97},{0,8,33},{0,9,163},{0,8,1},{0,8,129},{0,8,65}, + {0,9,227},{16,7,6},{0,8,89},{0,8,25},{0,9,147},{19,7,59},{0,8,121}, + {0,8,57},{0,9,211},{17,7,17},{0,8,105},{0,8,41},{0,9,179},{0,8,9}, + {0,8,137},{0,8,73},{0,9,243},{16,7,4},{0,8,85},{0,8,21},{16,8,258}, + {19,7,43},{0,8,117},{0,8,53},{0,9,203},{17,7,13},{0,8,101},{0,8,37}, + {0,9,171},{0,8,5},{0,8,133},{0,8,69},{0,9,235},{16,7,8},{0,8,93}, + {0,8,29},{0,9,155},{20,7,83},{0,8,125},{0,8,61},{0,9,219},{18,7,23}, + {0,8,109},{0,8,45},{0,9,187},{0,8,13},{0,8,141},{0,8,77},{0,9,251}, + {16,7,3},{0,8,83},{0,8,19},{21,8,195},{19,7,35},{0,8,115},{0,8,51}, + {0,9,199},{17,7,11},{0,8,99},{0,8,35},{0,9,167},{0,8,3},{0,8,131}, + {0,8,67},{0,9,231},{16,7,7},{0,8,91},{0,8,27},{0,9,151},{20,7,67}, + {0,8,123},{0,8,59},{0,9,215},{18,7,19},{0,8,107},{0,8,43},{0,9,183}, + {0,8,11},{0,8,139},{0,8,75},{0,9,247},{16,7,5},{0,8,87},{0,8,23}, + {64,8,0},{19,7,51},{0,8,119},{0,8,55},{0,9,207},{17,7,15},{0,8,103}, + {0,8,39},{0,9,175},{0,8,7},{0,8,135},{0,8,71},{0,9,239},{16,7,9}, + {0,8,95},{0,8,31},{0,9,159},{20,7,99},{0,8,127},{0,8,63},{0,9,223}, + {18,7,27},{0,8,111},{0,8,47},{0,9,191},{0,8,15},{0,8,143},{0,8,79}, + {0,9,255} + }; + + static const code distfix[32] = { + {16,5,1},{23,5,257},{19,5,17},{27,5,4097},{17,5,5},{25,5,1025}, + {21,5,65},{29,5,16385},{16,5,3},{24,5,513},{20,5,33},{28,5,8193}, + {18,5,9},{26,5,2049},{22,5,129},{64,5,0},{16,5,2},{23,5,385}, + {19,5,25},{27,5,6145},{17,5,7},{25,5,1537},{21,5,97},{29,5,24577}, + {16,5,4},{24,5,769},{20,5,49},{28,5,12289},{18,5,13},{26,5,3073}, + {22,5,193},{64,5,0} + }; diff --git a/snesreader/zlib/inflate.c b/snesreader/zlib/inflate.c new file mode 100644 index 00000000..37744b3e --- /dev/null +++ b/snesreader/zlib/inflate.c @@ -0,0 +1,1368 @@ +/* inflate.c -- zlib decompression + * Copyright (C) 1995-2005 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* + * Change history: + * + * 1.2.beta0 24 Nov 2002 + * - First version -- complete rewrite of inflate to simplify code, avoid + * creation of window when not needed, minimize use of window when it is + * needed, make inffast.c even faster, implement gzip decoding, and to + * improve code readability and style over the previous zlib inflate code + * + * 1.2.beta1 25 Nov 2002 + * - Use pointers for available input and output checking in inffast.c + * - Remove input and output counters in inffast.c + * - Change inffast.c entry and loop from avail_in >= 7 to >= 6 + * - Remove unnecessary second byte pull from length extra in inffast.c + * - Unroll direct copy to three copies per loop in inffast.c + * + * 1.2.beta2 4 Dec 2002 + * - Change external routine names to reduce potential conflicts + * - Correct filename to inffixed.h for fixed tables in inflate.c + * - Make hbuf[] unsigned char to match parameter type in inflate.c + * - Change strm->next_out[-state->offset] to *(strm->next_out - state->offset) + * to avoid negation problem on Alphas (64 bit) in inflate.c + * + * 1.2.beta3 22 Dec 2002 + * - Add comments on state->bits assertion in inffast.c + * - Add comments on op field in inftrees.h + * - Fix bug in reuse of allocated window after inflateReset() + * - Remove bit fields--back to byte structure for speed + * - Remove distance extra == 0 check in inflate_fast()--only helps for lengths + * - Change post-increments to pre-increments in inflate_fast(), PPC biased? + * - Add compile time option, POSTINC, to use post-increments instead (Intel?) + * - Make MATCH copy in inflate() much faster for when inflate_fast() not used + * - Use local copies of stream next and avail values, as well as local bit + * buffer and bit count in inflate()--for speed when inflate_fast() not used + * + * 1.2.beta4 1 Jan 2003 + * - Split ptr - 257 statements in inflate_table() to avoid compiler warnings + * - Move a comment on output buffer sizes from inffast.c to inflate.c + * - Add comments in inffast.c to introduce the inflate_fast() routine + * - Rearrange window copies in inflate_fast() for speed and simplification + * - Unroll last copy for window match in inflate_fast() + * - Use local copies of window variables in inflate_fast() for speed + * - Pull out common write == 0 case for speed in inflate_fast() + * - Make op and len in inflate_fast() unsigned for consistency + * - Add FAR to lcode and dcode declarations in inflate_fast() + * - Simplified bad distance check in inflate_fast() + * - Added inflateBackInit(), inflateBack(), and inflateBackEnd() in new + * source file infback.c to provide a call-back interface to inflate for + * programs like gzip and unzip -- uses window as output buffer to avoid + * window copying + * + * 1.2.beta5 1 Jan 2003 + * - Improved inflateBack() interface to allow the caller to provide initial + * input in strm. + * - Fixed stored blocks bug in inflateBack() + * + * 1.2.beta6 4 Jan 2003 + * - Added comments in inffast.c on effectiveness of POSTINC + * - Typecasting all around to reduce compiler warnings + * - Changed loops from while (1) or do {} while (1) to for (;;), again to + * make compilers happy + * - Changed type of window in inflateBackInit() to unsigned char * + * + * 1.2.beta7 27 Jan 2003 + * - Changed many types to unsigned or unsigned short to avoid warnings + * - Added inflateCopy() function + * + * 1.2.0 9 Mar 2003 + * - Changed inflateBack() interface to provide separate opaque descriptors + * for the in() and out() functions + * - Changed inflateBack() argument and in_func typedef to swap the length + * and buffer address return values for the input function + * - Check next_in and next_out for Z_NULL on entry to inflate() + * + * The history for versions after 1.2.0 are in ChangeLog in zlib distribution. + */ + +#include "zutil.h" +#include "inftrees.h" +#include "inflate.h" +#include "inffast.h" + +#ifdef MAKEFIXED +# ifndef BUILDFIXED +# define BUILDFIXED +# endif +#endif + +/* function prototypes */ +local void fixedtables OF((struct inflate_state FAR *state)); +local int updatewindow OF((z_streamp strm, unsigned out)); +#ifdef BUILDFIXED + void makefixed OF((void)); +#endif +local unsigned syncsearch OF((unsigned FAR *have, unsigned char FAR *buf, + unsigned len)); + +int ZEXPORT inflateReset(strm) +z_streamp strm; +{ + struct inflate_state FAR *state; + + if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR; + state = (struct inflate_state FAR *)strm->state; + strm->total_in = strm->total_out = state->total = 0; + strm->msg = Z_NULL; + strm->adler = 1; /* to support ill-conceived Java test suite */ + state->mode = HEAD; + state->last = 0; + state->havedict = 0; + state->dmax = 32768U; + state->head = Z_NULL; + state->wsize = 0; + state->whave = 0; + state->write = 0; + state->hold = 0; + state->bits = 0; + state->lencode = state->distcode = state->next = state->codes; + Tracev((stderr, "inflate: reset\n")); + return Z_OK; +} + +int ZEXPORT inflatePrime(strm, bits, value) +z_streamp strm; +int bits; +int value; +{ + struct inflate_state FAR *state; + + if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR; + state = (struct inflate_state FAR *)strm->state; + if (bits > 16 || state->bits + bits > 32) return Z_STREAM_ERROR; + value &= (1L << bits) - 1; + state->hold += value << state->bits; + state->bits += bits; + return Z_OK; +} + +int ZEXPORT inflateInit2_(strm, windowBits, version, stream_size) +z_streamp strm; +int windowBits; +const char *version; +int stream_size; +{ + struct inflate_state FAR *state; + + if (version == Z_NULL || version[0] != ZLIB_VERSION[0] || + stream_size != (int)(sizeof(z_stream))) + return Z_VERSION_ERROR; + if (strm == Z_NULL) return Z_STREAM_ERROR; + strm->msg = Z_NULL; /* in case we return an error */ + if (strm->zalloc == (alloc_func)0) { + strm->zalloc = zcalloc; + strm->opaque = (voidpf)0; + } + if (strm->zfree == (free_func)0) strm->zfree = zcfree; + state = (struct inflate_state FAR *) + ZALLOC(strm, 1, sizeof(struct inflate_state)); + if (state == Z_NULL) return Z_MEM_ERROR; + Tracev((stderr, "inflate: allocated\n")); + strm->state = (struct internal_state FAR *)state; + if (windowBits < 0) { + state->wrap = 0; + windowBits = -windowBits; + } + else { + state->wrap = (windowBits >> 4) + 1; +#ifdef GUNZIP + if (windowBits < 48) windowBits &= 15; +#endif + } + if (windowBits < 8 || windowBits > 15) { + ZFREE(strm, state); + strm->state = Z_NULL; + return Z_STREAM_ERROR; + } + state->wbits = (unsigned)windowBits; + state->window = Z_NULL; + return inflateReset(strm); +} + +int ZEXPORT inflateInit_(strm, version, stream_size) +z_streamp strm; +const char *version; +int stream_size; +{ + return inflateInit2_(strm, DEF_WBITS, version, stream_size); +} + +/* + Return state with length and distance decoding tables and index sizes set to + fixed code decoding. Normally this returns fixed tables from inffixed.h. + If BUILDFIXED is defined, then instead this routine builds the tables the + first time it's called, and returns those tables the first time and + thereafter. This reduces the size of the code by about 2K bytes, in + exchange for a little execution time. However, BUILDFIXED should not be + used for threaded applications, since the rewriting of the tables and virgin + may not be thread-safe. + */ +local void fixedtables(state) +struct inflate_state FAR *state; +{ +#ifdef BUILDFIXED + static int virgin = 1; + static code *lenfix, *distfix; + static code fixed[544]; + + /* build fixed huffman tables if first call (may not be thread safe) */ + if (virgin) { + unsigned sym, bits; + static code *next; + + /* literal/length table */ + sym = 0; + while (sym < 144) state->lens[sym++] = 8; + while (sym < 256) state->lens[sym++] = 9; + while (sym < 280) state->lens[sym++] = 7; + while (sym < 288) state->lens[sym++] = 8; + next = fixed; + lenfix = next; + bits = 9; + inflate_table(LENS, state->lens, 288, &(next), &(bits), state->work); + + /* distance table */ + sym = 0; + while (sym < 32) state->lens[sym++] = 5; + distfix = next; + bits = 5; + inflate_table(DISTS, state->lens, 32, &(next), &(bits), state->work); + + /* do this just once */ + virgin = 0; + } +#else /* !BUILDFIXED */ +# include "inffixed.h" +#endif /* BUILDFIXED */ + state->lencode = lenfix; + state->lenbits = 9; + state->distcode = distfix; + state->distbits = 5; +} + +#ifdef MAKEFIXED +#include + +/* + Write out the inffixed.h that is #include'd above. Defining MAKEFIXED also + defines BUILDFIXED, so the tables are built on the fly. makefixed() writes + those tables to stdout, which would be piped to inffixed.h. A small program + can simply call makefixed to do this: + + void makefixed(void); + + int main(void) + { + makefixed(); + return 0; + } + + Then that can be linked with zlib built with MAKEFIXED defined and run: + + a.out > inffixed.h + */ +void makefixed() +{ + unsigned low, size; + struct inflate_state state; + + fixedtables(&state); + puts(" /* inffixed.h -- table for decoding fixed codes"); + puts(" * Generated automatically by makefixed()."); + puts(" */"); + puts(""); + puts(" /* WARNING: this file should *not* be used by applications."); + puts(" It is part of the implementation of this library and is"); + puts(" subject to change. Applications should only use zlib.h."); + puts(" */"); + puts(""); + size = 1U << 9; + printf(" static const code lenfix[%u] = {", size); + low = 0; + for (;;) { + if ((low % 7) == 0) printf("\n "); + printf("{%u,%u,%d}", state.lencode[low].op, state.lencode[low].bits, + state.lencode[low].val); + if (++low == size) break; + putchar(','); + } + puts("\n };"); + size = 1U << 5; + printf("\n static const code distfix[%u] = {", size); + low = 0; + for (;;) { + if ((low % 6) == 0) printf("\n "); + printf("{%u,%u,%d}", state.distcode[low].op, state.distcode[low].bits, + state.distcode[low].val); + if (++low == size) break; + putchar(','); + } + puts("\n };"); +} +#endif /* MAKEFIXED */ + +/* + Update the window with the last wsize (normally 32K) bytes written before + returning. If window does not exist yet, create it. This is only called + when a window is already in use, or when output has been written during this + inflate call, but the end of the deflate stream has not been reached yet. + It is also called to create a window for dictionary data when a dictionary + is loaded. + + Providing output buffers larger than 32K to inflate() should provide a speed + advantage, since only the last 32K of output is copied to the sliding window + upon return from inflate(), and since all distances after the first 32K of + output will fall in the output data, making match copies simpler and faster. + The advantage may be dependent on the size of the processor's data caches. + */ +local int updatewindow(strm, out) +z_streamp strm; +unsigned out; +{ + struct inflate_state FAR *state; + unsigned copy, dist; + + state = (struct inflate_state FAR *)strm->state; + + /* if it hasn't been done already, allocate space for the window */ + if (state->window == Z_NULL) { + state->window = (unsigned char FAR *) + ZALLOC(strm, 1U << state->wbits, + sizeof(unsigned char)); + if (state->window == Z_NULL) return 1; + } + + /* if window not in use yet, initialize */ + if (state->wsize == 0) { + state->wsize = 1U << state->wbits; + state->write = 0; + state->whave = 0; + } + + /* copy state->wsize or less output bytes into the circular window */ + copy = out - strm->avail_out; + if (copy >= state->wsize) { + zmemcpy(state->window, strm->next_out - state->wsize, state->wsize); + state->write = 0; + state->whave = state->wsize; + } + else { + dist = state->wsize - state->write; + if (dist > copy) dist = copy; + zmemcpy(state->window + state->write, strm->next_out - copy, dist); + copy -= dist; + if (copy) { + zmemcpy(state->window, strm->next_out - copy, copy); + state->write = copy; + state->whave = state->wsize; + } + else { + state->write += dist; + if (state->write == state->wsize) state->write = 0; + if (state->whave < state->wsize) state->whave += dist; + } + } + return 0; +} + +/* Macros for inflate(): */ + +/* check function to use adler32() for zlib or crc32() for gzip */ +#ifdef GUNZIP +# define UPDATE(check, buf, len) \ + (state->flags ? crc32(check, buf, len) : adler32(check, buf, len)) +#else +# define UPDATE(check, buf, len) adler32(check, buf, len) +#endif + +/* check macros for header crc */ +#ifdef GUNZIP +# define CRC2(check, word) \ + do { \ + hbuf[0] = (unsigned char)(word); \ + hbuf[1] = (unsigned char)((word) >> 8); \ + check = crc32(check, hbuf, 2); \ + } while (0) + +# define CRC4(check, word) \ + do { \ + hbuf[0] = (unsigned char)(word); \ + hbuf[1] = (unsigned char)((word) >> 8); \ + hbuf[2] = (unsigned char)((word) >> 16); \ + hbuf[3] = (unsigned char)((word) >> 24); \ + check = crc32(check, hbuf, 4); \ + } while (0) +#endif + +/* Load registers with state in inflate() for speed */ +#define LOAD() \ + do { \ + put = strm->next_out; \ + left = strm->avail_out; \ + next = strm->next_in; \ + have = strm->avail_in; \ + hold = state->hold; \ + bits = state->bits; \ + } while (0) + +/* Restore state from registers in inflate() */ +#define RESTORE() \ + do { \ + strm->next_out = put; \ + strm->avail_out = left; \ + strm->next_in = next; \ + strm->avail_in = have; \ + state->hold = hold; \ + state->bits = bits; \ + } while (0) + +/* Clear the input bit accumulator */ +#define INITBITS() \ + do { \ + hold = 0; \ + bits = 0; \ + } while (0) + +/* Get a byte of input into the bit accumulator, or return from inflate() + if there is no input available. */ +#define PULLBYTE() \ + do { \ + if (have == 0) goto inf_leave; \ + have--; \ + hold += (unsigned long)(*next++) << bits; \ + bits += 8; \ + } while (0) + +/* Assure that there are at least n bits in the bit accumulator. If there is + not enough available input to do that, then return from inflate(). */ +#define NEEDBITS(n) \ + do { \ + while (bits < (unsigned)(n)) \ + PULLBYTE(); \ + } while (0) + +/* Return the low n bits of the bit accumulator (n < 16) */ +#define BITS(n) \ + ((unsigned)hold & ((1U << (n)) - 1)) + +/* Remove n bits from the bit accumulator */ +#define DROPBITS(n) \ + do { \ + hold >>= (n); \ + bits -= (unsigned)(n); \ + } while (0) + +/* Remove zero to seven bits as needed to go to a byte boundary */ +#define BYTEBITS() \ + do { \ + hold >>= bits & 7; \ + bits -= bits & 7; \ + } while (0) + +/* Reverse the bytes in a 32-bit value */ +#define REVERSE(q) \ + ((((q) >> 24) & 0xff) + (((q) >> 8) & 0xff00) + \ + (((q) & 0xff00) << 8) + (((q) & 0xff) << 24)) + +/* + inflate() uses a state machine to process as much input data and generate as + much output data as possible before returning. The state machine is + structured roughly as follows: + + for (;;) switch (state) { + ... + case STATEn: + if (not enough input data or output space to make progress) + return; + ... make progress ... + state = STATEm; + break; + ... + } + + so when inflate() is called again, the same case is attempted again, and + if the appropriate resources are provided, the machine proceeds to the + next state. The NEEDBITS() macro is usually the way the state evaluates + whether it can proceed or should return. NEEDBITS() does the return if + the requested bits are not available. The typical use of the BITS macros + is: + + NEEDBITS(n); + ... do something with BITS(n) ... + DROPBITS(n); + + where NEEDBITS(n) either returns from inflate() if there isn't enough + input left to load n bits into the accumulator, or it continues. BITS(n) + gives the low n bits in the accumulator. When done, DROPBITS(n) drops + the low n bits off the accumulator. INITBITS() clears the accumulator + and sets the number of available bits to zero. BYTEBITS() discards just + enough bits to put the accumulator on a byte boundary. After BYTEBITS() + and a NEEDBITS(8), then BITS(8) would return the next byte in the stream. + + NEEDBITS(n) uses PULLBYTE() to get an available byte of input, or to return + if there is no input available. The decoding of variable length codes uses + PULLBYTE() directly in order to pull just enough bytes to decode the next + code, and no more. + + Some states loop until they get enough input, making sure that enough + state information is maintained to continue the loop where it left off + if NEEDBITS() returns in the loop. For example, want, need, and keep + would all have to actually be part of the saved state in case NEEDBITS() + returns: + + case STATEw: + while (want < need) { + NEEDBITS(n); + keep[want++] = BITS(n); + DROPBITS(n); + } + state = STATEx; + case STATEx: + + As shown above, if the next state is also the next case, then the break + is omitted. + + A state may also return if there is not enough output space available to + complete that state. Those states are copying stored data, writing a + literal byte, and copying a matching string. + + When returning, a "goto inf_leave" is used to update the total counters, + update the check value, and determine whether any progress has been made + during that inflate() call in order to return the proper return code. + Progress is defined as a change in either strm->avail_in or strm->avail_out. + When there is a window, goto inf_leave will update the window with the last + output written. If a goto inf_leave occurs in the middle of decompression + and there is no window currently, goto inf_leave will create one and copy + output to the window for the next call of inflate(). + + In this implementation, the flush parameter of inflate() only affects the + return code (per zlib.h). inflate() always writes as much as possible to + strm->next_out, given the space available and the provided input--the effect + documented in zlib.h of Z_SYNC_FLUSH. Furthermore, inflate() always defers + the allocation of and copying into a sliding window until necessary, which + provides the effect documented in zlib.h for Z_FINISH when the entire input + stream available. So the only thing the flush parameter actually does is: + when flush is set to Z_FINISH, inflate() cannot return Z_OK. Instead it + will return Z_BUF_ERROR if it has not reached the end of the stream. + */ + +int ZEXPORT inflate(strm, flush) +z_streamp strm; +int flush; +{ + struct inflate_state FAR *state; + unsigned char FAR *next; /* next input */ + unsigned char FAR *put; /* next output */ + unsigned have, left; /* available input and output */ + unsigned long hold; /* bit buffer */ + unsigned bits; /* bits in bit buffer */ + unsigned in, out; /* save starting available input and output */ + unsigned copy; /* number of stored or match bytes to copy */ + unsigned char FAR *from; /* where to copy match bytes from */ + code this; /* current decoding table entry */ + code last; /* parent table entry */ + unsigned len; /* length to copy for repeats, bits to drop */ + int ret; /* return code */ +#ifdef GUNZIP + unsigned char hbuf[4]; /* buffer for gzip header crc calculation */ +#endif + static const unsigned short order[19] = /* permutation of code lengths */ + {16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15}; + + if (strm == Z_NULL || strm->state == Z_NULL || strm->next_out == Z_NULL || + (strm->next_in == Z_NULL && strm->avail_in != 0)) + return Z_STREAM_ERROR; + + state = (struct inflate_state FAR *)strm->state; + if (state->mode == TYPE) state->mode = TYPEDO; /* skip check */ + LOAD(); + in = have; + out = left; + ret = Z_OK; + for (;;) + switch (state->mode) { + case HEAD: + if (state->wrap == 0) { + state->mode = TYPEDO; + break; + } + NEEDBITS(16); +#ifdef GUNZIP + if ((state->wrap & 2) && hold == 0x8b1f) { /* gzip header */ + state->check = crc32(0L, Z_NULL, 0); + CRC2(state->check, hold); + INITBITS(); + state->mode = FLAGS; + break; + } + state->flags = 0; /* expect zlib header */ + if (state->head != Z_NULL) + state->head->done = -1; + if (!(state->wrap & 1) || /* check if zlib header allowed */ +#else + if ( +#endif + ((BITS(8) << 8) + (hold >> 8)) % 31) { + strm->msg = "incorrect header check"; + state->mode = BAD; + break; + } + if (BITS(4) != Z_DEFLATED) { + strm->msg = "unknown compression method"; + state->mode = BAD; + break; + } + DROPBITS(4); + len = BITS(4) + 8; + if (len > state->wbits) { + strm->msg = "invalid window size"; + state->mode = BAD; + break; + } + state->dmax = 1U << len; + Tracev((stderr, "inflate: zlib header ok\n")); + strm->adler = state->check = adler32(0L, Z_NULL, 0); + state->mode = hold & 0x200 ? DICTID : TYPE; + INITBITS(); + break; +#ifdef GUNZIP + case FLAGS: + NEEDBITS(16); + state->flags = (int)(hold); + if ((state->flags & 0xff) != Z_DEFLATED) { + strm->msg = "unknown compression method"; + state->mode = BAD; + break; + } + if (state->flags & 0xe000) { + strm->msg = "unknown header flags set"; + state->mode = BAD; + break; + } + if (state->head != Z_NULL) + state->head->text = (int)((hold >> 8) & 1); + if (state->flags & 0x0200) CRC2(state->check, hold); + INITBITS(); + state->mode = TIME; + case TIME: + NEEDBITS(32); + if (state->head != Z_NULL) + state->head->time = hold; + if (state->flags & 0x0200) CRC4(state->check, hold); + INITBITS(); + state->mode = OS; + case OS: + NEEDBITS(16); + if (state->head != Z_NULL) { + state->head->xflags = (int)(hold & 0xff); + state->head->os = (int)(hold >> 8); + } + if (state->flags & 0x0200) CRC2(state->check, hold); + INITBITS(); + state->mode = EXLEN; + case EXLEN: + if (state->flags & 0x0400) { + NEEDBITS(16); + state->length = (unsigned)(hold); + if (state->head != Z_NULL) + state->head->extra_len = (unsigned)hold; + if (state->flags & 0x0200) CRC2(state->check, hold); + INITBITS(); + } + else if (state->head != Z_NULL) + state->head->extra = Z_NULL; + state->mode = EXTRA; + case EXTRA: + if (state->flags & 0x0400) { + copy = state->length; + if (copy > have) copy = have; + if (copy) { + if (state->head != Z_NULL && + state->head->extra != Z_NULL) { + len = state->head->extra_len - state->length; + zmemcpy(state->head->extra + len, next, + len + copy > state->head->extra_max ? + state->head->extra_max - len : copy); + } + if (state->flags & 0x0200) + state->check = crc32(state->check, next, copy); + have -= copy; + next += copy; + state->length -= copy; + } + if (state->length) goto inf_leave; + } + state->length = 0; + state->mode = NAME; + case NAME: + if (state->flags & 0x0800) { + if (have == 0) goto inf_leave; + copy = 0; + do { + len = (unsigned)(next[copy++]); + if (state->head != Z_NULL && + state->head->name != Z_NULL && + state->length < state->head->name_max) + state->head->name[state->length++] = len; + } while (len && copy < have); + if (state->flags & 0x0200) + state->check = crc32(state->check, next, copy); + have -= copy; + next += copy; + if (len) goto inf_leave; + } + else if (state->head != Z_NULL) + state->head->name = Z_NULL; + state->length = 0; + state->mode = COMMENT; + case COMMENT: + if (state->flags & 0x1000) { + if (have == 0) goto inf_leave; + copy = 0; + do { + len = (unsigned)(next[copy++]); + if (state->head != Z_NULL && + state->head->comment != Z_NULL && + state->length < state->head->comm_max) + state->head->comment[state->length++] = len; + } while (len && copy < have); + if (state->flags & 0x0200) + state->check = crc32(state->check, next, copy); + have -= copy; + next += copy; + if (len) goto inf_leave; + } + else if (state->head != Z_NULL) + state->head->comment = Z_NULL; + state->mode = HCRC; + case HCRC: + if (state->flags & 0x0200) { + NEEDBITS(16); + if (hold != (state->check & 0xffff)) { + strm->msg = "header crc mismatch"; + state->mode = BAD; + break; + } + INITBITS(); + } + if (state->head != Z_NULL) { + state->head->hcrc = (int)((state->flags >> 9) & 1); + state->head->done = 1; + } + strm->adler = state->check = crc32(0L, Z_NULL, 0); + state->mode = TYPE; + break; +#endif + case DICTID: + NEEDBITS(32); + strm->adler = state->check = REVERSE(hold); + INITBITS(); + state->mode = DICT; + case DICT: + if (state->havedict == 0) { + RESTORE(); + return Z_NEED_DICT; + } + strm->adler = state->check = adler32(0L, Z_NULL, 0); + state->mode = TYPE; + case TYPE: + if (flush == Z_BLOCK) goto inf_leave; + case TYPEDO: + if (state->last) { + BYTEBITS(); + state->mode = CHECK; + break; + } + NEEDBITS(3); + state->last = BITS(1); + DROPBITS(1); + switch (BITS(2)) { + case 0: /* stored block */ + Tracev((stderr, "inflate: stored block%s\n", + state->last ? " (last)" : "")); + state->mode = STORED; + break; + case 1: /* fixed block */ + fixedtables(state); + Tracev((stderr, "inflate: fixed codes block%s\n", + state->last ? " (last)" : "")); + state->mode = LEN; /* decode codes */ + break; + case 2: /* dynamic block */ + Tracev((stderr, "inflate: dynamic codes block%s\n", + state->last ? " (last)" : "")); + state->mode = TABLE; + break; + case 3: + strm->msg = "invalid block type"; + state->mode = BAD; + } + DROPBITS(2); + break; + case STORED: + BYTEBITS(); /* go to byte boundary */ + NEEDBITS(32); + if ((hold & 0xffff) != ((hold >> 16) ^ 0xffff)) { + strm->msg = "invalid stored block lengths"; + state->mode = BAD; + break; + } + state->length = (unsigned)hold & 0xffff; + Tracev((stderr, "inflate: stored length %u\n", + state->length)); + INITBITS(); + state->mode = COPY; + case COPY: + copy = state->length; + if (copy) { + if (copy > have) copy = have; + if (copy > left) copy = left; + if (copy == 0) goto inf_leave; + zmemcpy(put, next, copy); + have -= copy; + next += copy; + left -= copy; + put += copy; + state->length -= copy; + break; + } + Tracev((stderr, "inflate: stored end\n")); + state->mode = TYPE; + break; + case TABLE: + NEEDBITS(14); + state->nlen = BITS(5) + 257; + DROPBITS(5); + state->ndist = BITS(5) + 1; + DROPBITS(5); + state->ncode = BITS(4) + 4; + DROPBITS(4); +#ifndef PKZIP_BUG_WORKAROUND + if (state->nlen > 286 || state->ndist > 30) { + strm->msg = "too many length or distance symbols"; + state->mode = BAD; + break; + } +#endif + Tracev((stderr, "inflate: table sizes ok\n")); + state->have = 0; + state->mode = LENLENS; + case LENLENS: + while (state->have < state->ncode) { + NEEDBITS(3); + state->lens[order[state->have++]] = (unsigned short)BITS(3); + DROPBITS(3); + } + while (state->have < 19) + state->lens[order[state->have++]] = 0; + state->next = state->codes; + state->lencode = (code const FAR *)(state->next); + state->lenbits = 7; + ret = inflate_table(CODES, state->lens, 19, &(state->next), + &(state->lenbits), state->work); + if (ret) { + strm->msg = "invalid code lengths set"; + state->mode = BAD; + break; + } + Tracev((stderr, "inflate: code lengths ok\n")); + state->have = 0; + state->mode = CODELENS; + case CODELENS: + while (state->have < state->nlen + state->ndist) { + for (;;) { + this = state->lencode[BITS(state->lenbits)]; + if ((unsigned)(this.bits) <= bits) break; + PULLBYTE(); + } + if (this.val < 16) { + NEEDBITS(this.bits); + DROPBITS(this.bits); + state->lens[state->have++] = this.val; + } + else { + if (this.val == 16) { + NEEDBITS(this.bits + 2); + DROPBITS(this.bits); + if (state->have == 0) { + strm->msg = "invalid bit length repeat"; + state->mode = BAD; + break; + } + len = state->lens[state->have - 1]; + copy = 3 + BITS(2); + DROPBITS(2); + } + else if (this.val == 17) { + NEEDBITS(this.bits + 3); + DROPBITS(this.bits); + len = 0; + copy = 3 + BITS(3); + DROPBITS(3); + } + else { + NEEDBITS(this.bits + 7); + DROPBITS(this.bits); + len = 0; + copy = 11 + BITS(7); + DROPBITS(7); + } + if (state->have + copy > state->nlen + state->ndist) { + strm->msg = "invalid bit length repeat"; + state->mode = BAD; + break; + } + while (copy--) + state->lens[state->have++] = (unsigned short)len; + } + } + + /* handle error breaks in while */ + if (state->mode == BAD) break; + + /* build code tables */ + state->next = state->codes; + state->lencode = (code const FAR *)(state->next); + state->lenbits = 9; + ret = inflate_table(LENS, state->lens, state->nlen, &(state->next), + &(state->lenbits), state->work); + if (ret) { + strm->msg = "invalid literal/lengths set"; + state->mode = BAD; + break; + } + state->distcode = (code const FAR *)(state->next); + state->distbits = 6; + ret = inflate_table(DISTS, state->lens + state->nlen, state->ndist, + &(state->next), &(state->distbits), state->work); + if (ret) { + strm->msg = "invalid distances set"; + state->mode = BAD; + break; + } + Tracev((stderr, "inflate: codes ok\n")); + state->mode = LEN; + case LEN: + if (have >= 6 && left >= 258) { + RESTORE(); + inflate_fast(strm, out); + LOAD(); + break; + } + for (;;) { + this = state->lencode[BITS(state->lenbits)]; + if ((unsigned)(this.bits) <= bits) break; + PULLBYTE(); + } + if (this.op && (this.op & 0xf0) == 0) { + last = this; + for (;;) { + this = state->lencode[last.val + + (BITS(last.bits + last.op) >> last.bits)]; + if ((unsigned)(last.bits + this.bits) <= bits) break; + PULLBYTE(); + } + DROPBITS(last.bits); + } + DROPBITS(this.bits); + state->length = (unsigned)this.val; + if ((int)(this.op) == 0) { + Tracevv((stderr, this.val >= 0x20 && this.val < 0x7f ? + "inflate: literal '%c'\n" : + "inflate: literal 0x%02x\n", this.val)); + state->mode = LIT; + break; + } + if (this.op & 32) { + Tracevv((stderr, "inflate: end of block\n")); + state->mode = TYPE; + break; + } + if (this.op & 64) { + strm->msg = "invalid literal/length code"; + state->mode = BAD; + break; + } + state->extra = (unsigned)(this.op) & 15; + state->mode = LENEXT; + case LENEXT: + if (state->extra) { + NEEDBITS(state->extra); + state->length += BITS(state->extra); + DROPBITS(state->extra); + } + Tracevv((stderr, "inflate: length %u\n", state->length)); + state->mode = DIST; + case DIST: + for (;;) { + this = state->distcode[BITS(state->distbits)]; + if ((unsigned)(this.bits) <= bits) break; + PULLBYTE(); + } + if ((this.op & 0xf0) == 0) { + last = this; + for (;;) { + this = state->distcode[last.val + + (BITS(last.bits + last.op) >> last.bits)]; + if ((unsigned)(last.bits + this.bits) <= bits) break; + PULLBYTE(); + } + DROPBITS(last.bits); + } + DROPBITS(this.bits); + if (this.op & 64) { + strm->msg = "invalid distance code"; + state->mode = BAD; + break; + } + state->offset = (unsigned)this.val; + state->extra = (unsigned)(this.op) & 15; + state->mode = DISTEXT; + case DISTEXT: + if (state->extra) { + NEEDBITS(state->extra); + state->offset += BITS(state->extra); + DROPBITS(state->extra); + } +#ifdef INFLATE_STRICT + if (state->offset > state->dmax) { + strm->msg = "invalid distance too far back"; + state->mode = BAD; + break; + } +#endif + if (state->offset > state->whave + out - left) { + strm->msg = "invalid distance too far back"; + state->mode = BAD; + break; + } + Tracevv((stderr, "inflate: distance %u\n", state->offset)); + state->mode = MATCH; + case MATCH: + if (left == 0) goto inf_leave; + copy = out - left; + if (state->offset > copy) { /* copy from window */ + copy = state->offset - copy; + if (copy > state->write) { + copy -= state->write; + from = state->window + (state->wsize - copy); + } + else + from = state->window + (state->write - copy); + if (copy > state->length) copy = state->length; + } + else { /* copy from output */ + from = put - state->offset; + copy = state->length; + } + if (copy > left) copy = left; + left -= copy; + state->length -= copy; + do { + *put++ = *from++; + } while (--copy); + if (state->length == 0) state->mode = LEN; + break; + case LIT: + if (left == 0) goto inf_leave; + *put++ = (unsigned char)(state->length); + left--; + state->mode = LEN; + break; + case CHECK: + if (state->wrap) { + NEEDBITS(32); + out -= left; + strm->total_out += out; + state->total += out; + if (out) + strm->adler = state->check = + UPDATE(state->check, put - out, out); + out = left; + if (( +#ifdef GUNZIP + state->flags ? hold : +#endif + REVERSE(hold)) != state->check) { + strm->msg = "incorrect data check"; + state->mode = BAD; + break; + } + INITBITS(); + Tracev((stderr, "inflate: check matches trailer\n")); + } +#ifdef GUNZIP + state->mode = LENGTH; + case LENGTH: + if (state->wrap && state->flags) { + NEEDBITS(32); + if (hold != (state->total & 0xffffffffUL)) { + strm->msg = "incorrect length check"; + state->mode = BAD; + break; + } + INITBITS(); + Tracev((stderr, "inflate: length matches trailer\n")); + } +#endif + state->mode = DONE; + case DONE: + ret = Z_STREAM_END; + goto inf_leave; + case BAD: + ret = Z_DATA_ERROR; + goto inf_leave; + case MEM: + return Z_MEM_ERROR; + case SYNC: + default: + return Z_STREAM_ERROR; + } + + /* + Return from inflate(), updating the total counts and the check value. + If there was no progress during the inflate() call, return a buffer + error. Call updatewindow() to create and/or update the window state. + Note: a memory error from inflate() is non-recoverable. + */ + inf_leave: + RESTORE(); + if (state->wsize || (state->mode < CHECK && out != strm->avail_out)) + if (updatewindow(strm, out)) { + state->mode = MEM; + return Z_MEM_ERROR; + } + in -= strm->avail_in; + out -= strm->avail_out; + strm->total_in += in; + strm->total_out += out; + state->total += out; + if (state->wrap && out) + strm->adler = state->check = + UPDATE(state->check, strm->next_out - out, out); + strm->data_type = state->bits + (state->last ? 64 : 0) + + (state->mode == TYPE ? 128 : 0); + if (((in == 0 && out == 0) || flush == Z_FINISH) && ret == Z_OK) + ret = Z_BUF_ERROR; + return ret; +} + +int ZEXPORT inflateEnd(strm) +z_streamp strm; +{ + struct inflate_state FAR *state; + if (strm == Z_NULL || strm->state == Z_NULL || strm->zfree == (free_func)0) + return Z_STREAM_ERROR; + state = (struct inflate_state FAR *)strm->state; + if (state->window != Z_NULL) ZFREE(strm, state->window); + ZFREE(strm, strm->state); + strm->state = Z_NULL; + Tracev((stderr, "inflate: end\n")); + return Z_OK; +} + +int ZEXPORT inflateSetDictionary(strm, dictionary, dictLength) +z_streamp strm; +const Bytef *dictionary; +uInt dictLength; +{ + struct inflate_state FAR *state; + unsigned long id; + + /* check state */ + if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR; + state = (struct inflate_state FAR *)strm->state; + if (state->wrap != 0 && state->mode != DICT) + return Z_STREAM_ERROR; + + /* check for correct dictionary id */ + if (state->mode == DICT) { + id = adler32(0L, Z_NULL, 0); + id = adler32(id, dictionary, dictLength); + if (id != state->check) + return Z_DATA_ERROR; + } + + /* copy dictionary to window */ + if (updatewindow(strm, strm->avail_out)) { + state->mode = MEM; + return Z_MEM_ERROR; + } + if (dictLength > state->wsize) { + zmemcpy(state->window, dictionary + dictLength - state->wsize, + state->wsize); + state->whave = state->wsize; + } + else { + zmemcpy(state->window + state->wsize - dictLength, dictionary, + dictLength); + state->whave = dictLength; + } + state->havedict = 1; + Tracev((stderr, "inflate: dictionary set\n")); + return Z_OK; +} + +int ZEXPORT inflateGetHeader(strm, head) +z_streamp strm; +gz_headerp head; +{ + struct inflate_state FAR *state; + + /* check state */ + if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR; + state = (struct inflate_state FAR *)strm->state; + if ((state->wrap & 2) == 0) return Z_STREAM_ERROR; + + /* save header structure */ + state->head = head; + head->done = 0; + return Z_OK; +} + +/* + Search buf[0..len-1] for the pattern: 0, 0, 0xff, 0xff. Return when found + or when out of input. When called, *have is the number of pattern bytes + found in order so far, in 0..3. On return *have is updated to the new + state. If on return *have equals four, then the pattern was found and the + return value is how many bytes were read including the last byte of the + pattern. If *have is less than four, then the pattern has not been found + yet and the return value is len. In the latter case, syncsearch() can be + called again with more data and the *have state. *have is initialized to + zero for the first call. + */ +local unsigned syncsearch(have, buf, len) +unsigned FAR *have; +unsigned char FAR *buf; +unsigned len; +{ + unsigned got; + unsigned next; + + got = *have; + next = 0; + while (next < len && got < 4) { + if ((int)(buf[next]) == (got < 2 ? 0 : 0xff)) + got++; + else if (buf[next]) + got = 0; + else + got = 4 - got; + next++; + } + *have = got; + return next; +} + +int ZEXPORT inflateSync(strm) +z_streamp strm; +{ + unsigned len; /* number of bytes to look at or looked at */ + unsigned long in, out; /* temporary to save total_in and total_out */ + unsigned char buf[4]; /* to restore bit buffer to byte string */ + struct inflate_state FAR *state; + + /* check parameters */ + if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR; + state = (struct inflate_state FAR *)strm->state; + if (strm->avail_in == 0 && state->bits < 8) return Z_BUF_ERROR; + + /* if first time, start search in bit buffer */ + if (state->mode != SYNC) { + state->mode = SYNC; + state->hold <<= state->bits & 7; + state->bits -= state->bits & 7; + len = 0; + while (state->bits >= 8) { + buf[len++] = (unsigned char)(state->hold); + state->hold >>= 8; + state->bits -= 8; + } + state->have = 0; + syncsearch(&(state->have), buf, len); + } + + /* search available input */ + len = syncsearch(&(state->have), strm->next_in, strm->avail_in); + strm->avail_in -= len; + strm->next_in += len; + strm->total_in += len; + + /* return no joy or set up to restart inflate() on a new block */ + if (state->have != 4) return Z_DATA_ERROR; + in = strm->total_in; out = strm->total_out; + inflateReset(strm); + strm->total_in = in; strm->total_out = out; + state->mode = TYPE; + return Z_OK; +} + +/* + Returns true if inflate is currently at the end of a block generated by + Z_SYNC_FLUSH or Z_FULL_FLUSH. This function is used by one PPP + implementation to provide an additional safety check. PPP uses + Z_SYNC_FLUSH but removes the length bytes of the resulting empty stored + block. When decompressing, PPP checks that at the end of input packet, + inflate is waiting for these length bytes. + */ +int ZEXPORT inflateSyncPoint(strm) +z_streamp strm; +{ + struct inflate_state FAR *state; + + if (strm == Z_NULL || strm->state == Z_NULL) return Z_STREAM_ERROR; + state = (struct inflate_state FAR *)strm->state; + return state->mode == STORED && state->bits == 0; +} + +int ZEXPORT inflateCopy(dest, source) +z_streamp dest; +z_streamp source; +{ + struct inflate_state FAR *state; + struct inflate_state FAR *copy; + unsigned char FAR *window; + unsigned wsize; + + /* check input */ + if (dest == Z_NULL || source == Z_NULL || source->state == Z_NULL || + source->zalloc == (alloc_func)0 || source->zfree == (free_func)0) + return Z_STREAM_ERROR; + state = (struct inflate_state FAR *)source->state; + + /* allocate space */ + copy = (struct inflate_state FAR *) + ZALLOC(source, 1, sizeof(struct inflate_state)); + if (copy == Z_NULL) return Z_MEM_ERROR; + window = Z_NULL; + if (state->window != Z_NULL) { + window = (unsigned char FAR *) + ZALLOC(source, 1U << state->wbits, sizeof(unsigned char)); + if (window == Z_NULL) { + ZFREE(source, copy); + return Z_MEM_ERROR; + } + } + + /* copy state */ + zmemcpy(dest, source, sizeof(z_stream)); + zmemcpy(copy, state, sizeof(struct inflate_state)); + if (state->lencode >= state->codes && + state->lencode <= state->codes + ENOUGH - 1) { + copy->lencode = copy->codes + (state->lencode - state->codes); + copy->distcode = copy->codes + (state->distcode - state->codes); + } + copy->next = copy->codes + (state->next - state->codes); + if (window != Z_NULL) { + wsize = 1U << state->wbits; + zmemcpy(window, state->window, wsize); + } + copy->window = window; + dest->state = (struct internal_state FAR *)copy; + return Z_OK; +} diff --git a/snesreader/zlib/inflate.h b/snesreader/zlib/inflate.h new file mode 100644 index 00000000..07bd3e78 --- /dev/null +++ b/snesreader/zlib/inflate.h @@ -0,0 +1,115 @@ +/* inflate.h -- internal inflate state definition + * Copyright (C) 1995-2004 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* WARNING: this file should *not* be used by applications. It is + part of the implementation of the compression library and is + subject to change. Applications should only use zlib.h. + */ + +/* define NO_GZIP when compiling if you want to disable gzip header and + trailer decoding by inflate(). NO_GZIP would be used to avoid linking in + the crc code when it is not needed. For shared libraries, gzip decoding + should be left enabled. */ +#ifndef NO_GZIP +# define GUNZIP +#endif + +/* Possible inflate modes between inflate() calls */ +typedef enum { + HEAD, /* i: waiting for magic header */ + FLAGS, /* i: waiting for method and flags (gzip) */ + TIME, /* i: waiting for modification time (gzip) */ + OS, /* i: waiting for extra flags and operating system (gzip) */ + EXLEN, /* i: waiting for extra length (gzip) */ + EXTRA, /* i: waiting for extra bytes (gzip) */ + NAME, /* i: waiting for end of file name (gzip) */ + COMMENT, /* i: waiting for end of comment (gzip) */ + HCRC, /* i: waiting for header crc (gzip) */ + DICTID, /* i: waiting for dictionary check value */ + DICT, /* waiting for inflateSetDictionary() call */ + TYPE, /* i: waiting for type bits, including last-flag bit */ + TYPEDO, /* i: same, but skip check to exit inflate on new block */ + STORED, /* i: waiting for stored size (length and complement) */ + COPY, /* i/o: waiting for input or output to copy stored block */ + TABLE, /* i: waiting for dynamic block table lengths */ + LENLENS, /* i: waiting for code length code lengths */ + CODELENS, /* i: waiting for length/lit and distance code lengths */ + LEN, /* i: waiting for length/lit code */ + LENEXT, /* i: waiting for length extra bits */ + DIST, /* i: waiting for distance code */ + DISTEXT, /* i: waiting for distance extra bits */ + MATCH, /* o: waiting for output space to copy string */ + LIT, /* o: waiting for output space to write literal */ + CHECK, /* i: waiting for 32-bit check value */ + LENGTH, /* i: waiting for 32-bit length (gzip) */ + DONE, /* finished check, done -- remain here until reset */ + BAD, /* got a data error -- remain here until reset */ + MEM, /* got an inflate() memory error -- remain here until reset */ + SYNC /* looking for synchronization bytes to restart inflate() */ +} inflate_mode; + +/* + State transitions between above modes - + + (most modes can go to the BAD or MEM mode -- not shown for clarity) + + Process header: + HEAD -> (gzip) or (zlib) + (gzip) -> FLAGS -> TIME -> OS -> EXLEN -> EXTRA -> NAME + NAME -> COMMENT -> HCRC -> TYPE + (zlib) -> DICTID or TYPE + DICTID -> DICT -> TYPE + Read deflate blocks: + TYPE -> STORED or TABLE or LEN or CHECK + STORED -> COPY -> TYPE + TABLE -> LENLENS -> CODELENS -> LEN + Read deflate codes: + LEN -> LENEXT or LIT or TYPE + LENEXT -> DIST -> DISTEXT -> MATCH -> LEN + LIT -> LEN + Process trailer: + CHECK -> LENGTH -> DONE + */ + +/* state maintained between inflate() calls. Approximately 7K bytes. */ +struct inflate_state { + inflate_mode mode; /* current inflate mode */ + int last; /* true if processing last block */ + int wrap; /* bit 0 true for zlib, bit 1 true for gzip */ + int havedict; /* true if dictionary provided */ + int flags; /* gzip header method and flags (0 if zlib) */ + unsigned dmax; /* zlib header max distance (INFLATE_STRICT) */ + unsigned long check; /* protected copy of check value */ + unsigned long total; /* protected copy of output count */ + gz_headerp head; /* where to save gzip header information */ + /* sliding window */ + unsigned wbits; /* log base 2 of requested window size */ + unsigned wsize; /* window size or zero if not using window */ + unsigned whave; /* valid bytes in the window */ + unsigned write; /* window write index */ + unsigned char FAR *window; /* allocated sliding window, if needed */ + /* bit accumulator */ + unsigned long hold; /* input bit accumulator */ + unsigned bits; /* number of bits in "in" */ + /* for string and stored block copying */ + unsigned length; /* literal or length of data to copy */ + unsigned offset; /* distance back to copy string from */ + /* for table and code decoding */ + unsigned extra; /* extra bits needed */ + /* fixed and dynamic code tables */ + code const FAR *lencode; /* starting table for length/literal codes */ + code const FAR *distcode; /* starting table for distance codes */ + unsigned lenbits; /* index bits for lencode */ + unsigned distbits; /* index bits for distcode */ + /* dynamic table building */ + unsigned ncode; /* number of code length code lengths */ + unsigned nlen; /* number of length code lengths */ + unsigned ndist; /* number of distance code lengths */ + unsigned have; /* number of code lengths in lens[] */ + code FAR *next; /* next available space in codes[] */ + unsigned short lens[320]; /* temporary storage for code lengths */ + unsigned short work[288]; /* work area for code table building */ + code codes[ENOUGH]; /* space for code tables */ +}; diff --git a/snesreader/zlib/inftrees.c b/snesreader/zlib/inftrees.c new file mode 100644 index 00000000..8a9c13ff --- /dev/null +++ b/snesreader/zlib/inftrees.c @@ -0,0 +1,329 @@ +/* inftrees.c -- generate Huffman trees for efficient decoding + * Copyright (C) 1995-2005 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +#include "zutil.h" +#include "inftrees.h" + +#define MAXBITS 15 + +const char inflate_copyright[] = + " inflate 1.2.3 Copyright 1995-2005 Mark Adler "; +/* + If you use the zlib library in a product, an acknowledgment is welcome + in the documentation of your product. If for some reason you cannot + include such an acknowledgment, I would appreciate that you keep this + copyright string in the executable of your product. + */ + +/* + Build a set of tables to decode the provided canonical Huffman code. + The code lengths are lens[0..codes-1]. The result starts at *table, + whose indices are 0..2^bits-1. work is a writable array of at least + lens shorts, which is used as a work area. type is the type of code + to be generated, CODES, LENS, or DISTS. On return, zero is success, + -1 is an invalid code, and +1 means that ENOUGH isn't enough. table + on return points to the next available entry's address. bits is the + requested root table index bits, and on return it is the actual root + table index bits. It will differ if the request is greater than the + longest code or if it is less than the shortest code. + */ +int inflate_table(type, lens, codes, table, bits, work) +codetype type; +unsigned short FAR *lens; +unsigned codes; +code FAR * FAR *table; +unsigned FAR *bits; +unsigned short FAR *work; +{ + unsigned len; /* a code's length in bits */ + unsigned sym; /* index of code symbols */ + unsigned min, max; /* minimum and maximum code lengths */ + unsigned root; /* number of index bits for root table */ + unsigned curr; /* number of index bits for current table */ + unsigned drop; /* code bits to drop for sub-table */ + int left; /* number of prefix codes available */ + unsigned used; /* code entries in table used */ + unsigned huff; /* Huffman code */ + unsigned incr; /* for incrementing code, index */ + unsigned fill; /* index for replicating entries */ + unsigned low; /* low bits for current root entry */ + unsigned mask; /* mask for low root bits */ + code this; /* table entry for duplication */ + code FAR *next; /* next available space in table */ + const unsigned short FAR *base; /* base value table to use */ + const unsigned short FAR *extra; /* extra bits table to use */ + int end; /* use base and extra for symbol > end */ + unsigned short count[MAXBITS+1]; /* number of codes of each length */ + unsigned short offs[MAXBITS+1]; /* offsets in table for each length */ + static const unsigned short lbase[31] = { /* Length codes 257..285 base */ + 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, + 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0}; + static const unsigned short lext[31] = { /* Length codes 257..285 extra */ + 16, 16, 16, 16, 16, 16, 16, 16, 17, 17, 17, 17, 18, 18, 18, 18, + 19, 19, 19, 19, 20, 20, 20, 20, 21, 21, 21, 21, 16, 201, 196}; + static const unsigned short dbase[32] = { /* Distance codes 0..29 base */ + 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, + 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, + 8193, 12289, 16385, 24577, 0, 0}; + static const unsigned short dext[32] = { /* Distance codes 0..29 extra */ + 16, 16, 16, 16, 17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22, + 23, 23, 24, 24, 25, 25, 26, 26, 27, 27, + 28, 28, 29, 29, 64, 64}; + + /* + Process a set of code lengths to create a canonical Huffman code. The + code lengths are lens[0..codes-1]. Each length corresponds to the + symbols 0..codes-1. The Huffman code is generated by first sorting the + symbols by length from short to long, and retaining the symbol order + for codes with equal lengths. Then the code starts with all zero bits + for the first code of the shortest length, and the codes are integer + increments for the same length, and zeros are appended as the length + increases. For the deflate format, these bits are stored backwards + from their more natural integer increment ordering, and so when the + decoding tables are built in the large loop below, the integer codes + are incremented backwards. + + This routine assumes, but does not check, that all of the entries in + lens[] are in the range 0..MAXBITS. The caller must assure this. + 1..MAXBITS is interpreted as that code length. zero means that that + symbol does not occur in this code. + + The codes are sorted by computing a count of codes for each length, + creating from that a table of starting indices for each length in the + sorted table, and then entering the symbols in order in the sorted + table. The sorted table is work[], with that space being provided by + the caller. + + The length counts are used for other purposes as well, i.e. finding + the minimum and maximum length codes, determining if there are any + codes at all, checking for a valid set of lengths, and looking ahead + at length counts to determine sub-table sizes when building the + decoding tables. + */ + + /* accumulate lengths for codes (assumes lens[] all in 0..MAXBITS) */ + for (len = 0; len <= MAXBITS; len++) + count[len] = 0; + for (sym = 0; sym < codes; sym++) + count[lens[sym]]++; + + /* bound code lengths, force root to be within code lengths */ + root = *bits; + for (max = MAXBITS; max >= 1; max--) + if (count[max] != 0) break; + if (root > max) root = max; + if (max == 0) { /* no symbols to code at all */ + this.op = (unsigned char)64; /* invalid code marker */ + this.bits = (unsigned char)1; + this.val = (unsigned short)0; + *(*table)++ = this; /* make a table to force an error */ + *(*table)++ = this; + *bits = 1; + return 0; /* no symbols, but wait for decoding to report error */ + } + for (min = 1; min <= MAXBITS; min++) + if (count[min] != 0) break; + if (root < min) root = min; + + /* check for an over-subscribed or incomplete set of lengths */ + left = 1; + for (len = 1; len <= MAXBITS; len++) { + left <<= 1; + left -= count[len]; + if (left < 0) return -1; /* over-subscribed */ + } + if (left > 0 && (type == CODES || max != 1)) + return -1; /* incomplete set */ + + /* generate offsets into symbol table for each length for sorting */ + offs[1] = 0; + for (len = 1; len < MAXBITS; len++) + offs[len + 1] = offs[len] + count[len]; + + /* sort symbols by length, by symbol order within each length */ + for (sym = 0; sym < codes; sym++) + if (lens[sym] != 0) work[offs[lens[sym]]++] = (unsigned short)sym; + + /* + Create and fill in decoding tables. In this loop, the table being + filled is at next and has curr index bits. The code being used is huff + with length len. That code is converted to an index by dropping drop + bits off of the bottom. For codes where len is less than drop + curr, + those top drop + curr - len bits are incremented through all values to + fill the table with replicated entries. + + root is the number of index bits for the root table. When len exceeds + root, sub-tables are created pointed to by the root entry with an index + of the low root bits of huff. This is saved in low to check for when a + new sub-table should be started. drop is zero when the root table is + being filled, and drop is root when sub-tables are being filled. + + When a new sub-table is needed, it is necessary to look ahead in the + code lengths to determine what size sub-table is needed. The length + counts are used for this, and so count[] is decremented as codes are + entered in the tables. + + used keeps track of how many table entries have been allocated from the + provided *table space. It is checked when a LENS table is being made + against the space in *table, ENOUGH, minus the maximum space needed by + the worst case distance code, MAXD. This should never happen, but the + sufficiency of ENOUGH has not been proven exhaustively, hence the check. + This assumes that when type == LENS, bits == 9. + + sym increments through all symbols, and the loop terminates when + all codes of length max, i.e. all codes, have been processed. This + routine permits incomplete codes, so another loop after this one fills + in the rest of the decoding tables with invalid code markers. + */ + + /* set up for code type */ + switch (type) { + case CODES: + base = extra = work; /* dummy value--not used */ + end = 19; + break; + case LENS: + base = lbase; + base -= 257; + extra = lext; + extra -= 257; + end = 256; + break; + default: /* DISTS */ + base = dbase; + extra = dext; + end = -1; + } + + /* initialize state for loop */ + huff = 0; /* starting code */ + sym = 0; /* starting code symbol */ + len = min; /* starting code length */ + next = *table; /* current table to fill in */ + curr = root; /* current table index bits */ + drop = 0; /* current bits to drop from code for index */ + low = (unsigned)(-1); /* trigger new sub-table when len > root */ + used = 1U << root; /* use root table entries */ + mask = used - 1; /* mask for comparing low */ + + /* check available table space */ + if (type == LENS && used >= ENOUGH - MAXD) + return 1; + + /* process all codes and make table entries */ + for (;;) { + /* create table entry */ + this.bits = (unsigned char)(len - drop); + if ((int)(work[sym]) < end) { + this.op = (unsigned char)0; + this.val = work[sym]; + } + else if ((int)(work[sym]) > end) { + this.op = (unsigned char)(extra[work[sym]]); + this.val = base[work[sym]]; + } + else { + this.op = (unsigned char)(32 + 64); /* end of block */ + this.val = 0; + } + + /* replicate for those indices with low len bits equal to huff */ + incr = 1U << (len - drop); + fill = 1U << curr; + min = fill; /* save offset to next table */ + do { + fill -= incr; + next[(huff >> drop) + fill] = this; + } while (fill != 0); + + /* backwards increment the len-bit code huff */ + incr = 1U << (len - 1); + while (huff & incr) + incr >>= 1; + if (incr != 0) { + huff &= incr - 1; + huff += incr; + } + else + huff = 0; + + /* go to next symbol, update count, len */ + sym++; + if (--(count[len]) == 0) { + if (len == max) break; + len = lens[work[sym]]; + } + + /* create new sub-table if needed */ + if (len > root && (huff & mask) != low) { + /* if first time, transition to sub-tables */ + if (drop == 0) + drop = root; + + /* increment past last table */ + next += min; /* here min is 1 << curr */ + + /* determine length of next table */ + curr = len - drop; + left = (int)(1 << curr); + while (curr + drop < max) { + left -= count[curr + drop]; + if (left <= 0) break; + curr++; + left <<= 1; + } + + /* check for enough space */ + used += 1U << curr; + if (type == LENS && used >= ENOUGH - MAXD) + return 1; + + /* point entry in root table to sub-table */ + low = huff & mask; + (*table)[low].op = (unsigned char)curr; + (*table)[low].bits = (unsigned char)root; + (*table)[low].val = (unsigned short)(next - *table); + } + } + + /* + Fill in rest of table for incomplete codes. This loop is similar to the + loop above in incrementing huff for table indices. It is assumed that + len is equal to curr + drop, so there is no loop needed to increment + through high index bits. When the current sub-table is filled, the loop + drops back to the root table to fill in any remaining entries there. + */ + this.op = (unsigned char)64; /* invalid code marker */ + this.bits = (unsigned char)(len - drop); + this.val = (unsigned short)0; + while (huff != 0) { + /* when done with sub-table, drop back to root table */ + if (drop != 0 && (huff & mask) != low) { + drop = 0; + len = root; + next = *table; + this.bits = (unsigned char)len; + } + + /* put invalid code marker in table */ + next[huff >> drop] = this; + + /* backwards increment the len-bit code huff */ + incr = 1U << (len - 1); + while (huff & incr) + incr >>= 1; + if (incr != 0) { + huff &= incr - 1; + huff += incr; + } + else + huff = 0; + } + + /* set return parameters */ + *table += used; + *bits = root; + return 0; +} diff --git a/snesreader/zlib/inftrees.h b/snesreader/zlib/inftrees.h new file mode 100644 index 00000000..b1104c87 --- /dev/null +++ b/snesreader/zlib/inftrees.h @@ -0,0 +1,55 @@ +/* inftrees.h -- header to use inftrees.c + * Copyright (C) 1995-2005 Mark Adler + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* WARNING: this file should *not* be used by applications. It is + part of the implementation of the compression library and is + subject to change. Applications should only use zlib.h. + */ + +/* Structure for decoding tables. Each entry provides either the + information needed to do the operation requested by the code that + indexed that table entry, or it provides a pointer to another + table that indexes more bits of the code. op indicates whether + the entry is a pointer to another table, a literal, a length or + distance, an end-of-block, or an invalid code. For a table + pointer, the low four bits of op is the number of index bits of + that table. For a length or distance, the low four bits of op + is the number of extra bits to get after the code. bits is + the number of bits in this code or part of the code to drop off + of the bit buffer. val is the actual byte to output in the case + of a literal, the base length or distance, or the offset from + the current table to the next table. Each entry is four bytes. */ +typedef struct { + unsigned char op; /* operation, extra bits, table bits */ + unsigned char bits; /* bits in this part of the code */ + unsigned short val; /* offset in table or code value */ +} code; + +/* op values as set by inflate_table(): + 00000000 - literal + 0000tttt - table link, tttt != 0 is the number of table index bits + 0001eeee - length or distance, eeee is the number of extra bits + 01100000 - end of block + 01000000 - invalid code + */ + +/* Maximum size of dynamic tree. The maximum found in a long but non- + exhaustive search was 1444 code structures (852 for length/literals + and 592 for distances, the latter actually the result of an + exhaustive search). The true maximum is not known, but the value + below is more than safe. */ +#define ENOUGH 2048 +#define MAXD 592 + +/* Type of code to build for inftable() */ +typedef enum { + CODES, + LENS, + DISTS +} codetype; + +extern int inflate_table OF((codetype type, unsigned short FAR *lens, + unsigned codes, code FAR * FAR *table, + unsigned FAR *bits, unsigned short FAR *work)); diff --git a/snesreader/zlib/readme.txt b/snesreader/zlib/readme.txt new file mode 100644 index 00000000..d1331635 --- /dev/null +++ b/snesreader/zlib/readme.txt @@ -0,0 +1,10 @@ +This is NOT the complete zlib distribution; it is just a subset of the +source needed by the File_Extractor library. I've made some minor +changes: + +* Enabled DYNAMIC_CRC_TABLE in zconf.h, to reduce executable size +slightly. +* Made z_stream_s's msg const char* to eliminate many warnings. + +You can remove these sources and link to your own copy of zlib if +desired. diff --git a/snesreader/zlib/zconf.h b/snesreader/zlib/zconf.h new file mode 100644 index 00000000..ee5a9181 --- /dev/null +++ b/snesreader/zlib/zconf.h @@ -0,0 +1,335 @@ +/* zconf.h -- configuration of the zlib compression library + * Copyright (C) 1995-2005 Jean-loup Gailly. + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* @(#) $Id$ */ + +#ifndef ZCONF_H +#define ZCONF_H + +/* added for file_extractor; OK to remove, just increases executable size */ +#define DYNAMIC_CRC_TABLE + +/* + * If you *really* need a unique prefix for all types and library functions, + * compile with -DZ_PREFIX. The "standard" zlib should be compiled without it. + */ +#ifdef Z_PREFIX +# define deflateInit_ z_deflateInit_ +# define deflate z_deflate +# define deflateEnd z_deflateEnd +# define inflateInit_ z_inflateInit_ +# define inflate z_inflate +# define inflateEnd z_inflateEnd +# define deflateInit2_ z_deflateInit2_ +# define deflateSetDictionary z_deflateSetDictionary +# define deflateCopy z_deflateCopy +# define deflateReset z_deflateReset +# define deflateParams z_deflateParams +# define deflateBound z_deflateBound +# define deflatePrime z_deflatePrime +# define inflateInit2_ z_inflateInit2_ +# define inflateSetDictionary z_inflateSetDictionary +# define inflateSync z_inflateSync +# define inflateSyncPoint z_inflateSyncPoint +# define inflateCopy z_inflateCopy +# define inflateReset z_inflateReset +# define inflateBack z_inflateBack +# define inflateBackEnd z_inflateBackEnd +# define compress z_compress +# define compress2 z_compress2 +# define compressBound z_compressBound +# define uncompress z_uncompress +# define adler32 z_adler32 +# define crc32 z_crc32 +# define get_crc_table z_get_crc_table +# define zError z_zError + +# define alloc_func z_alloc_func +# define free_func z_free_func +# define in_func z_in_func +# define out_func z_out_func +# define Byte z_Byte +# define uInt z_uInt +# define uLong z_uLong +# define Bytef z_Bytef +# define charf z_charf +# define intf z_intf +# define uIntf z_uIntf +# define uLongf z_uLongf +# define voidpf z_voidpf +# define voidp z_voidp +#endif + +#if defined(__MSDOS__) && !defined(MSDOS) +# define MSDOS +#endif +#if (defined(OS_2) || defined(__OS2__)) && !defined(OS2) +# define OS2 +#endif +#if defined(_WINDOWS) && !defined(WINDOWS) +# define WINDOWS +#endif +#if defined(_WIN32) || defined(_WIN32_WCE) || defined(__WIN32__) +# ifndef WIN32 +# define WIN32 +# endif +#endif +#if (defined(MSDOS) || defined(OS2) || defined(WINDOWS)) && !defined(WIN32) +# if !defined(__GNUC__) && !defined(__FLAT__) && !defined(__386__) +# ifndef SYS16BIT +# define SYS16BIT +# endif +# endif +#endif + +/* + * Compile with -DMAXSEG_64K if the alloc function cannot allocate more + * than 64k bytes at a time (needed on systems with 16-bit int). + */ +#ifdef SYS16BIT +# define MAXSEG_64K +#endif +#ifdef MSDOS +# define UNALIGNED_OK +#endif + +#ifdef __STDC_VERSION__ +# ifndef STDC +# define STDC +# endif +# if __STDC_VERSION__ >= 199901L +# ifndef STDC99 +# define STDC99 +# endif +# endif +#endif +#if !defined(STDC) && (defined(__STDC__) || defined(__cplusplus)) +# define STDC +#endif +#if !defined(STDC) && (defined(__GNUC__) || defined(__BORLANDC__)) +# define STDC +#endif +#if !defined(STDC) && (defined(MSDOS) || defined(WINDOWS) || defined(WIN32)) +# define STDC +#endif +#if !defined(STDC) && (defined(OS2) || defined(__HOS_AIX__)) +# define STDC +#endif + +#if defined(__OS400__) && !defined(STDC) /* iSeries (formerly AS/400). */ +# define STDC +#endif + +#ifndef STDC +# ifndef const /* cannot use !defined(STDC) && !defined(const) on Mac */ +# define const /* note: need a more gentle solution here */ +# endif +#endif + +/* Some Mac compilers merge all .h files incorrectly: */ +#if defined(__MWERKS__)||defined(applec)||defined(THINK_C)||defined(__SC__) +# define NO_DUMMY_DECL +#endif + +/* Maximum value for memLevel in deflateInit2 */ +#ifndef MAX_MEM_LEVEL +# ifdef MAXSEG_64K +# define MAX_MEM_LEVEL 8 +# else +# define MAX_MEM_LEVEL 9 +# endif +#endif + +/* Maximum value for windowBits in deflateInit2 and inflateInit2. + * WARNING: reducing MAX_WBITS makes minigzip unable to extract .gz files + * created by gzip. (Files created by minigzip can still be extracted by + * gzip.) + */ +#ifndef MAX_WBITS +# define MAX_WBITS 15 /* 32K LZ77 window */ +#endif + +/* The memory requirements for deflate are (in bytes): + (1 << (windowBits+2)) + (1 << (memLevel+9)) + that is: 128K for windowBits=15 + 128K for memLevel = 8 (default values) + plus a few kilobytes for small objects. For example, if you want to reduce + the default memory requirements from 256K to 128K, compile with + make CFLAGS="-O -DMAX_WBITS=14 -DMAX_MEM_LEVEL=7" + Of course this will generally degrade compression (there's no free lunch). + + The memory requirements for inflate are (in bytes) 1 << windowBits + that is, 32K for windowBits=15 (default value) plus a few kilobytes + for small objects. +*/ + + /* Type declarations */ + +#ifndef OF /* function prototypes */ +# ifdef STDC +# define OF(args) args +# else +# define OF(args) () +# endif +#endif + +/* The following definitions for FAR are needed only for MSDOS mixed + * model programming (small or medium model with some far allocations). + * This was tested only with MSC; for other MSDOS compilers you may have + * to define NO_MEMCPY in zutil.h. If you don't need the mixed model, + * just define FAR to be empty. + */ +#ifdef SYS16BIT +# if defined(M_I86SM) || defined(M_I86MM) + /* MSC small or medium model */ +# define SMALL_MEDIUM +# ifdef _MSC_VER +# define FAR _far +# else +# define FAR far +# endif +# endif +# if (defined(__SMALL__) || defined(__MEDIUM__)) + /* Turbo C small or medium model */ +# define SMALL_MEDIUM +# ifdef __BORLANDC__ +# define FAR _far +# else +# define FAR far +# endif +# endif +#endif + +#if defined(WINDOWS) || defined(WIN32) + /* If building or using zlib as a DLL, define ZLIB_DLL. + * This is not mandatory, but it offers a little performance increase. + */ +# ifdef ZLIB_DLL +# if defined(WIN32) && (!defined(__BORLANDC__) || (__BORLANDC__ >= 0x500)) +# ifdef ZLIB_INTERNAL +# define ZEXTERN extern __declspec(dllexport) +# else +# define ZEXTERN extern __declspec(dllimport) +# endif +# endif +# endif /* ZLIB_DLL */ + /* If building or using zlib with the WINAPI/WINAPIV calling convention, + * define ZLIB_WINAPI. + * Caution: the standard ZLIB1.DLL is NOT compiled using ZLIB_WINAPI. + */ +# ifdef ZLIB_WINAPI +# ifdef FAR +# undef FAR +# endif +# include + /* No need for _export, use ZLIB.DEF instead. */ + /* For complete Windows compatibility, use WINAPI, not __stdcall. */ +# define ZEXPORT WINAPI +# ifdef WIN32 +# define ZEXPORTVA WINAPIV +# else +# define ZEXPORTVA FAR CDECL +# endif +# endif +#endif + +#if defined (__BEOS__) +# ifdef ZLIB_DLL +# ifdef ZLIB_INTERNAL +# define ZEXPORT __declspec(dllexport) +# define ZEXPORTVA __declspec(dllexport) +# else +# define ZEXPORT __declspec(dllimport) +# define ZEXPORTVA __declspec(dllimport) +# endif +# endif +#endif + +#ifndef ZEXTERN +# define ZEXTERN extern +#endif +#ifndef ZEXPORT +# define ZEXPORT +#endif +#ifndef ZEXPORTVA +# define ZEXPORTVA +#endif + +#ifndef FAR +# define FAR +#endif + +#if !defined(__MACTYPES__) +typedef unsigned char Byte; /* 8 bits */ +#endif +typedef unsigned int uInt; /* 16 bits or more */ +typedef unsigned long uLong; /* 32 bits or more */ + +#ifdef SMALL_MEDIUM + /* Borland C/C++ and some old MSC versions ignore FAR inside typedef */ +# define Bytef Byte FAR +#else + typedef Byte FAR Bytef; +#endif +typedef char FAR charf; +typedef int FAR intf; +typedef uInt FAR uIntf; +typedef uLong FAR uLongf; + +#ifdef STDC + typedef void const *voidpc; + typedef void FAR *voidpf; + typedef void *voidp; +#else + typedef Byte const *voidpc; + typedef Byte FAR *voidpf; + typedef Byte *voidp; +#endif + +#if 0 /* HAVE_UNISTD_H -- this line is updated by ./configure */ +# include /* for off_t */ +# include /* for SEEK_* and off_t */ +# ifdef VMS +# include /* for off_t */ +# endif +# define z_off_t off_t +#endif +#ifndef SEEK_SET +# define SEEK_SET 0 /* Seek from beginning of file. */ +# define SEEK_CUR 1 /* Seek from current position. */ +# define SEEK_END 2 /* Set file pointer to EOF plus "offset" */ +#endif +#ifndef z_off_t +# define z_off_t long +#endif + +#if defined(__OS400__) +# define NO_vsnprintf +#endif + +#if defined(__MVS__) +# define NO_vsnprintf +# ifdef FAR +# undef FAR +# endif +#endif + +/* MVS linker does not support external names larger than 8 bytes */ +#if defined(__MVS__) +# pragma map(deflateInit_,"DEIN") +# pragma map(deflateInit2_,"DEIN2") +# pragma map(deflateEnd,"DEEND") +# pragma map(deflateBound,"DEBND") +# pragma map(inflateInit_,"ININ") +# pragma map(inflateInit2_,"ININ2") +# pragma map(inflateEnd,"INEND") +# pragma map(inflateSync,"INSY") +# pragma map(inflateSetDictionary,"INSEDI") +# pragma map(compressBound,"CMBND") +# pragma map(inflate_table,"INTABL") +# pragma map(inflate_fast,"INFA") +# pragma map(inflate_copyright,"INCOPY") +#endif + +#endif /* ZCONF_H */ diff --git a/snesreader/zlib/zlib.h b/snesreader/zlib/zlib.h new file mode 100644 index 00000000..e4768717 --- /dev/null +++ b/snesreader/zlib/zlib.h @@ -0,0 +1,1357 @@ +/* zlib.h -- interface of the 'zlib' general purpose compression library + version 1.2.3, July 18th, 2005 + + Copyright (C) 1995-2005 Jean-loup Gailly and Mark Adler + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + Jean-loup Gailly Mark Adler + jloup@gzip.org madler@alumni.caltech.edu + + + The data format used by the zlib library is described by RFCs (Request for + Comments) 1950 to 1952 in the files http://www.ietf.org/rfc/rfc1950.txt + (zlib format), rfc1951.txt (deflate format) and rfc1952.txt (gzip format). +*/ + +#ifndef ZLIB_H +#define ZLIB_H + +#include "zconf.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define ZLIB_VERSION "1.2.3" +#define ZLIB_VERNUM 0x1230 + +/* + The 'zlib' compression library provides in-memory compression and + decompression functions, including integrity checks of the uncompressed + data. This version of the library supports only one compression method + (deflation) but other algorithms will be added later and will have the same + stream interface. + + Compression can be done in a single step if the buffers are large + enough (for example if an input file is mmap'ed), or can be done by + repeated calls of the compression function. In the latter case, the + application must provide more input and/or consume the output + (providing more output space) before each call. + + The compressed data format used by default by the in-memory functions is + the zlib format, which is a zlib wrapper documented in RFC 1950, wrapped + around a deflate stream, which is itself documented in RFC 1951. + + The library also supports reading and writing files in gzip (.gz) format + with an interface similar to that of stdio using the functions that start + with "gz". The gzip format is different from the zlib format. gzip is a + gzip wrapper, documented in RFC 1952, wrapped around a deflate stream. + + This library can optionally read and write gzip streams in memory as well. + + The zlib format was designed to be compact and fast for use in memory + and on communications channels. The gzip format was designed for single- + file compression on file systems, has a larger header than zlib to maintain + directory information, and uses a different, slower check method than zlib. + + The library does not install any signal handler. The decoder checks + the consistency of the compressed data, so the library should never + crash even in case of corrupted input. +*/ + +typedef voidpf (*alloc_func) OF((voidpf opaque, uInt items, uInt size)); +typedef void (*free_func) OF((voidpf opaque, voidpf address)); + +struct internal_state; + +typedef struct z_stream_s { + Bytef *next_in; /* next input byte */ + uInt avail_in; /* number of bytes available at next_in */ + uLong total_in; /* total nb of input bytes read so far */ + + Bytef *next_out; /* next output byte should be put there */ + uInt avail_out; /* remaining free space at next_out */ + uLong total_out; /* total nb of bytes output so far */ + + const char *msg; /* last error message, NULL if no error */ + struct internal_state FAR *state; /* not visible by applications */ + + alloc_func zalloc; /* used to allocate the internal state */ + free_func zfree; /* used to free the internal state */ + voidpf opaque; /* private data object passed to zalloc and zfree */ + + int data_type; /* best guess about the data type: binary or text */ + uLong adler; /* adler32 value of the uncompressed data */ + uLong reserved; /* reserved for future use */ +} z_stream; + +typedef z_stream FAR *z_streamp; + +/* + gzip header information passed to and from zlib routines. See RFC 1952 + for more details on the meanings of these fields. +*/ +typedef struct gz_header_s { + int text; /* true if compressed data believed to be text */ + uLong time; /* modification time */ + int xflags; /* extra flags (not used when writing a gzip file) */ + int os; /* operating system */ + Bytef *extra; /* pointer to extra field or Z_NULL if none */ + uInt extra_len; /* extra field length (valid if extra != Z_NULL) */ + uInt extra_max; /* space at extra (only when reading header) */ + Bytef *name; /* pointer to zero-terminated file name or Z_NULL */ + uInt name_max; /* space at name (only when reading header) */ + Bytef *comment; /* pointer to zero-terminated comment or Z_NULL */ + uInt comm_max; /* space at comment (only when reading header) */ + int hcrc; /* true if there was or will be a header crc */ + int done; /* true when done reading gzip header (not used + when writing a gzip file) */ +} gz_header; + +typedef gz_header FAR *gz_headerp; + +/* + The application must update next_in and avail_in when avail_in has + dropped to zero. It must update next_out and avail_out when avail_out + has dropped to zero. The application must initialize zalloc, zfree and + opaque before calling the init function. All other fields are set by the + compression library and must not be updated by the application. + + The opaque value provided by the application will be passed as the first + parameter for calls of zalloc and zfree. This can be useful for custom + memory management. The compression library attaches no meaning to the + opaque value. + + zalloc must return Z_NULL if there is not enough memory for the object. + If zlib is used in a multi-threaded application, zalloc and zfree must be + thread safe. + + On 16-bit systems, the functions zalloc and zfree must be able to allocate + exactly 65536 bytes, but will not be required to allocate more than this + if the symbol MAXSEG_64K is defined (see zconf.h). WARNING: On MSDOS, + pointers returned by zalloc for objects of exactly 65536 bytes *must* + have their offset normalized to zero. The default allocation function + provided by this library ensures this (see zutil.c). To reduce memory + requirements and avoid any allocation of 64K objects, at the expense of + compression ratio, compile the library with -DMAX_WBITS=14 (see zconf.h). + + The fields total_in and total_out can be used for statistics or + progress reports. After compression, total_in holds the total size of + the uncompressed data and may be saved for use in the decompressor + (particularly if the decompressor wants to decompress everything in + a single step). +*/ + + /* constants */ + +#define Z_NO_FLUSH 0 +#define Z_PARTIAL_FLUSH 1 /* will be removed, use Z_SYNC_FLUSH instead */ +#define Z_SYNC_FLUSH 2 +#define Z_FULL_FLUSH 3 +#define Z_FINISH 4 +#define Z_BLOCK 5 +/* Allowed flush values; see deflate() and inflate() below for details */ + +#define Z_OK 0 +#define Z_STREAM_END 1 +#define Z_NEED_DICT 2 +#define Z_ERRNO (-1) +#define Z_STREAM_ERROR (-2) +#define Z_DATA_ERROR (-3) +#define Z_MEM_ERROR (-4) +#define Z_BUF_ERROR (-5) +#define Z_VERSION_ERROR (-6) +/* Return codes for the compression/decompression functions. Negative + * values are errors, positive values are used for special but normal events. + */ + +#define Z_NO_COMPRESSION 0 +#define Z_BEST_SPEED 1 +#define Z_BEST_COMPRESSION 9 +#define Z_DEFAULT_COMPRESSION (-1) +/* compression levels */ + +#define Z_FILTERED 1 +#define Z_HUFFMAN_ONLY 2 +#define Z_RLE 3 +#define Z_FIXED 4 +#define Z_DEFAULT_STRATEGY 0 +/* compression strategy; see deflateInit2() below for details */ + +#define Z_BINARY 0 +#define Z_TEXT 1 +#define Z_ASCII Z_TEXT /* for compatibility with 1.2.2 and earlier */ +#define Z_UNKNOWN 2 +/* Possible values of the data_type field (though see inflate()) */ + +#define Z_DEFLATED 8 +/* The deflate compression method (the only one supported in this version) */ + +#define Z_NULL 0 /* for initializing zalloc, zfree, opaque */ + +#define zlib_version zlibVersion() +/* for compatibility with versions < 1.0.2 */ + + /* basic functions */ + +ZEXTERN const char * ZEXPORT zlibVersion OF((void)); +/* The application can compare zlibVersion and ZLIB_VERSION for consistency. + If the first character differs, the library code actually used is + not compatible with the zlib.h header file used by the application. + This check is automatically made by deflateInit and inflateInit. + */ + +/* +ZEXTERN int ZEXPORT deflateInit OF((z_streamp strm, int level)); + + Initializes the internal stream state for compression. The fields + zalloc, zfree and opaque must be initialized before by the caller. + If zalloc and zfree are set to Z_NULL, deflateInit updates them to + use default allocation functions. + + The compression level must be Z_DEFAULT_COMPRESSION, or between 0 and 9: + 1 gives best speed, 9 gives best compression, 0 gives no compression at + all (the input data is simply copied a block at a time). + Z_DEFAULT_COMPRESSION requests a default compromise between speed and + compression (currently equivalent to level 6). + + deflateInit returns Z_OK if success, Z_MEM_ERROR if there was not + enough memory, Z_STREAM_ERROR if level is not a valid compression level, + Z_VERSION_ERROR if the zlib library version (zlib_version) is incompatible + with the version assumed by the caller (ZLIB_VERSION). + msg is set to null if there is no error message. deflateInit does not + perform any compression: this will be done by deflate(). +*/ + + +ZEXTERN int ZEXPORT deflate OF((z_streamp strm, int flush)); +/* + deflate compresses as much data as possible, and stops when the input + buffer becomes empty or the output buffer becomes full. It may introduce some + output latency (reading input without producing any output) except when + forced to flush. + + The detailed semantics are as follows. deflate performs one or both of the + following actions: + + - Compress more input starting at next_in and update next_in and avail_in + accordingly. If not all input can be processed (because there is not + enough room in the output buffer), next_in and avail_in are updated and + processing will resume at this point for the next call of deflate(). + + - Provide more output starting at next_out and update next_out and avail_out + accordingly. This action is forced if the parameter flush is non zero. + Forcing flush frequently degrades the compression ratio, so this parameter + should be set only when necessary (in interactive applications). + Some output may be provided even if flush is not set. + + Before the call of deflate(), the application should ensure that at least + one of the actions is possible, by providing more input and/or consuming + more output, and updating avail_in or avail_out accordingly; avail_out + should never be zero before the call. The application can consume the + compressed output when it wants, for example when the output buffer is full + (avail_out == 0), or after each call of deflate(). If deflate returns Z_OK + and with zero avail_out, it must be called again after making room in the + output buffer because there might be more output pending. + + Normally the parameter flush is set to Z_NO_FLUSH, which allows deflate to + decide how much data to accumualte before producing output, in order to + maximize compression. + + If the parameter flush is set to Z_SYNC_FLUSH, all pending output is + flushed to the output buffer and the output is aligned on a byte boundary, so + that the decompressor can get all input data available so far. (In particular + avail_in is zero after the call if enough output space has been provided + before the call.) Flushing may degrade compression for some compression + algorithms and so it should be used only when necessary. + + If flush is set to Z_FULL_FLUSH, all output is flushed as with + Z_SYNC_FLUSH, and the compression state is reset so that decompression can + restart from this point if previous compressed data has been damaged or if + random access is desired. Using Z_FULL_FLUSH too often can seriously degrade + compression. + + If deflate returns with avail_out == 0, this function must be called again + with the same value of the flush parameter and more output space (updated + avail_out), until the flush is complete (deflate returns with non-zero + avail_out). In the case of a Z_FULL_FLUSH or Z_SYNC_FLUSH, make sure that + avail_out is greater than six to avoid repeated flush markers due to + avail_out == 0 on return. + + If the parameter flush is set to Z_FINISH, pending input is processed, + pending output is flushed and deflate returns with Z_STREAM_END if there + was enough output space; if deflate returns with Z_OK, this function must be + called again with Z_FINISH and more output space (updated avail_out) but no + more input data, until it returns with Z_STREAM_END or an error. After + deflate has returned Z_STREAM_END, the only possible operations on the + stream are deflateReset or deflateEnd. + + Z_FINISH can be used immediately after deflateInit if all the compression + is to be done in a single step. In this case, avail_out must be at least + the value returned by deflateBound (see below). If deflate does not return + Z_STREAM_END, then it must be called again as described above. + + deflate() sets strm->adler to the adler32 checksum of all input read + so far (that is, total_in bytes). + + deflate() may update strm->data_type if it can make a good guess about + the input data type (Z_BINARY or Z_TEXT). In doubt, the data is considered + binary. This field is only for information purposes and does not affect + the compression algorithm in any manner. + + deflate() returns Z_OK if some progress has been made (more input + processed or more output produced), Z_STREAM_END if all input has been + consumed and all output has been produced (only when flush is set to + Z_FINISH), Z_STREAM_ERROR if the stream state was inconsistent (for example + if next_in or next_out was NULL), Z_BUF_ERROR if no progress is possible + (for example avail_in or avail_out was zero). Note that Z_BUF_ERROR is not + fatal, and deflate() can be called again with more input and more output + space to continue compressing. +*/ + + +ZEXTERN int ZEXPORT deflateEnd OF((z_streamp strm)); +/* + All dynamically allocated data structures for this stream are freed. + This function discards any unprocessed input and does not flush any + pending output. + + deflateEnd returns Z_OK if success, Z_STREAM_ERROR if the + stream state was inconsistent, Z_DATA_ERROR if the stream was freed + prematurely (some input or output was discarded). In the error case, + msg may be set but then points to a static string (which must not be + deallocated). +*/ + + +/* +ZEXTERN int ZEXPORT inflateInit OF((z_streamp strm)); + + Initializes the internal stream state for decompression. The fields + next_in, avail_in, zalloc, zfree and opaque must be initialized before by + the caller. If next_in is not Z_NULL and avail_in is large enough (the exact + value depends on the compression method), inflateInit determines the + compression method from the zlib header and allocates all data structures + accordingly; otherwise the allocation will be deferred to the first call of + inflate. If zalloc and zfree are set to Z_NULL, inflateInit updates them to + use default allocation functions. + + inflateInit returns Z_OK if success, Z_MEM_ERROR if there was not enough + memory, Z_VERSION_ERROR if the zlib library version is incompatible with the + version assumed by the caller. msg is set to null if there is no error + message. inflateInit does not perform any decompression apart from reading + the zlib header if present: this will be done by inflate(). (So next_in and + avail_in may be modified, but next_out and avail_out are unchanged.) +*/ + + +ZEXTERN int ZEXPORT inflate OF((z_streamp strm, int flush)); +/* + inflate decompresses as much data as possible, and stops when the input + buffer becomes empty or the output buffer becomes full. It may introduce + some output latency (reading input without producing any output) except when + forced to flush. + + The detailed semantics are as follows. inflate performs one or both of the + following actions: + + - Decompress more input starting at next_in and update next_in and avail_in + accordingly. If not all input can be processed (because there is not + enough room in the output buffer), next_in is updated and processing + will resume at this point for the next call of inflate(). + + - Provide more output starting at next_out and update next_out and avail_out + accordingly. inflate() provides as much output as possible, until there + is no more input data or no more space in the output buffer (see below + about the flush parameter). + + Before the call of inflate(), the application should ensure that at least + one of the actions is possible, by providing more input and/or consuming + more output, and updating the next_* and avail_* values accordingly. + The application can consume the uncompressed output when it wants, for + example when the output buffer is full (avail_out == 0), or after each + call of inflate(). If inflate returns Z_OK and with zero avail_out, it + must be called again after making room in the output buffer because there + might be more output pending. + + The flush parameter of inflate() can be Z_NO_FLUSH, Z_SYNC_FLUSH, + Z_FINISH, or Z_BLOCK. Z_SYNC_FLUSH requests that inflate() flush as much + output as possible to the output buffer. Z_BLOCK requests that inflate() stop + if and when it gets to the next deflate block boundary. When decoding the + zlib or gzip format, this will cause inflate() to return immediately after + the header and before the first block. When doing a raw inflate, inflate() + will go ahead and process the first block, and will return when it gets to + the end of that block, or when it runs out of data. + + The Z_BLOCK option assists in appending to or combining deflate streams. + Also to assist in this, on return inflate() will set strm->data_type to the + number of unused bits in the last byte taken from strm->next_in, plus 64 + if inflate() is currently decoding the last block in the deflate stream, + plus 128 if inflate() returned immediately after decoding an end-of-block + code or decoding the complete header up to just before the first byte of the + deflate stream. The end-of-block will not be indicated until all of the + uncompressed data from that block has been written to strm->next_out. The + number of unused bits may in general be greater than seven, except when + bit 7 of data_type is set, in which case the number of unused bits will be + less than eight. + + inflate() should normally be called until it returns Z_STREAM_END or an + error. However if all decompression is to be performed in a single step + (a single call of inflate), the parameter flush should be set to + Z_FINISH. In this case all pending input is processed and all pending + output is flushed; avail_out must be large enough to hold all the + uncompressed data. (The size of the uncompressed data may have been saved + by the compressor for this purpose.) The next operation on this stream must + be inflateEnd to deallocate the decompression state. The use of Z_FINISH + is never required, but can be used to inform inflate that a faster approach + may be used for the single inflate() call. + + In this implementation, inflate() always flushes as much output as + possible to the output buffer, and always uses the faster approach on the + first call. So the only effect of the flush parameter in this implementation + is on the return value of inflate(), as noted below, or when it returns early + because Z_BLOCK is used. + + If a preset dictionary is needed after this call (see inflateSetDictionary + below), inflate sets strm->adler to the adler32 checksum of the dictionary + chosen by the compressor and returns Z_NEED_DICT; otherwise it sets + strm->adler to the adler32 checksum of all output produced so far (that is, + total_out bytes) and returns Z_OK, Z_STREAM_END or an error code as described + below. At the end of the stream, inflate() checks that its computed adler32 + checksum is equal to that saved by the compressor and returns Z_STREAM_END + only if the checksum is correct. + + inflate() will decompress and check either zlib-wrapped or gzip-wrapped + deflate data. The header type is detected automatically. Any information + contained in the gzip header is not retained, so applications that need that + information should instead use raw inflate, see inflateInit2() below, or + inflateBack() and perform their own processing of the gzip header and + trailer. + + inflate() returns Z_OK if some progress has been made (more input processed + or more output produced), Z_STREAM_END if the end of the compressed data has + been reached and all uncompressed output has been produced, Z_NEED_DICT if a + preset dictionary is needed at this point, Z_DATA_ERROR if the input data was + corrupted (input stream not conforming to the zlib format or incorrect check + value), Z_STREAM_ERROR if the stream structure was inconsistent (for example + if next_in or next_out was NULL), Z_MEM_ERROR if there was not enough memory, + Z_BUF_ERROR if no progress is possible or if there was not enough room in the + output buffer when Z_FINISH is used. Note that Z_BUF_ERROR is not fatal, and + inflate() can be called again with more input and more output space to + continue decompressing. If Z_DATA_ERROR is returned, the application may then + call inflateSync() to look for a good compression block if a partial recovery + of the data is desired. +*/ + + +ZEXTERN int ZEXPORT inflateEnd OF((z_streamp strm)); +/* + All dynamically allocated data structures for this stream are freed. + This function discards any unprocessed input and does not flush any + pending output. + + inflateEnd returns Z_OK if success, Z_STREAM_ERROR if the stream state + was inconsistent. In the error case, msg may be set but then points to a + static string (which must not be deallocated). +*/ + + /* Advanced functions */ + +/* + The following functions are needed only in some special applications. +*/ + +/* +ZEXTERN int ZEXPORT deflateInit2 OF((z_streamp strm, + int level, + int method, + int windowBits, + int memLevel, + int strategy)); + + This is another version of deflateInit with more compression options. The + fields next_in, zalloc, zfree and opaque must be initialized before by + the caller. + + The method parameter is the compression method. It must be Z_DEFLATED in + this version of the library. + + The windowBits parameter is the base two logarithm of the window size + (the size of the history buffer). It should be in the range 8..15 for this + version of the library. Larger values of this parameter result in better + compression at the expense of memory usage. The default value is 15 if + deflateInit is used instead. + + windowBits can also be -8..-15 for raw deflate. In this case, -windowBits + determines the window size. deflate() will then generate raw deflate data + with no zlib header or trailer, and will not compute an adler32 check value. + + windowBits can also be greater than 15 for optional gzip encoding. Add + 16 to windowBits to write a simple gzip header and trailer around the + compressed data instead of a zlib wrapper. The gzip header will have no + file name, no extra data, no comment, no modification time (set to zero), + no header crc, and the operating system will be set to 255 (unknown). If a + gzip stream is being written, strm->adler is a crc32 instead of an adler32. + + The memLevel parameter specifies how much memory should be allocated + for the internal compression state. memLevel=1 uses minimum memory but + is slow and reduces compression ratio; memLevel=9 uses maximum memory + for optimal speed. The default value is 8. See zconf.h for total memory + usage as a function of windowBits and memLevel. + + The strategy parameter is used to tune the compression algorithm. Use the + value Z_DEFAULT_STRATEGY for normal data, Z_FILTERED for data produced by a + filter (or predictor), Z_HUFFMAN_ONLY to force Huffman encoding only (no + string match), or Z_RLE to limit match distances to one (run-length + encoding). Filtered data consists mostly of small values with a somewhat + random distribution. In this case, the compression algorithm is tuned to + compress them better. The effect of Z_FILTERED is to force more Huffman + coding and less string matching; it is somewhat intermediate between + Z_DEFAULT and Z_HUFFMAN_ONLY. Z_RLE is designed to be almost as fast as + Z_HUFFMAN_ONLY, but give better compression for PNG image data. The strategy + parameter only affects the compression ratio but not the correctness of the + compressed output even if it is not set appropriately. Z_FIXED prevents the + use of dynamic Huffman codes, allowing for a simpler decoder for special + applications. + + deflateInit2 returns Z_OK if success, Z_MEM_ERROR if there was not enough + memory, Z_STREAM_ERROR if a parameter is invalid (such as an invalid + method). msg is set to null if there is no error message. deflateInit2 does + not perform any compression: this will be done by deflate(). +*/ + +ZEXTERN int ZEXPORT deflateSetDictionary OF((z_streamp strm, + const Bytef *dictionary, + uInt dictLength)); +/* + Initializes the compression dictionary from the given byte sequence + without producing any compressed output. This function must be called + immediately after deflateInit, deflateInit2 or deflateReset, before any + call of deflate. The compressor and decompressor must use exactly the same + dictionary (see inflateSetDictionary). + + The dictionary should consist of strings (byte sequences) that are likely + to be encountered later in the data to be compressed, with the most commonly + used strings preferably put towards the end of the dictionary. Using a + dictionary is most useful when the data to be compressed is short and can be + predicted with good accuracy; the data can then be compressed better than + with the default empty dictionary. + + Depending on the size of the compression data structures selected by + deflateInit or deflateInit2, a part of the dictionary may in effect be + discarded, for example if the dictionary is larger than the window size in + deflate or deflate2. Thus the strings most likely to be useful should be + put at the end of the dictionary, not at the front. In addition, the + current implementation of deflate will use at most the window size minus + 262 bytes of the provided dictionary. + + Upon return of this function, strm->adler is set to the adler32 value + of the dictionary; the decompressor may later use this value to determine + which dictionary has been used by the compressor. (The adler32 value + applies to the whole dictionary even if only a subset of the dictionary is + actually used by the compressor.) If a raw deflate was requested, then the + adler32 value is not computed and strm->adler is not set. + + deflateSetDictionary returns Z_OK if success, or Z_STREAM_ERROR if a + parameter is invalid (such as NULL dictionary) or the stream state is + inconsistent (for example if deflate has already been called for this stream + or if the compression method is bsort). deflateSetDictionary does not + perform any compression: this will be done by deflate(). +*/ + +ZEXTERN int ZEXPORT deflateCopy OF((z_streamp dest, + z_streamp source)); +/* + Sets the destination stream as a complete copy of the source stream. + + This function can be useful when several compression strategies will be + tried, for example when there are several ways of pre-processing the input + data with a filter. The streams that will be discarded should then be freed + by calling deflateEnd. Note that deflateCopy duplicates the internal + compression state which can be quite large, so this strategy is slow and + can consume lots of memory. + + deflateCopy returns Z_OK if success, Z_MEM_ERROR if there was not + enough memory, Z_STREAM_ERROR if the source stream state was inconsistent + (such as zalloc being NULL). msg is left unchanged in both source and + destination. +*/ + +ZEXTERN int ZEXPORT deflateReset OF((z_streamp strm)); +/* + This function is equivalent to deflateEnd followed by deflateInit, + but does not free and reallocate all the internal compression state. + The stream will keep the same compression level and any other attributes + that may have been set by deflateInit2. + + deflateReset returns Z_OK if success, or Z_STREAM_ERROR if the source + stream state was inconsistent (such as zalloc or state being NULL). +*/ + +ZEXTERN int ZEXPORT deflateParams OF((z_streamp strm, + int level, + int strategy)); +/* + Dynamically update the compression level and compression strategy. The + interpretation of level and strategy is as in deflateInit2. This can be + used to switch between compression and straight copy of the input data, or + to switch to a different kind of input data requiring a different + strategy. If the compression level is changed, the input available so far + is compressed with the old level (and may be flushed); the new level will + take effect only at the next call of deflate(). + + Before the call of deflateParams, the stream state must be set as for + a call of deflate(), since the currently available input may have to + be compressed and flushed. In particular, strm->avail_out must be non-zero. + + deflateParams returns Z_OK if success, Z_STREAM_ERROR if the source + stream state was inconsistent or if a parameter was invalid, Z_BUF_ERROR + if strm->avail_out was zero. +*/ + +ZEXTERN int ZEXPORT deflateTune OF((z_streamp strm, + int good_length, + int max_lazy, + int nice_length, + int max_chain)); +/* + Fine tune deflate's internal compression parameters. This should only be + used by someone who understands the algorithm used by zlib's deflate for + searching for the best matching string, and even then only by the most + fanatic optimizer trying to squeeze out the last compressed bit for their + specific input data. Read the deflate.c source code for the meaning of the + max_lazy, good_length, nice_length, and max_chain parameters. + + deflateTune() can be called after deflateInit() or deflateInit2(), and + returns Z_OK on success, or Z_STREAM_ERROR for an invalid deflate stream. + */ + +ZEXTERN uLong ZEXPORT deflateBound OF((z_streamp strm, + uLong sourceLen)); +/* + deflateBound() returns an upper bound on the compressed size after + deflation of sourceLen bytes. It must be called after deflateInit() + or deflateInit2(). This would be used to allocate an output buffer + for deflation in a single pass, and so would be called before deflate(). +*/ + +ZEXTERN int ZEXPORT deflatePrime OF((z_streamp strm, + int bits, + int value)); +/* + deflatePrime() inserts bits in the deflate output stream. The intent + is that this function is used to start off the deflate output with the + bits leftover from a previous deflate stream when appending to it. As such, + this function can only be used for raw deflate, and must be used before the + first deflate() call after a deflateInit2() or deflateReset(). bits must be + less than or equal to 16, and that many of the least significant bits of + value will be inserted in the output. + + deflatePrime returns Z_OK if success, or Z_STREAM_ERROR if the source + stream state was inconsistent. +*/ + +ZEXTERN int ZEXPORT deflateSetHeader OF((z_streamp strm, + gz_headerp head)); +/* + deflateSetHeader() provides gzip header information for when a gzip + stream is requested by deflateInit2(). deflateSetHeader() may be called + after deflateInit2() or deflateReset() and before the first call of + deflate(). The text, time, os, extra field, name, and comment information + in the provided gz_header structure are written to the gzip header (xflag is + ignored -- the extra flags are set according to the compression level). The + caller must assure that, if not Z_NULL, name and comment are terminated with + a zero byte, and that if extra is not Z_NULL, that extra_len bytes are + available there. If hcrc is true, a gzip header crc is included. Note that + the current versions of the command-line version of gzip (up through version + 1.3.x) do not support header crc's, and will report that it is a "multi-part + gzip file" and give up. + + If deflateSetHeader is not used, the default gzip header has text false, + the time set to zero, and os set to 255, with no extra, name, or comment + fields. The gzip header is returned to the default state by deflateReset(). + + deflateSetHeader returns Z_OK if success, or Z_STREAM_ERROR if the source + stream state was inconsistent. +*/ + +/* +ZEXTERN int ZEXPORT inflateInit2 OF((z_streamp strm, + int windowBits)); + + This is another version of inflateInit with an extra parameter. The + fields next_in, avail_in, zalloc, zfree and opaque must be initialized + before by the caller. + + The windowBits parameter is the base two logarithm of the maximum window + size (the size of the history buffer). It should be in the range 8..15 for + this version of the library. The default value is 15 if inflateInit is used + instead. windowBits must be greater than or equal to the windowBits value + provided to deflateInit2() while compressing, or it must be equal to 15 if + deflateInit2() was not used. If a compressed stream with a larger window + size is given as input, inflate() will return with the error code + Z_DATA_ERROR instead of trying to allocate a larger window. + + windowBits can also be -8..-15 for raw inflate. In this case, -windowBits + determines the window size. inflate() will then process raw deflate data, + not looking for a zlib or gzip header, not generating a check value, and not + looking for any check values for comparison at the end of the stream. This + is for use with other formats that use the deflate compressed data format + such as zip. Those formats provide their own check values. If a custom + format is developed using the raw deflate format for compressed data, it is + recommended that a check value such as an adler32 or a crc32 be applied to + the uncompressed data as is done in the zlib, gzip, and zip formats. For + most applications, the zlib format should be used as is. Note that comments + above on the use in deflateInit2() applies to the magnitude of windowBits. + + windowBits can also be greater than 15 for optional gzip decoding. Add + 32 to windowBits to enable zlib and gzip decoding with automatic header + detection, or add 16 to decode only the gzip format (the zlib format will + return a Z_DATA_ERROR). If a gzip stream is being decoded, strm->adler is + a crc32 instead of an adler32. + + inflateInit2 returns Z_OK if success, Z_MEM_ERROR if there was not enough + memory, Z_STREAM_ERROR if a parameter is invalid (such as a null strm). msg + is set to null if there is no error message. inflateInit2 does not perform + any decompression apart from reading the zlib header if present: this will + be done by inflate(). (So next_in and avail_in may be modified, but next_out + and avail_out are unchanged.) +*/ + +ZEXTERN int ZEXPORT inflateSetDictionary OF((z_streamp strm, + const Bytef *dictionary, + uInt dictLength)); +/* + Initializes the decompression dictionary from the given uncompressed byte + sequence. This function must be called immediately after a call of inflate, + if that call returned Z_NEED_DICT. The dictionary chosen by the compressor + can be determined from the adler32 value returned by that call of inflate. + The compressor and decompressor must use exactly the same dictionary (see + deflateSetDictionary). For raw inflate, this function can be called + immediately after inflateInit2() or inflateReset() and before any call of + inflate() to set the dictionary. The application must insure that the + dictionary that was used for compression is provided. + + inflateSetDictionary returns Z_OK if success, Z_STREAM_ERROR if a + parameter is invalid (such as NULL dictionary) or the stream state is + inconsistent, Z_DATA_ERROR if the given dictionary doesn't match the + expected one (incorrect adler32 value). inflateSetDictionary does not + perform any decompression: this will be done by subsequent calls of + inflate(). +*/ + +ZEXTERN int ZEXPORT inflateSync OF((z_streamp strm)); +/* + Skips invalid compressed data until a full flush point (see above the + description of deflate with Z_FULL_FLUSH) can be found, or until all + available input is skipped. No output is provided. + + inflateSync returns Z_OK if a full flush point has been found, Z_BUF_ERROR + if no more input was provided, Z_DATA_ERROR if no flush point has been found, + or Z_STREAM_ERROR if the stream structure was inconsistent. In the success + case, the application may save the current current value of total_in which + indicates where valid compressed data was found. In the error case, the + application may repeatedly call inflateSync, providing more input each time, + until success or end of the input data. +*/ + +ZEXTERN int ZEXPORT inflateCopy OF((z_streamp dest, + z_streamp source)); +/* + Sets the destination stream as a complete copy of the source stream. + + This function can be useful when randomly accessing a large stream. The + first pass through the stream can periodically record the inflate state, + allowing restarting inflate at those points when randomly accessing the + stream. + + inflateCopy returns Z_OK if success, Z_MEM_ERROR if there was not + enough memory, Z_STREAM_ERROR if the source stream state was inconsistent + (such as zalloc being NULL). msg is left unchanged in both source and + destination. +*/ + +ZEXTERN int ZEXPORT inflateReset OF((z_streamp strm)); +/* + This function is equivalent to inflateEnd followed by inflateInit, + but does not free and reallocate all the internal decompression state. + The stream will keep attributes that may have been set by inflateInit2. + + inflateReset returns Z_OK if success, or Z_STREAM_ERROR if the source + stream state was inconsistent (such as zalloc or state being NULL). +*/ + +ZEXTERN int ZEXPORT inflatePrime OF((z_streamp strm, + int bits, + int value)); +/* + This function inserts bits in the inflate input stream. The intent is + that this function is used to start inflating at a bit position in the + middle of a byte. The provided bits will be used before any bytes are used + from next_in. This function should only be used with raw inflate, and + should be used before the first inflate() call after inflateInit2() or + inflateReset(). bits must be less than or equal to 16, and that many of the + least significant bits of value will be inserted in the input. + + inflatePrime returns Z_OK if success, or Z_STREAM_ERROR if the source + stream state was inconsistent. +*/ + +ZEXTERN int ZEXPORT inflateGetHeader OF((z_streamp strm, + gz_headerp head)); +/* + inflateGetHeader() requests that gzip header information be stored in the + provided gz_header structure. inflateGetHeader() may be called after + inflateInit2() or inflateReset(), and before the first call of inflate(). + As inflate() processes the gzip stream, head->done is zero until the header + is completed, at which time head->done is set to one. If a zlib stream is + being decoded, then head->done is set to -1 to indicate that there will be + no gzip header information forthcoming. Note that Z_BLOCK can be used to + force inflate() to return immediately after header processing is complete + and before any actual data is decompressed. + + The text, time, xflags, and os fields are filled in with the gzip header + contents. hcrc is set to true if there is a header CRC. (The header CRC + was valid if done is set to one.) If extra is not Z_NULL, then extra_max + contains the maximum number of bytes to write to extra. Once done is true, + extra_len contains the actual extra field length, and extra contains the + extra field, or that field truncated if extra_max is less than extra_len. + If name is not Z_NULL, then up to name_max characters are written there, + terminated with a zero unless the length is greater than name_max. If + comment is not Z_NULL, then up to comm_max characters are written there, + terminated with a zero unless the length is greater than comm_max. When + any of extra, name, or comment are not Z_NULL and the respective field is + not present in the header, then that field is set to Z_NULL to signal its + absence. This allows the use of deflateSetHeader() with the returned + structure to duplicate the header. However if those fields are set to + allocated memory, then the application will need to save those pointers + elsewhere so that they can be eventually freed. + + If inflateGetHeader is not used, then the header information is simply + discarded. The header is always checked for validity, including the header + CRC if present. inflateReset() will reset the process to discard the header + information. The application would need to call inflateGetHeader() again to + retrieve the header from the next gzip stream. + + inflateGetHeader returns Z_OK if success, or Z_STREAM_ERROR if the source + stream state was inconsistent. +*/ + +/* +ZEXTERN int ZEXPORT inflateBackInit OF((z_streamp strm, int windowBits, + unsigned char FAR *window)); + + Initialize the internal stream state for decompression using inflateBack() + calls. The fields zalloc, zfree and opaque in strm must be initialized + before the call. If zalloc and zfree are Z_NULL, then the default library- + derived memory allocation routines are used. windowBits is the base two + logarithm of the window size, in the range 8..15. window is a caller + supplied buffer of that size. Except for special applications where it is + assured that deflate was used with small window sizes, windowBits must be 15 + and a 32K byte window must be supplied to be able to decompress general + deflate streams. + + See inflateBack() for the usage of these routines. + + inflateBackInit will return Z_OK on success, Z_STREAM_ERROR if any of + the paramaters are invalid, Z_MEM_ERROR if the internal state could not + be allocated, or Z_VERSION_ERROR if the version of the library does not + match the version of the header file. +*/ + +typedef unsigned (*in_func) OF((void FAR *, unsigned char FAR * FAR *)); +typedef int (*out_func) OF((void FAR *, unsigned char FAR *, unsigned)); + +ZEXTERN int ZEXPORT inflateBack OF((z_streamp strm, + in_func in, void FAR *in_desc, + out_func out, void FAR *out_desc)); +/* + inflateBack() does a raw inflate with a single call using a call-back + interface for input and output. This is more efficient than inflate() for + file i/o applications in that it avoids copying between the output and the + sliding window by simply making the window itself the output buffer. This + function trusts the application to not change the output buffer passed by + the output function, at least until inflateBack() returns. + + inflateBackInit() must be called first to allocate the internal state + and to initialize the state with the user-provided window buffer. + inflateBack() may then be used multiple times to inflate a complete, raw + deflate stream with each call. inflateBackEnd() is then called to free + the allocated state. + + A raw deflate stream is one with no zlib or gzip header or trailer. + This routine would normally be used in a utility that reads zip or gzip + files and writes out uncompressed files. The utility would decode the + header and process the trailer on its own, hence this routine expects + only the raw deflate stream to decompress. This is different from the + normal behavior of inflate(), which expects either a zlib or gzip header and + trailer around the deflate stream. + + inflateBack() uses two subroutines supplied by the caller that are then + called by inflateBack() for input and output. inflateBack() calls those + routines until it reads a complete deflate stream and writes out all of the + uncompressed data, or until it encounters an error. The function's + parameters and return types are defined above in the in_func and out_func + typedefs. inflateBack() will call in(in_desc, &buf) which should return the + number of bytes of provided input, and a pointer to that input in buf. If + there is no input available, in() must return zero--buf is ignored in that + case--and inflateBack() will return a buffer error. inflateBack() will call + out(out_desc, buf, len) to write the uncompressed data buf[0..len-1]. out() + should return zero on success, or non-zero on failure. If out() returns + non-zero, inflateBack() will return with an error. Neither in() nor out() + are permitted to change the contents of the window provided to + inflateBackInit(), which is also the buffer that out() uses to write from. + The length written by out() will be at most the window size. Any non-zero + amount of input may be provided by in(). + + For convenience, inflateBack() can be provided input on the first call by + setting strm->next_in and strm->avail_in. If that input is exhausted, then + in() will be called. Therefore strm->next_in must be initialized before + calling inflateBack(). If strm->next_in is Z_NULL, then in() will be called + immediately for input. If strm->next_in is not Z_NULL, then strm->avail_in + must also be initialized, and then if strm->avail_in is not zero, input will + initially be taken from strm->next_in[0 .. strm->avail_in - 1]. + + The in_desc and out_desc parameters of inflateBack() is passed as the + first parameter of in() and out() respectively when they are called. These + descriptors can be optionally used to pass any information that the caller- + supplied in() and out() functions need to do their job. + + On return, inflateBack() will set strm->next_in and strm->avail_in to + pass back any unused input that was provided by the last in() call. The + return values of inflateBack() can be Z_STREAM_END on success, Z_BUF_ERROR + if in() or out() returned an error, Z_DATA_ERROR if there was a format + error in the deflate stream (in which case strm->msg is set to indicate the + nature of the error), or Z_STREAM_ERROR if the stream was not properly + initialized. In the case of Z_BUF_ERROR, an input or output error can be + distinguished using strm->next_in which will be Z_NULL only if in() returned + an error. If strm->next is not Z_NULL, then the Z_BUF_ERROR was due to + out() returning non-zero. (in() will always be called before out(), so + strm->next_in is assured to be defined if out() returns non-zero.) Note + that inflateBack() cannot return Z_OK. +*/ + +ZEXTERN int ZEXPORT inflateBackEnd OF((z_streamp strm)); +/* + All memory allocated by inflateBackInit() is freed. + + inflateBackEnd() returns Z_OK on success, or Z_STREAM_ERROR if the stream + state was inconsistent. +*/ + +ZEXTERN uLong ZEXPORT zlibCompileFlags OF((void)); +/* Return flags indicating compile-time options. + + Type sizes, two bits each, 00 = 16 bits, 01 = 32, 10 = 64, 11 = other: + 1.0: size of uInt + 3.2: size of uLong + 5.4: size of voidpf (pointer) + 7.6: size of z_off_t + + Compiler, assembler, and debug options: + 8: DEBUG + 9: ASMV or ASMINF -- use ASM code + 10: ZLIB_WINAPI -- exported functions use the WINAPI calling convention + 11: 0 (reserved) + + One-time table building (smaller code, but not thread-safe if true): + 12: BUILDFIXED -- build static block decoding tables when needed + 13: DYNAMIC_CRC_TABLE -- build CRC calculation tables when needed + 14,15: 0 (reserved) + + Library content (indicates missing functionality): + 16: NO_GZCOMPRESS -- gz* functions cannot compress (to avoid linking + deflate code when not needed) + 17: NO_GZIP -- deflate can't write gzip streams, and inflate can't detect + and decode gzip streams (to avoid linking crc code) + 18-19: 0 (reserved) + + Operation variations (changes in library functionality): + 20: PKZIP_BUG_WORKAROUND -- slightly more permissive inflate + 21: FASTEST -- deflate algorithm with only one, lowest compression level + 22,23: 0 (reserved) + + The sprintf variant used by gzprintf (zero is best): + 24: 0 = vs*, 1 = s* -- 1 means limited to 20 arguments after the format + 25: 0 = *nprintf, 1 = *printf -- 1 means gzprintf() not secure! + 26: 0 = returns value, 1 = void -- 1 means inferred string length returned + + Remainder: + 27-31: 0 (reserved) + */ + + + /* utility functions */ + +/* + The following utility functions are implemented on top of the + basic stream-oriented functions. To simplify the interface, some + default options are assumed (compression level and memory usage, + standard memory allocation functions). The source code of these + utility functions can easily be modified if you need special options. +*/ + +ZEXTERN int ZEXPORT compress OF((Bytef *dest, uLongf *destLen, + const Bytef *source, uLong sourceLen)); +/* + Compresses the source buffer into the destination buffer. sourceLen is + the byte length of the source buffer. Upon entry, destLen is the total + size of the destination buffer, which must be at least the value returned + by compressBound(sourceLen). Upon exit, destLen is the actual size of the + compressed buffer. + This function can be used to compress a whole file at once if the + input file is mmap'ed. + compress returns Z_OK if success, Z_MEM_ERROR if there was not + enough memory, Z_BUF_ERROR if there was not enough room in the output + buffer. +*/ + +ZEXTERN int ZEXPORT compress2 OF((Bytef *dest, uLongf *destLen, + const Bytef *source, uLong sourceLen, + int level)); +/* + Compresses the source buffer into the destination buffer. The level + parameter has the same meaning as in deflateInit. sourceLen is the byte + length of the source buffer. Upon entry, destLen is the total size of the + destination buffer, which must be at least the value returned by + compressBound(sourceLen). Upon exit, destLen is the actual size of the + compressed buffer. + + compress2 returns Z_OK if success, Z_MEM_ERROR if there was not enough + memory, Z_BUF_ERROR if there was not enough room in the output buffer, + Z_STREAM_ERROR if the level parameter is invalid. +*/ + +ZEXTERN uLong ZEXPORT compressBound OF((uLong sourceLen)); +/* + compressBound() returns an upper bound on the compressed size after + compress() or compress2() on sourceLen bytes. It would be used before + a compress() or compress2() call to allocate the destination buffer. +*/ + +ZEXTERN int ZEXPORT uncompress OF((Bytef *dest, uLongf *destLen, + const Bytef *source, uLong sourceLen)); +/* + Decompresses the source buffer into the destination buffer. sourceLen is + the byte length of the source buffer. Upon entry, destLen is the total + size of the destination buffer, which must be large enough to hold the + entire uncompressed data. (The size of the uncompressed data must have + been saved previously by the compressor and transmitted to the decompressor + by some mechanism outside the scope of this compression library.) + Upon exit, destLen is the actual size of the compressed buffer. + This function can be used to decompress a whole file at once if the + input file is mmap'ed. + + uncompress returns Z_OK if success, Z_MEM_ERROR if there was not + enough memory, Z_BUF_ERROR if there was not enough room in the output + buffer, or Z_DATA_ERROR if the input data was corrupted or incomplete. +*/ + + +typedef voidp gzFile; + +ZEXTERN gzFile ZEXPORT gzopen OF((const char *path, const char *mode)); +/* + Opens a gzip (.gz) file for reading or writing. The mode parameter + is as in fopen ("rb" or "wb") but can also include a compression level + ("wb9") or a strategy: 'f' for filtered data as in "wb6f", 'h' for + Huffman only compression as in "wb1h", or 'R' for run-length encoding + as in "wb1R". (See the description of deflateInit2 for more information + about the strategy parameter.) + + gzopen can be used to read a file which is not in gzip format; in this + case gzread will directly read from the file without decompression. + + gzopen returns NULL if the file could not be opened or if there was + insufficient memory to allocate the (de)compression state; errno + can be checked to distinguish the two cases (if errno is zero, the + zlib error is Z_MEM_ERROR). */ + +ZEXTERN gzFile ZEXPORT gzdopen OF((int fd, const char *mode)); +/* + gzdopen() associates a gzFile with the file descriptor fd. File + descriptors are obtained from calls like open, dup, creat, pipe or + fileno (in the file has been previously opened with fopen). + The mode parameter is as in gzopen. + The next call of gzclose on the returned gzFile will also close the + file descriptor fd, just like fclose(fdopen(fd), mode) closes the file + descriptor fd. If you want to keep fd open, use gzdopen(dup(fd), mode). + gzdopen returns NULL if there was insufficient memory to allocate + the (de)compression state. +*/ + +ZEXTERN int ZEXPORT gzsetparams OF((gzFile file, int level, int strategy)); +/* + Dynamically update the compression level or strategy. See the description + of deflateInit2 for the meaning of these parameters. + gzsetparams returns Z_OK if success, or Z_STREAM_ERROR if the file was not + opened for writing. +*/ + +ZEXTERN int ZEXPORT gzread OF((gzFile file, voidp buf, unsigned len)); +/* + Reads the given number of uncompressed bytes from the compressed file. + If the input file was not in gzip format, gzread copies the given number + of bytes into the buffer. + gzread returns the number of uncompressed bytes actually read (0 for + end of file, -1 for error). */ + +ZEXTERN int ZEXPORT gzwrite OF((gzFile file, + voidpc buf, unsigned len)); +/* + Writes the given number of uncompressed bytes into the compressed file. + gzwrite returns the number of uncompressed bytes actually written + (0 in case of error). +*/ + +ZEXTERN int ZEXPORTVA gzprintf OF((gzFile file, const char *format, ...)); +/* + Converts, formats, and writes the args to the compressed file under + control of the format string, as in fprintf. gzprintf returns the number of + uncompressed bytes actually written (0 in case of error). The number of + uncompressed bytes written is limited to 4095. The caller should assure that + this limit is not exceeded. If it is exceeded, then gzprintf() will return + return an error (0) with nothing written. In this case, there may also be a + buffer overflow with unpredictable consequences, which is possible only if + zlib was compiled with the insecure functions sprintf() or vsprintf() + because the secure snprintf() or vsnprintf() functions were not available. +*/ + +ZEXTERN int ZEXPORT gzputs OF((gzFile file, const char *s)); +/* + Writes the given null-terminated string to the compressed file, excluding + the terminating null character. + gzputs returns the number of characters written, or -1 in case of error. +*/ + +ZEXTERN char * ZEXPORT gzgets OF((gzFile file, char *buf, int len)); +/* + Reads bytes from the compressed file until len-1 characters are read, or + a newline character is read and transferred to buf, or an end-of-file + condition is encountered. The string is then terminated with a null + character. + gzgets returns buf, or Z_NULL in case of error. +*/ + +ZEXTERN int ZEXPORT gzputc OF((gzFile file, int c)); +/* + Writes c, converted to an unsigned char, into the compressed file. + gzputc returns the value that was written, or -1 in case of error. +*/ + +ZEXTERN int ZEXPORT gzgetc OF((gzFile file)); +/* + Reads one byte from the compressed file. gzgetc returns this byte + or -1 in case of end of file or error. +*/ + +ZEXTERN int ZEXPORT gzungetc OF((int c, gzFile file)); +/* + Push one character back onto the stream to be read again later. + Only one character of push-back is allowed. gzungetc() returns the + character pushed, or -1 on failure. gzungetc() will fail if a + character has been pushed but not read yet, or if c is -1. The pushed + character will be discarded if the stream is repositioned with gzseek() + or gzrewind(). +*/ + +ZEXTERN int ZEXPORT gzflush OF((gzFile file, int flush)); +/* + Flushes all pending output into the compressed file. The parameter + flush is as in the deflate() function. The return value is the zlib + error number (see function gzerror below). gzflush returns Z_OK if + the flush parameter is Z_FINISH and all output could be flushed. + gzflush should be called only when strictly necessary because it can + degrade compression. +*/ + +ZEXTERN z_off_t ZEXPORT gzseek OF((gzFile file, + z_off_t offset, int whence)); +/* + Sets the starting position for the next gzread or gzwrite on the + given compressed file. The offset represents a number of bytes in the + uncompressed data stream. The whence parameter is defined as in lseek(2); + the value SEEK_END is not supported. + If the file is opened for reading, this function is emulated but can be + extremely slow. If the file is opened for writing, only forward seeks are + supported; gzseek then compresses a sequence of zeroes up to the new + starting position. + + gzseek returns the resulting offset location as measured in bytes from + the beginning of the uncompressed stream, or -1 in case of error, in + particular if the file is opened for writing and the new starting position + would be before the current position. +*/ + +ZEXTERN int ZEXPORT gzrewind OF((gzFile file)); +/* + Rewinds the given file. This function is supported only for reading. + + gzrewind(file) is equivalent to (int)gzseek(file, 0L, SEEK_SET) +*/ + +ZEXTERN z_off_t ZEXPORT gztell OF((gzFile file)); +/* + Returns the starting position for the next gzread or gzwrite on the + given compressed file. This position represents a number of bytes in the + uncompressed data stream. + + gztell(file) is equivalent to gzseek(file, 0L, SEEK_CUR) +*/ + +ZEXTERN int ZEXPORT gzeof OF((gzFile file)); +/* + Returns 1 when EOF has previously been detected reading the given + input stream, otherwise zero. +*/ + +ZEXTERN int ZEXPORT gzdirect OF((gzFile file)); +/* + Returns 1 if file is being read directly without decompression, otherwise + zero. +*/ + +ZEXTERN int ZEXPORT gzclose OF((gzFile file)); +/* + Flushes all pending output if necessary, closes the compressed file + and deallocates all the (de)compression state. The return value is the zlib + error number (see function gzerror below). +*/ + +ZEXTERN const char * ZEXPORT gzerror OF((gzFile file, int *errnum)); +/* + Returns the error message for the last error which occurred on the + given compressed file. errnum is set to zlib error number. If an + error occurred in the file system and not in the compression library, + errnum is set to Z_ERRNO and the application may consult errno + to get the exact error code. +*/ + +ZEXTERN void ZEXPORT gzclearerr OF((gzFile file)); +/* + Clears the error and end-of-file flags for file. This is analogous to the + clearerr() function in stdio. This is useful for continuing to read a gzip + file that is being written concurrently. +*/ + + /* checksum functions */ + +/* + These functions are not related to compression but are exported + anyway because they might be useful in applications using the + compression library. +*/ + +ZEXTERN uLong ZEXPORT adler32 OF((uLong adler, const Bytef *buf, uInt len)); +/* + Update a running Adler-32 checksum with the bytes buf[0..len-1] and + return the updated checksum. If buf is NULL, this function returns + the required initial value for the checksum. + An Adler-32 checksum is almost as reliable as a CRC32 but can be computed + much faster. Usage example: + + uLong adler = adler32(0L, Z_NULL, 0); + + while (read_buffer(buffer, length) != EOF) { + adler = adler32(adler, buffer, length); + } + if (adler != original_adler) error(); +*/ + +ZEXTERN uLong ZEXPORT adler32_combine OF((uLong adler1, uLong adler2, + z_off_t len2)); +/* + Combine two Adler-32 checksums into one. For two sequences of bytes, seq1 + and seq2 with lengths len1 and len2, Adler-32 checksums were calculated for + each, adler1 and adler2. adler32_combine() returns the Adler-32 checksum of + seq1 and seq2 concatenated, requiring only adler1, adler2, and len2. +*/ + +ZEXTERN uLong ZEXPORT crc32 OF((uLong crc, const Bytef *buf, uInt len)); +/* + Update a running CRC-32 with the bytes buf[0..len-1] and return the + updated CRC-32. If buf is NULL, this function returns the required initial + value for the for the crc. Pre- and post-conditioning (one's complement) is + performed within this function so it shouldn't be done by the application. + Usage example: + + uLong crc = crc32(0L, Z_NULL, 0); + + while (read_buffer(buffer, length) != EOF) { + crc = crc32(crc, buffer, length); + } + if (crc != original_crc) error(); +*/ + +ZEXTERN uLong ZEXPORT crc32_combine OF((uLong crc1, uLong crc2, z_off_t len2)); + +/* + Combine two CRC-32 check values into one. For two sequences of bytes, + seq1 and seq2 with lengths len1 and len2, CRC-32 check values were + calculated for each, crc1 and crc2. crc32_combine() returns the CRC-32 + check value of seq1 and seq2 concatenated, requiring only crc1, crc2, and + len2. +*/ + + + /* various hacks, don't look :) */ + +/* deflateInit and inflateInit are macros to allow checking the zlib version + * and the compiler's view of z_stream: + */ +ZEXTERN int ZEXPORT deflateInit_ OF((z_streamp strm, int level, + const char *version, int stream_size)); +ZEXTERN int ZEXPORT inflateInit_ OF((z_streamp strm, + const char *version, int stream_size)); +ZEXTERN int ZEXPORT deflateInit2_ OF((z_streamp strm, int level, int method, + int windowBits, int memLevel, + int strategy, const char *version, + int stream_size)); +ZEXTERN int ZEXPORT inflateInit2_ OF((z_streamp strm, int windowBits, + const char *version, int stream_size)); +ZEXTERN int ZEXPORT inflateBackInit_ OF((z_streamp strm, int windowBits, + unsigned char FAR *window, + const char *version, + int stream_size)); +#define deflateInit(strm, level) \ + deflateInit_((strm), (level), ZLIB_VERSION, sizeof(z_stream)) +#define inflateInit(strm) \ + inflateInit_((strm), ZLIB_VERSION, sizeof(z_stream)) +#define deflateInit2(strm, level, method, windowBits, memLevel, strategy) \ + deflateInit2_((strm),(level),(method),(windowBits),(memLevel),\ + (strategy), ZLIB_VERSION, sizeof(z_stream)) +#define inflateInit2(strm, windowBits) \ + inflateInit2_((strm), (windowBits), ZLIB_VERSION, sizeof(z_stream)) +#define inflateBackInit(strm, windowBits, window) \ + inflateBackInit_((strm), (windowBits), (window), \ + ZLIB_VERSION, sizeof(z_stream)) + + +#if !defined(ZUTIL_H) && !defined(NO_DUMMY_DECL) + struct internal_state {int dummy;}; /* hack for buggy compilers */ +#endif + +ZEXTERN const char * ZEXPORT zError OF((int)); +ZEXTERN int ZEXPORT inflateSyncPoint OF((z_streamp z)); +ZEXTERN const uLongf * ZEXPORT get_crc_table OF((void)); + +#ifdef __cplusplus +} +#endif + +#endif /* ZLIB_H */ diff --git a/snesreader/zlib/zlib.txt b/snesreader/zlib/zlib.txt new file mode 100644 index 00000000..80f71ae8 --- /dev/null +++ b/snesreader/zlib/zlib.txt @@ -0,0 +1,125 @@ +ZLIB DATA COMPRESSION LIBRARY + +zlib 1.2.3 is a general purpose data compression library. All the code is +thread safe. The data format used by the zlib library is described by RFCs +(Request for Comments) 1950 to 1952 in the files +http://www.ietf.org/rfc/rfc1950.txt (zlib format), rfc1951.txt (deflate format) +and rfc1952.txt (gzip format). These documents are also available in other +formats from ftp://ftp.uu.net/graphics/png/documents/zlib/zdoc-index.html + +All functions of the compression library are documented in the file zlib.h +(volunteer to write man pages welcome, contact zlib@gzip.org). A usage example +of the library is given in the file example.c which also tests that the library +is working correctly. Another example is given in the file minigzip.c. The +compression library itself is composed of all source files except example.c and +minigzip.c. + +To compile all files and run the test program, follow the instructions given at +the top of Makefile. In short "make test; make install" should work for most +machines. For Unix: "./configure; make test; make install". For MSDOS, use one +of the special makefiles such as Makefile.msc. For VMS, use make_vms.com. + +Questions about zlib should be sent to , or to Gilles Vollant + for the Windows DLL version. The zlib home page is +http://www.zlib.org or http://www.gzip.org/zlib/ Before reporting a problem, +please check this site to verify that you have the latest version of zlib; +otherwise get the latest version and check whether the problem still exists or +not. + +PLEASE read the zlib FAQ http://www.gzip.org/zlib/zlib_faq.html before asking +for help. + +Mark Nelson wrote an article about zlib for the Jan. 1997 +issue of Dr. Dobb's Journal; a copy of the article is available in +http://dogma.net/markn/articles/zlibtool/zlibtool.htm + +The changes made in version 1.2.3 are documented in the file ChangeLog. + +Unsupported third party contributions are provided in directory "contrib". + +A Java implementation of zlib is available in the Java Development Kit +http://java.sun.com/j2se/1.4.2/docs/api/java/util/zip/package-summary.html +See the zlib home page http://www.zlib.org for details. + +A Perl interface to zlib written by Paul Marquess is in the +CPAN (Comprehensive Perl Archive Network) sites +http://www.cpan.org/modules/by-module/Compress/ + +A Python interface to zlib written by A.M. Kuchling is +available in Python 1.5 and later versions, see +http://www.python.org/doc/lib/module-zlib.html + +A zlib binding for TCL written by Andreas Kupries is +availlable at http://www.oche.de/~akupries/soft/trf/trf_zip.html + +An experimental package to read and write files in .zip format, written on top +of zlib by Gilles Vollant , is available in the +contrib/minizip directory of zlib. + + +Notes for some targets: + +- For Windows DLL versions, please see win32/DLL_FAQ.txt + +- For 64-bit Irix, deflate.c must be compiled without any optimization. With + -O, one libpng test fails. The test works in 32 bit mode (with the -n32 + compiler flag). The compiler bug has been reported to SGI. + +- zlib doesn't work with gcc 2.6.3 on a DEC 3000/300LX under OSF/1 2.1 it works + when compiled with cc. + +- On Digital Unix 4.0D (formely OSF/1) on AlphaServer, the cc option -std1 is + necessary to get gzprintf working correctly. This is done by configure. + +- zlib doesn't work on HP-UX 9.05 with some versions of /bin/cc. It works with + other compilers. Use "make test" to check your compiler. + +- gzdopen is not supported on RISCOS, BEOS and by some Mac compilers. + +- For PalmOs, see http://palmzlib.sourceforge.net/ + +- When building a shared, i.e. dynamic library on Mac OS X, the library must be + installed before testing (do "make install" before "make test"), since the + library location is specified in the library. + + +Acknowledgments: + + The deflate format used by zlib was defined by Phil Katz. The deflate + and zlib specifications were written by L. Peter Deutsch. Thanks to all the + people who reported problems and suggested various improvements in zlib; + they are too numerous to cite here. + +Copyright notice: + + (C) 1995-2004 Jean-loup Gailly and Mark Adler + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + Jean-loup Gailly Mark Adler + jloup@gzip.org madler@alumni.caltech.edu + +If you use the zlib library in a product, we would appreciate *not* +receiving lengthy legal documents to sign. The sources are provided +for free but without warranty of any kind. The library has been +entirely written by Jean-loup Gailly and Mark Adler; it does not +include third-party code. + +If you redistribute modified sources, we would appreciate that you include +in the file ChangeLog history information documenting your changes. Please +read the FAQ for more information on the distribution of modified source +versions. diff --git a/snesreader/zlib/zutil.c b/snesreader/zlib/zutil.c new file mode 100644 index 00000000..d55f5948 --- /dev/null +++ b/snesreader/zlib/zutil.c @@ -0,0 +1,318 @@ +/* zutil.c -- target dependent utility functions for the compression library + * Copyright (C) 1995-2005 Jean-loup Gailly. + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* @(#) $Id$ */ + +#include "zutil.h" + +#ifndef NO_DUMMY_DECL +struct internal_state {int dummy;}; /* for buggy compilers */ +#endif + +const char * const z_errmsg[10] = { +"need dictionary", /* Z_NEED_DICT 2 */ +"stream end", /* Z_STREAM_END 1 */ +"", /* Z_OK 0 */ +"file error", /* Z_ERRNO (-1) */ +"stream error", /* Z_STREAM_ERROR (-2) */ +"data error", /* Z_DATA_ERROR (-3) */ +"insufficient memory", /* Z_MEM_ERROR (-4) */ +"buffer error", /* Z_BUF_ERROR (-5) */ +"incompatible version",/* Z_VERSION_ERROR (-6) */ +""}; + + +const char * ZEXPORT zlibVersion() +{ + return ZLIB_VERSION; +} + +uLong ZEXPORT zlibCompileFlags() +{ + uLong flags; + + flags = 0; + switch (sizeof(uInt)) { + case 2: break; + case 4: flags += 1; break; + case 8: flags += 2; break; + default: flags += 3; + } + switch (sizeof(uLong)) { + case 2: break; + case 4: flags += 1 << 2; break; + case 8: flags += 2 << 2; break; + default: flags += 3 << 2; + } + switch (sizeof(voidpf)) { + case 2: break; + case 4: flags += 1 << 4; break; + case 8: flags += 2 << 4; break; + default: flags += 3 << 4; + } + switch (sizeof(z_off_t)) { + case 2: break; + case 4: flags += 1 << 6; break; + case 8: flags += 2 << 6; break; + default: flags += 3 << 6; + } +#ifdef DEBUG + flags += 1 << 8; +#endif +#if defined(ASMV) || defined(ASMINF) + flags += 1 << 9; +#endif +#ifdef ZLIB_WINAPI + flags += 1 << 10; +#endif +#ifdef BUILDFIXED + flags += 1 << 12; +#endif +#ifdef DYNAMIC_CRC_TABLE + flags += 1 << 13; +#endif +#ifdef NO_GZCOMPRESS + flags += 1L << 16; +#endif +#ifdef NO_GZIP + flags += 1L << 17; +#endif +#ifdef PKZIP_BUG_WORKAROUND + flags += 1L << 20; +#endif +#ifdef FASTEST + flags += 1L << 21; +#endif +#ifdef STDC +# ifdef NO_vsnprintf + flags += 1L << 25; +# ifdef HAS_vsprintf_void + flags += 1L << 26; +# endif +# else +# ifdef HAS_vsnprintf_void + flags += 1L << 26; +# endif +# endif +#else + flags += 1L << 24; +# ifdef NO_snprintf + flags += 1L << 25; +# ifdef HAS_sprintf_void + flags += 1L << 26; +# endif +# else +# ifdef HAS_snprintf_void + flags += 1L << 26; +# endif +# endif +#endif + return flags; +} + +#ifdef DEBUG + +# ifndef verbose +# define verbose 0 +# endif +int z_verbose = verbose; + +void z_error (m) + char *m; +{ + fprintf(stderr, "%s\n", m); + exit(1); +} +#endif + +/* exported to allow conversion of error code to string for compress() and + * uncompress() + */ +const char * ZEXPORT zError(err) + int err; +{ + return ERR_MSG(err); +} + +#if defined(_WIN32_WCE) + /* The Microsoft C Run-Time Library for Windows CE doesn't have + * errno. We define it as a global variable to simplify porting. + * Its value is always 0 and should not be used. + */ + int errno = 0; +#endif + +#ifndef HAVE_MEMCPY + +void zmemcpy(dest, source, len) + Bytef* dest; + const Bytef* source; + uInt len; +{ + if (len == 0) return; + do { + *dest++ = *source++; /* ??? to be unrolled */ + } while (--len != 0); +} + +int zmemcmp(s1, s2, len) + const Bytef* s1; + const Bytef* s2; + uInt len; +{ + uInt j; + + for (j = 0; j < len; j++) { + if (s1[j] != s2[j]) return 2*(s1[j] > s2[j])-1; + } + return 0; +} + +void zmemzero(dest, len) + Bytef* dest; + uInt len; +{ + if (len == 0) return; + do { + *dest++ = 0; /* ??? to be unrolled */ + } while (--len != 0); +} +#endif + + +#ifdef SYS16BIT + +#ifdef __TURBOC__ +/* Turbo C in 16-bit mode */ + +# define MY_ZCALLOC + +/* Turbo C malloc() does not allow dynamic allocation of 64K bytes + * and farmalloc(64K) returns a pointer with an offset of 8, so we + * must fix the pointer. Warning: the pointer must be put back to its + * original form in order to free it, use zcfree(). + */ + +#define MAX_PTR 10 +/* 10*64K = 640K */ + +local int next_ptr = 0; + +typedef struct ptr_table_s { + voidpf org_ptr; + voidpf new_ptr; +} ptr_table; + +local ptr_table table[MAX_PTR]; +/* This table is used to remember the original form of pointers + * to large buffers (64K). Such pointers are normalized with a zero offset. + * Since MSDOS is not a preemptive multitasking OS, this table is not + * protected from concurrent access. This hack doesn't work anyway on + * a protected system like OS/2. Use Microsoft C instead. + */ + +voidpf zcalloc (voidpf opaque, unsigned items, unsigned size) +{ + voidpf buf = opaque; /* just to make some compilers happy */ + ulg bsize = (ulg)items*size; + + /* If we allocate less than 65520 bytes, we assume that farmalloc + * will return a usable pointer which doesn't have to be normalized. + */ + if (bsize < 65520L) { + buf = farmalloc(bsize); + if (*(ush*)&buf != 0) return buf; + } else { + buf = farmalloc(bsize + 16L); + } + if (buf == NULL || next_ptr >= MAX_PTR) return NULL; + table[next_ptr].org_ptr = buf; + + /* Normalize the pointer to seg:0 */ + *((ush*)&buf+1) += ((ush)((uch*)buf-0) + 15) >> 4; + *(ush*)&buf = 0; + table[next_ptr++].new_ptr = buf; + return buf; +} + +void zcfree (voidpf opaque, voidpf ptr) +{ + int n; + if (*(ush*)&ptr != 0) { /* object < 64K */ + farfree(ptr); + return; + } + /* Find the original pointer */ + for (n = 0; n < next_ptr; n++) { + if (ptr != table[n].new_ptr) continue; + + farfree(table[n].org_ptr); + while (++n < next_ptr) { + table[n-1] = table[n]; + } + next_ptr--; + return; + } + ptr = opaque; /* just to make some compilers happy */ + Assert(0, "zcfree: ptr not found"); +} + +#endif /* __TURBOC__ */ + + +#ifdef M_I86 +/* Microsoft C in 16-bit mode */ + +# define MY_ZCALLOC + +#if (!defined(_MSC_VER) || (_MSC_VER <= 600)) +# define _halloc halloc +# define _hfree hfree +#endif + +voidpf zcalloc (voidpf opaque, unsigned items, unsigned size) +{ + if (opaque) opaque = 0; /* to make compiler happy */ + return _halloc((long)items, size); +} + +void zcfree (voidpf opaque, voidpf ptr) +{ + if (opaque) opaque = 0; /* to make compiler happy */ + _hfree(ptr); +} + +#endif /* M_I86 */ + +#endif /* SYS16BIT */ + + +#ifndef MY_ZCALLOC /* Any system without a special alloc function */ + +#ifndef STDC +extern voidp malloc OF((uInt size)); +extern voidp calloc OF((uInt items, uInt size)); +extern void free OF((voidpf ptr)); +#endif + +voidpf zcalloc (opaque, items, size) + voidpf opaque; + unsigned items; + unsigned size; +{ + if (opaque) items += size - size; /* make compiler happy */ + return sizeof(uInt) > 2 ? (voidpf)malloc(items * size) : + (voidpf)calloc(items, size); +} + +void zcfree (opaque, ptr) + voidpf opaque; + voidpf ptr; +{ + free(ptr); + if (opaque) return; /* make compiler happy */ +} + +#endif /* MY_ZCALLOC */ diff --git a/snesreader/zlib/zutil.h b/snesreader/zlib/zutil.h new file mode 100644 index 00000000..b7d5eff8 --- /dev/null +++ b/snesreader/zlib/zutil.h @@ -0,0 +1,269 @@ +/* zutil.h -- internal interface and configuration of the compression library + * Copyright (C) 1995-2005 Jean-loup Gailly. + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* WARNING: this file should *not* be used by applications. It is + part of the implementation of the compression library and is + subject to change. Applications should only use zlib.h. + */ + +/* @(#) $Id$ */ + +#ifndef ZUTIL_H +#define ZUTIL_H + +#define ZLIB_INTERNAL +#include "zlib.h" + +#ifdef STDC +# ifndef _WIN32_WCE +# include +# endif +# include +# include +#endif +#ifdef NO_ERRNO_H +# ifdef _WIN32_WCE + /* The Microsoft C Run-Time Library for Windows CE doesn't have + * errno. We define it as a global variable to simplify porting. + * Its value is always 0 and should not be used. We rename it to + * avoid conflict with other libraries that use the same workaround. + */ +# define errno z_errno +# endif + extern int errno; +#else +# ifndef _WIN32_WCE +# include +# endif +#endif + +#ifndef local +# define local static +#endif +/* compile with -Dlocal if your debugger can't find static symbols */ + +typedef unsigned char uch; +typedef uch FAR uchf; +typedef unsigned short ush; +typedef ush FAR ushf; +typedef unsigned long ulg; + +extern const char * const z_errmsg[10]; /* indexed by 2-zlib_error */ +/* (size given to avoid silly warnings with Visual C++) */ + +#define ERR_MSG(err) z_errmsg[Z_NEED_DICT-(err)] + +#define ERR_RETURN(strm,err) \ + return (strm->msg = (char*)ERR_MSG(err), (err)) +/* To be used only when the state is known to be valid */ + + /* common constants */ + +#ifndef DEF_WBITS +# define DEF_WBITS MAX_WBITS +#endif +/* default windowBits for decompression. MAX_WBITS is for compression only */ + +#if MAX_MEM_LEVEL >= 8 +# define DEF_MEM_LEVEL 8 +#else +# define DEF_MEM_LEVEL MAX_MEM_LEVEL +#endif +/* default memLevel */ + +#define STORED_BLOCK 0 +#define STATIC_TREES 1 +#define DYN_TREES 2 +/* The three kinds of block type */ + +#define MIN_MATCH 3 +#define MAX_MATCH 258 +/* The minimum and maximum match lengths */ + +#define PRESET_DICT 0x20 /* preset dictionary flag in zlib header */ + + /* target dependencies */ + +#if defined(MSDOS) || (defined(WINDOWS) && !defined(WIN32)) +# define OS_CODE 0x00 +# if defined(__TURBOC__) || defined(__BORLANDC__) +# if(__STDC__ == 1) && (defined(__LARGE__) || defined(__COMPACT__)) + /* Allow compilation with ANSI keywords only enabled */ + void _Cdecl farfree( void *block ); + void *_Cdecl farmalloc( unsigned long nbytes ); +# else +# include +# endif +# else /* MSC or DJGPP */ +# include +# endif +#endif + +#ifdef AMIGA +# define OS_CODE 0x01 +#endif + +#if defined(VAXC) || defined(VMS) +# define OS_CODE 0x02 +# define F_OPEN(name, mode) \ + fopen((name), (mode), "mbc=60", "ctx=stm", "rfm=fix", "mrs=512") +#endif + +#if defined(ATARI) || defined(atarist) +# define OS_CODE 0x05 +#endif + +#ifdef OS2 +# define OS_CODE 0x06 +# ifdef M_I86 + #include +# endif +#endif + +#if defined(MACOS) || defined(TARGET_OS_MAC) +# define OS_CODE 0x07 +# if defined(__MWERKS__) && __dest_os != __be_os && __dest_os != __win32_os +# include /* for fdopen */ +# else +# ifndef fdopen +# define fdopen(fd,mode) NULL /* No fdopen() */ +# endif +# endif +#endif + +#ifdef TOPS20 +# define OS_CODE 0x0a +#endif + +#ifdef WIN32 +# ifndef __CYGWIN__ /* Cygwin is Unix, not Win32 */ +# define OS_CODE 0x0b +# endif +#endif + +#ifdef __50SERIES /* Prime/PRIMOS */ +# define OS_CODE 0x0f +#endif + +#if defined(_BEOS_) || defined(RISCOS) +# define fdopen(fd,mode) NULL /* No fdopen() */ +#endif + +#if (defined(_MSC_VER) && (_MSC_VER > 600)) +# if defined(_WIN32_WCE) +# define fdopen(fd,mode) NULL /* No fdopen() */ +# ifndef _PTRDIFF_T_DEFINED + typedef int ptrdiff_t; +# define _PTRDIFF_T_DEFINED +# endif +# else +# define fdopen(fd,type) _fdopen(fd,type) +# endif +#endif + + /* common defaults */ + +#ifndef OS_CODE +# define OS_CODE 0x03 /* assume Unix */ +#endif + +#ifndef F_OPEN +# define F_OPEN(name, mode) fopen((name), (mode)) +#endif + + /* functions */ + +#if defined(STDC99) || (defined(__TURBOC__) && __TURBOC__ >= 0x550) +# ifndef HAVE_VSNPRINTF +# define HAVE_VSNPRINTF +# endif +#endif +#if defined(__CYGWIN__) +# ifndef HAVE_VSNPRINTF +# define HAVE_VSNPRINTF +# endif +#endif +#ifndef HAVE_VSNPRINTF +# ifdef MSDOS + /* vsnprintf may exist on some MS-DOS compilers (DJGPP?), + but for now we just assume it doesn't. */ +# define NO_vsnprintf +# endif +# ifdef __TURBOC__ +# define NO_vsnprintf +# endif +# ifdef WIN32 + /* In Win32, vsnprintf is available as the "non-ANSI" _vsnprintf. */ +# if !defined(vsnprintf) && !defined(NO_vsnprintf) +# define vsnprintf _vsnprintf +# endif +# endif +# ifdef __SASC +# define NO_vsnprintf +# endif +#endif +#ifdef VMS +# define NO_vsnprintf +#endif + +#if defined(pyr) +# define NO_MEMCPY +#endif +#if defined(SMALL_MEDIUM) && !defined(_MSC_VER) && !defined(__SC__) + /* Use our own functions for small and medium model with MSC <= 5.0. + * You may have to use the same strategy for Borland C (untested). + * The __SC__ check is for Symantec. + */ +# define NO_MEMCPY +#endif +#if defined(STDC) && !defined(HAVE_MEMCPY) && !defined(NO_MEMCPY) +# define HAVE_MEMCPY +#endif +#ifdef HAVE_MEMCPY +# ifdef SMALL_MEDIUM /* MSDOS small or medium model */ +# define zmemcpy _fmemcpy +# define zmemcmp _fmemcmp +# define zmemzero(dest, len) _fmemset(dest, 0, len) +# else +# define zmemcpy memcpy +# define zmemcmp memcmp +# define zmemzero(dest, len) memset(dest, 0, len) +# endif +#else + extern void zmemcpy OF((Bytef* dest, const Bytef* source, uInt len)); + extern int zmemcmp OF((const Bytef* s1, const Bytef* s2, uInt len)); + extern void zmemzero OF((Bytef* dest, uInt len)); +#endif + +/* Diagnostic functions */ +#ifdef DEBUG +# include + extern int z_verbose; + extern void z_error OF((char *m)); +# define Assert(cond,msg) {if(!(cond)) z_error(msg);} +# define Trace(x) {if (z_verbose>=0) fprintf x ;} +# define Tracev(x) {if (z_verbose>0) fprintf x ;} +# define Tracevv(x) {if (z_verbose>1) fprintf x ;} +# define Tracec(c,x) {if (z_verbose>0 && (c)) fprintf x ;} +# define Tracecv(c,x) {if (z_verbose>1 && (c)) fprintf x ;} +#else +# define Assert(cond,msg) +# define Trace(x) +# define Tracev(x) +# define Tracevv(x) +# define Tracec(c,x) +# define Tracecv(c,x) +#endif + + +voidpf zcalloc OF((voidpf opaque, unsigned items, unsigned size)); +void zcfree OF((voidpf opaque, voidpf ptr)); + +#define ZALLOC(strm, items, size) \ + (*((strm)->zalloc))((strm)->opaque, (items), (size)) +#define ZFREE(strm, addr) (*((strm)->zfree))((strm)->opaque, (voidpf)(addr)) +#define TRY_FREE(s, p) {if (p) ZFREE(s, p);} + +#endif /* ZUTIL_H */ diff --git a/src/nall/info/snes.hpp b/src/nall/snes/info.hpp similarity index 99% rename from src/nall/info/snes.hpp rename to src/nall/snes/info.hpp index 05560df2..5b3fc9d9 100644 --- a/src/nall/info/snes.hpp +++ b/src/nall/snes/info.hpp @@ -1,5 +1,5 @@ -#ifndef NALL_INFO_SNES_HPP -#define NALL_INFO_SNES_HPP +#ifndef NALL_SNES_INFO_HPP +#define NALL_SNES_INFO_HPP namespace nall { diff --git a/src/snes/Makefile b/src/snes/Makefile index 44ce3bb9..ae00af29 100644 --- a/src/snes/Makefile +++ b/src/snes/Makefile @@ -18,7 +18,7 @@ obj/libco.o: libco/libco.c libco/* obj/libsnes.o: snes/libsnes/libsnes.cpp snes/libsnes/* # system -obj/snes-system.o: snes/system/system.cpp $(call rwildcard,snes/system/) +obj/snes-system.o: snes/system/system.cpp $(call rwildcard,snes/system/) $(call rwildcard,snes/video/) # memory obj/snes-memory.o : snes/memory/memory.cpp snes/memory/* diff --git a/src/snes/interface/interface.hpp b/src/snes/interface/interface.hpp index a4814884..e9ec2635 100644 --- a/src/snes/interface/interface.hpp +++ b/src/snes/interface/interface.hpp @@ -1,6 +1,6 @@ class Interface { public: - virtual void video_refresh(uint16_t *data, unsigned pitch, unsigned *line, unsigned width, unsigned height) {} + virtual void video_refresh(const uint16_t *data, unsigned width, unsigned height) {} virtual void audio_sample(uint16_t l_sample, uint16_t r_sample) {} virtual void input_poll() {} virtual int16_t input_poll(bool port, Input::Device device, unsigned index, unsigned id) { return 0; } diff --git a/src/snes/libsnes/libsnes.cpp b/src/snes/libsnes/libsnes.cpp index 91574dcf..7bd22ddb 100644 --- a/src/snes/libsnes/libsnes.cpp +++ b/src/snes/libsnes/libsnes.cpp @@ -1,7 +1,7 @@ #include "libsnes.hpp" #include -#include +#include using namespace nall; struct Interface : public SNES::Interface { @@ -10,20 +10,21 @@ struct Interface : public SNES::Interface { snes_input_poll_t pinput_poll; snes_input_state_t pinput_state; - void video_refresh(uint16_t *data, unsigned pitch, unsigned *line, unsigned width, unsigned height) { - return pvideo_refresh(data, pitch, line, width, height); + void video_refresh(const uint16_t *data, unsigned width, unsigned height) { + if(pvideo_refresh) return pvideo_refresh(data, width, height); } void audio_sample(uint16_t left, uint16_t right) { - return paudio_sample(left, right); + if(paudio_sample) return paudio_sample(left, right); } void input_poll() { - return pinput_poll(); + if(pinput_poll) return pinput_poll(); } int16_t input_poll(bool port, SNES::Input::Device device, unsigned index, unsigned id) { - return pinput_state(port, (unsigned)device, index, id); + if(pinput_state) return pinput_state(port, (unsigned)device, index, id); + return 0; } Interface() : pvideo_refresh(0), paudio_sample(0), pinput_poll(0), pinput_state(0) { @@ -32,7 +33,7 @@ struct Interface : public SNES::Interface { static Interface interface; -unsigned snes_library_revision() { +unsigned snes_library_revision(void) { return 1; } @@ -56,25 +57,33 @@ void snes_set_controller_port_device(bool port, unsigned device) { SNES::input.port_set_device(port, (SNES::Input::Device)device); } -void snes_init() { +void snes_init(void) { SNES::system.init(&interface); SNES::input.port_set_device(0, SNES::Input::Device::Joypad); SNES::input.port_set_device(1, SNES::Input::Device::Joypad); } -void snes_term() { +void snes_term(void) { SNES::system.term(); } -void snes_unload() { - SNES::cartridge.unload(); +void snes_power(void) { + SNES::system.power(); } -void snes_run() { +void snes_reset(void) { + SNES::system.reset(); +} + +void snes_run(void) { SNES::system.run(); } -unsigned snes_serialize_size() { +void snes_unload(void) { + SNES::cartridge.unload(); +} + +unsigned snes_serialize_size(void) { return SNES::system.serialize_size(); } @@ -91,7 +100,7 @@ bool snes_unserialize(const uint8_t *data, unsigned size) { return SNES::system.unserialize(s); } -void snes_cheat_reset() { +void snes_cheat_reset(void) { SNES::cheat.reset(); SNES::cheat.synchronize(); } @@ -105,7 +114,7 @@ void snes_cheat_set(unsigned index, bool enabled, const char *code) { void snes_load_cartridge_normal( const char *rom_xml, const uint8_t *rom_data, unsigned rom_size ) { - SNES::cheat.reset(); + snes_cheat_reset(); SNES::memory::cartrom.copy(rom_data, rom_size); string xmlrom = rom_xml ? string(rom_xml) : snes_information(rom_data, rom_size).xml_memory_map; SNES::cartridge.load(SNES::Cartridge::Mode::Normal, { xmlrom }); @@ -116,7 +125,7 @@ void snes_load_cartridge_bsx_slotted( const char *rom_xml, const uint8_t *rom_data, unsigned rom_size, const char *bsx_xml, const uint8_t *bsx_data, unsigned bsx_size ) { - SNES::cheat.reset(); + snes_cheat_reset(); SNES::memory::cartrom.copy(rom_data, rom_size); string xmlrom = rom_xml ? string(rom_xml) : snes_information(rom_data, rom_size).xml_memory_map; SNES::memory::bsxflash.copy(bsx_data, bsx_size); @@ -129,7 +138,7 @@ void snes_load_cartridge_bsx( const char *rom_xml, const uint8_t *rom_data, unsigned rom_size, const char *bsx_xml, const uint8_t *bsx_data, unsigned bsx_size ) { - SNES::cheat.reset(); + snes_cheat_reset(); SNES::memory::cartrom.copy(rom_data, rom_size); string xmlrom = rom_xml ? string(rom_xml) : snes_information(rom_data, rom_size).xml_memory_map; SNES::memory::bsxflash.copy(bsx_data, bsx_size); @@ -143,7 +152,7 @@ void snes_load_cartridge_sufami_turbo( const char *sta_xml, const uint8_t *sta_data, unsigned sta_size, const char *stb_xml, const uint8_t *stb_data, unsigned stb_size ) { - SNES::cheat.reset(); + snes_cheat_reset(); SNES::memory::cartrom.copy(rom_data, rom_size); string xmlrom = rom_xml ? string(rom_xml) : snes_information(rom_data, rom_size).xml_memory_map; SNES::memory::stArom.copy(sta_data, sta_size); @@ -158,7 +167,7 @@ void snes_load_cartridge_super_game_boy( const char *rom_xml, const uint8_t *rom_data, unsigned rom_size, const char *dmg_xml, const uint8_t *dmg_data, unsigned dmg_size ) { - SNES::cheat.reset(); + snes_cheat_reset(); SNES::memory::cartrom.copy(rom_data, rom_size); string xmlrom = rom_xml ? string(rom_xml) : snes_information(rom_data, rom_size).xml_memory_map; SNES::memory::gbrom.copy(dmg_data, dmg_size); diff --git a/src/snes/libsnes/libsnes.hpp b/src/snes/libsnes/libsnes.hpp index ab7035fa..8ad2cce5 100644 --- a/src/snes/libsnes/libsnes.hpp +++ b/src/snes/libsnes/libsnes.hpp @@ -1,3 +1,6 @@ +#ifndef LIBSNES_HPP +#define LIBSNES_HPP + #include #ifdef __cplusplus @@ -51,29 +54,32 @@ extern "C" { #define SNES_DEVICE_ID_JUSTIFIER_TRIGGER 2 #define SNES_DEVICE_ID_JUSTIFIER_START 3 -typedef void (*snes_video_refresh_t)(const uint16_t *data, unsigned pitch, const unsigned *line, unsigned width, unsigned height); +typedef void (*snes_video_refresh_t)(const uint16_t *data, unsigned width, unsigned height); typedef void (*snes_audio_sample_t)(uint16_t left, uint16_t right); -typedef void (*snes_input_poll_t)(); +typedef void (*snes_input_poll_t)(void); typedef int16_t (*snes_input_state_t)(bool port, unsigned device, unsigned index, unsigned id); -unsigned snes_library_revision(); +unsigned snes_library_revision(void); void snes_set_video_refresh(snes_video_refresh_t); void snes_set_audio_sample(snes_audio_sample_t); void snes_set_input_poll(snes_input_poll_t); void snes_set_input_state(snes_input_state_t); + void snes_set_controller_port_device(bool port, unsigned device); -void snes_init(); -void snes_term(); -void snes_unload(); -void snes_run(); +void snes_init(void); +void snes_term(void); +void snes_power(void); +void snes_reset(void); +void snes_run(void); +void snes_unload(void); -unsigned snes_serialize_size(); +unsigned snes_serialize_size(void); bool snes_serialize(uint8_t *data, unsigned size); bool snes_unserialize(const uint8_t *data, unsigned size); -void snes_cheat_reset(); +void snes_cheat_reset(void); void snes_cheat_set(unsigned index, bool enabled, const char *code); void snes_load_cartridge_normal( @@ -107,3 +113,5 @@ unsigned snes_get_memory_size(unsigned id); #ifdef __cplusplus } #endif + +#endif diff --git a/src/snes/snes.hpp b/src/snes/snes.hpp index 844dd565..0684316b 100644 --- a/src/snes/snes.hpp +++ b/src/snes/snes.hpp @@ -1,4 +1,4 @@ -static const char bsnesVersion[] = "063.14"; +static const char bsnesVersion[] = "064"; static const char bsnesTitle[] = "bsnes"; static const unsigned bsnesSerializerVersion = 10; diff --git a/src/snes/system/system.cpp b/src/snes/system/system.cpp index e6dd965d..b400b38f 100644 --- a/src/snes/system/system.cpp +++ b/src/snes/system/system.cpp @@ -225,7 +225,7 @@ void System::frame() { } System::System() : interface(0) { - region = Region::NTSC; + region = Region::Autodetect; expansion = ExpansionPortDevice::None; } diff --git a/src/snes/video/video.cpp b/src/snes/video/video.cpp index d1557746..dc74c3ec 100644 --- a/src/snes/video/video.cpp +++ b/src/snes/video/video.cpp @@ -25,7 +25,7 @@ void Video::draw_cursor(uint16_t color, int x, int y) { int vy = y + cy - 7; if(vy <= 0 || vy >= 240) continue; //do not draw offscreen - bool hires = (pline_width[vy] == 512); + bool hires = (line_width[vy] == 512); for(int cx = 0; cx < 15; cx++) { int vx = x + cx - 7; if(vx < 0 || vx >= 256) continue; //do not draw offscreen @@ -47,32 +47,33 @@ void Video::draw_cursor(uint16_t color, int x, int y) { } void Video::update() { - uint16_t *data = (uint16_t*)ppu.output; - unsigned width, height; - switch(input.port[1].device) { case Input::Device::SuperScope: draw_cursor(0x001f, input.port[1].superscope.x, input.port[1].superscope.y); break; case Input::Device::Justifiers: draw_cursor(0x02e0, input.port[1].justifier.x2, input.port[1].justifier.y2); //fallthrough case Input::Device::Justifier: draw_cursor(0x001f, input.port[1].justifier.x1, input.port[1].justifier.y1); break; } - unsigned yoffset = 1; //scanline 0 is always black, skip this line for video output - if(mode == Mode::NTSC && ppu.overscan()) yoffset += 8; //NTSC overscan centers x240 height image + uint16_t *data = (uint16_t*)ppu.output; + unsigned width = 256; + unsigned height = !ppu.overscan() ? 224 : 239; - switch(mode) { default: - case Mode::NTSC: { width = 256; height = 224; } break; - case Mode::PAL: { width = 256; height = 239; } break; + if(frame_hires) { + width <<= 1; + //normalize line widths + for(unsigned y = 0; y < 240; y++) { + if(line_width[y] == 512) continue; + uint16_t *buffer = data + y * 1024; + for(signed x = 255; x >= 0; x--) { + buffer[(x * 2) + 0] = buffer[(x * 2) + 1] = buffer[x]; + } + } } - if(frame_hires) width <<= 1; - if(frame_interlace) height <<= 1; + if(frame_interlace) { + height <<= 1; + } - system.interface->video_refresh( - data + yoffset * 1024, - /* pitch = */ height <= 240 ? 2048 : 1024, - /* line[] = */ height <= 240 ? (pline_width + yoffset) : (iline_width + yoffset * 2), - width, height - ); + system.interface->video_refresh(data + 1024, width, height); frame_hires = false; frame_interlace = false; @@ -82,24 +83,16 @@ void Video::scanline() { unsigned y = cpu.vcounter(); if(y >= 240) return; - unsigned width = (ppu.hires() == false ? 256 : 512); - pline_width[y] = width; - iline_width[y * 2 + (int)cpu.field()] = width; - frame_hires |= ppu.hires(); frame_interlace |= ppu.interlace(); -} - -void Video::set_mode(Mode mode_) { - mode = mode_; + unsigned width = (ppu.hires() == false ? 256 : 512); + line_width[y] = width; } void Video::init() { - for(unsigned i = 0; i < 240; i++) pline_width[i] = 256; - for(unsigned i = 0; i < 480; i++) iline_width[i] = 256; frame_hires = false; frame_interlace = false; - set_mode(Mode::NTSC); + for(unsigned i = 0; i < 240; i++) line_width[i] = 256; } #endif diff --git a/src/snes/video/video.hpp b/src/snes/video/video.hpp index f81a1e40..2d19a313 100644 --- a/src/snes/video/video.hpp +++ b/src/snes/video/video.hpp @@ -1,15 +1,7 @@ class Video { -public: - enum class Mode : unsigned { NTSC, PAL }; - void set_mode(Mode); - -private: - Mode mode; bool frame_hires; bool frame_interlace; - - unsigned pline_width[240]; //progressive - unsigned iline_width[480]; //interlace + unsigned line_width[240]; void update(); void scanline(); diff --git a/src/ui_qt/application/init.cpp b/src/ui_qt/application/init.cpp index 3cb77c87..e5fed0e8 100644 --- a/src/ui_qt/application/init.cpp +++ b/src/ui_qt/application/init.cpp @@ -98,7 +98,6 @@ void Application::init() { utility.resizeMainWindow(); utility.updateAvSync(); - utility.updateVideoMode(); utility.updateColorFilter(); utility.updatePixelShader(); utility.updateHardwareFilter(); diff --git a/src/ui_qt/interface.cpp b/src/ui_qt/interface.cpp index 49fca028..d76f19c7 100644 --- a/src/ui_qt/interface.cpp +++ b/src/ui_qt/interface.cpp @@ -1,6 +1,22 @@ Interface interface; -void Interface::video_refresh(uint16_t *data, unsigned pitch, unsigned *line, unsigned width, unsigned height) { +void Interface::video_refresh(const uint16_t *data, unsigned width, unsigned height) { + bool interlace = (height >= 240); + bool overscan = (height == 239 || height == 478); + unsigned pitch = interlace ? 1024 : 2048; + + //TV resolution and overscan simulation + if(config().video.context->region == 0) { + //NTSC + height = 224; + if(interlace) height <<= 1; + if(overscan) data += 8 * 1024; + } else { + //PAL + height = 239; + if(interlace) height <<= 1; + } + //scale display.crop* values from percentage-based (0-100%) to exact pixel sizes (width, height) unsigned cropLeft = (double)display.cropLeft / 100.0 * width; unsigned cropTop = (double)display.cropTop / 100.0 * height; @@ -16,7 +32,7 @@ void Interface::video_refresh(uint16_t *data, unsigned pitch, unsigned *line, un if(video.lock(output, outpitch, outwidth, outheight) == true) { data += cropTop * (pitch >> 1) + cropLeft; - filter.render(output, outpitch, data, pitch, line + cropTop, width, height); + filter.render(output, outpitch, data, pitch, width, height); video.unlock(); video.refresh(); if(saveScreenshot == true) captureScreenshot(output, outpitch, outwidth, outheight); diff --git a/src/ui_qt/interface.hpp b/src/ui_qt/interface.hpp index 8a5296c2..e00f6ff9 100644 --- a/src/ui_qt/interface.hpp +++ b/src/ui_qt/interface.hpp @@ -1,6 +1,6 @@ class Interface : public SNES::Interface { public: - void video_refresh(uint16_t *data, unsigned pitch, unsigned *line, unsigned width, unsigned height); + void video_refresh(const uint16_t *data, unsigned width, unsigned height); void audio_sample(uint16_t left, uint16_t right); void input_poll(); int16_t input_poll(bool port, SNES::Input::Device device, unsigned index, unsigned id); diff --git a/src/ui_qt/link/filter.cpp b/src/ui_qt/link/filter.cpp index b89119a6..7470c76d 100644 --- a/src/ui_qt/link/filter.cpp +++ b/src/ui_qt/link/filter.cpp @@ -10,32 +10,27 @@ void ScanlineFilter::size(unsigned &width, unsigned &height) { void ScanlineFilter::render( const uint16_t *&input, unsigned &pitch, - const unsigned *&line, unsigned width, unsigned &height + unsigned width, unsigned &height ) { if(enabled && height <= 240) { pitch >>= 1; const uint16_t *sp = input; uint16_t *dp = buffer; - unsigned *lp = linewidth; for(unsigned y = 0; y < height; y++) { - for(unsigned x = 0; x < line[y]; x++) { + for(unsigned x = 0; x < width; x++) { uint16_t color = *sp++; *(dp + 0) = color; *(dp + 512) = adjust[color]; dp++; } - sp += pitch - line[y]; - dp += 1024 - line[y]; - - *lp++ = line[y]; - *lp++ = line[y]; + sp += pitch - width; + dp += 1024 - width; } input = buffer; pitch = 1024; - line = linewidth; height *= 2; } } @@ -180,13 +175,12 @@ void Filter::size(unsigned &outwidth, unsigned &outheight, unsigned width, unsig void Filter::render( uint32_t *output, unsigned outpitch, - const uint16_t *input, unsigned pitch, - const unsigned *line, unsigned width, unsigned height + const uint16_t *input, unsigned pitch, unsigned width, unsigned height ) { - scanlineFilter.render(input, pitch, line, width, height); + scanlineFilter.render(input, pitch, width, height); if(opened() && renderer > 0) { - return dl_render(renderer, output, outpitch, input, pitch, line, width, height); + return dl_render(renderer, output, outpitch, input, pitch, width, height); } pitch >>= 1; @@ -195,18 +189,8 @@ void Filter::render( for(unsigned y = 0; y < height; y++) { const uint16_t *in = input + y * pitch; uint32_t *out = output + y * outpitch; - - if(width > 256 && line[y] <= 256) { - for(unsigned x = 0; x < line[y]; x++) { - uint16_t p = *in++; - *out++ = colortable[p]; - *out++ = colortable[p]; - } - } else { - for(unsigned x = 0; x < width; x++) { - uint16_t p = *in++; - *out++ = colortable[p]; - } + for(unsigned x = 0; x < width; x++) { + *out++ = colortable[*in++]; } } } diff --git a/src/ui_qt/link/filter.hpp b/src/ui_qt/link/filter.hpp index 6db29989..b71c675d 100644 --- a/src/ui_qt/link/filter.hpp +++ b/src/ui_qt/link/filter.hpp @@ -3,7 +3,7 @@ public: bool enabled; void size(unsigned&, unsigned&); - void render(const uint16_t*&, unsigned&, const unsigned*&, unsigned, unsigned&); + void render(const uint16_t*&, unsigned&, unsigned, unsigned&); void setIntensity(unsigned); ScanlineFilter(); @@ -12,7 +12,6 @@ public: private: uint16_t *adjust; uint16_t *buffer; - unsigned linewidth[480]; }; class Filter : public library { @@ -21,7 +20,7 @@ public: function dl_colortable; function dl_configuration; function dl_size; - function dl_render; + function dl_render; function dl_settings; unsigned renderer; @@ -38,7 +37,7 @@ public: void colortable_update(); void size(unsigned&, unsigned&, unsigned, unsigned); - void render(uint32_t*, unsigned, const uint16_t*, unsigned, const unsigned*, unsigned, unsigned); + void render(uint32_t*, unsigned, const uint16_t*, unsigned, unsigned, unsigned); QWidget* settings(); Filter(); diff --git a/src/ui_qt/ui-base.hpp b/src/ui_qt/ui-base.hpp index 3c7e76cf..735e5cc5 100644 --- a/src/ui_qt/ui-base.hpp +++ b/src/ui_qt/ui-base.hpp @@ -15,7 +15,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/src/ui_qt/utility/utility.cpp b/src/ui_qt/utility/utility.cpp index 0e76fe6b..0cc5fa92 100644 --- a/src/ui_qt/utility/utility.cpp +++ b/src/ui_qt/utility/utility.cpp @@ -62,14 +62,6 @@ void Utility::updateAvSync() { audio.set(Audio::Synchronize, config().audio.synchronize); } -void Utility::updateVideoMode() { - if(config().video.context->region == 0) { - SNES::video.set_mode(SNES::Video::Mode::NTSC); - } else { - SNES::video.set_mode(SNES::Video::Mode::PAL); - } -} - void Utility::updateColorFilter() { filter.contrast = config().video.contrastAdjust; filter.brightness = config().video.brightnessAdjust; diff --git a/src/ui_qt/utility/utility.hpp b/src/ui_qt/utility/utility.hpp index 3564f968..5ed0edd8 100644 --- a/src/ui_qt/utility/utility.hpp +++ b/src/ui_qt/utility/utility.hpp @@ -8,7 +8,6 @@ public: void unacquireMouse(); void updateAvSync(); - void updateVideoMode(); void updateColorFilter(); void updatePixelShader(); void updateHardwareFilter(); diff --git a/src/ui_qt/utility/window.cpp b/src/ui_qt/utility/window.cpp index 44bd8e1f..28dcded6 100644 --- a/src/ui_qt/utility/window.cpp +++ b/src/ui_qt/utility/window.cpp @@ -21,7 +21,6 @@ void Utility::updateFullscreenState() { #endif //refresh options that are unique to each video context - updateVideoMode(); updateHardwareFilter(); updateSoftwareFilter(); mainWindow->syncUi(); @@ -140,7 +139,6 @@ void Utility::toggleSynchronizeAudio() { void Utility::setNtscMode() { config().video.context->region = 0; - updateVideoMode(); resizeMainWindow(); mainWindow->shrink(); mainWindow->syncUi(); @@ -148,7 +146,6 @@ void Utility::setNtscMode() { void Utility::setPalMode() { config().video.context->region = 1; - updateVideoMode(); resizeMainWindow(); mainWindow->shrink(); mainWindow->syncUi(); diff --git a/src/ui_sdl/main.cpp b/src/ui_sdl/main.cpp index 957f533d..5393b292 100644 --- a/src/ui_sdl/main.cpp +++ b/src/ui_sdl/main.cpp @@ -3,7 +3,7 @@ SDL_Surface *screen, *backbuffer; -void video_refresh(const uint16_t *data, unsigned pitch, const unsigned *line, unsigned width, unsigned height) { +void video_refresh(const uint16_t *data, unsigned width, unsigned height) { if(SDL_MUSTLOCK(screen)) SDL_LockSurface(screen); if(SDL_MUSTLOCK(backbuffer)) SDL_LockSurface(backbuffer); @@ -11,7 +11,7 @@ void video_refresh(const uint16_t *data, unsigned pitch, const unsigned *line, u uint16_t *outputData = (uint16_t*)backbuffer->pixels; for(unsigned y = 0; y < height; y++) { - uint16_t *src = (uint16_t*)((uint8_t*)data + y * pitch); + uint16_t *src = (uint16_t*)((uint8_t*)data + y * 2048); uint16_t *dst = (uint16_t*)((uint8_t*)outputData + y * outputPitch); memcpy(dst, src, width * sizeof(uint16_t)); } diff --git a/supergameboy/Makefile b/supergameboy/Makefile new file mode 100644 index 00000000..60409afa --- /dev/null +++ b/supergameboy/Makefile @@ -0,0 +1,126 @@ +include nall/Makefile + +c := $(compiler) -std=gnu99 +cpp := $(subst cc,++,$(compiler)) -std=gnu++0x +flags := -O3 -fomit-frame-pointer -I. -Icommon -Ilibgambatte/include -Ilibgambatte/src +link := + +ifeq ($(platform),osx) + flags := -fPIC $(flags) +else ifeq ($(platform),x) + flags := -fPIC $(flags) + link += -s +endif + +objects := supergameboy +objects += bitmap_font colorconversion cpu gambatte initstate interrupter +objects += memory rtc sound state_osd_elements statesaver video +objects += channel1 channel2 channel3 channel4 duty_unit envelope_unit length_counter +objects += basic_add_event break_event irq_event ly_counter lyc_irq +objects += m3_extra_cycles mode3_event mode0_irq mode1_irq mode2_irq +objects += sc_reader scx_reader sprite_mapper we_master_checker we wx_reader wy +objects += catrom2x catrom3x kreed2xsai maxsthq2x maxsthq3x file + +compile = \ + $(strip \ + $(if $(filter %.c,$<), \ + $(c) $(flags) $1 -c $< -o $@, \ + $(if $(filter %.cpp,$<), \ + $(cpp) $(flags) $1 -c $< -o $@ \ + ) \ + ) \ + ) + +%.o: $<; $(call compile) + +all: build; + +objects := $(patsubst %,obj/%.o,$(objects)) + +#################### +### supergameboy ### +#################### + +obj/supergameboy.o: supergameboy.cpp *.cpp *.hpp $(call rwildcard,interface/) + +################### +### libgambatte ### +################### + +obj/bitmap_font.o: libgambatte/src/bitmap_font.cpp $(call rwildcard,common/) $(call rwildcard,libgambatte/) +obj/colorconversion.o: libgambatte/src/colorconversion.cpp $(call rwildcard,common/) $(call rwildcard,libgambatte/) +obj/cpu.o: libgambatte/src/cpu.cpp $(call rwildcard,common/) $(call rwildcard,libgambatte/) +obj/gambatte.o: libgambatte/src/gambatte.cpp $(call rwildcard,common/) $(call rwildcard,libgambatte/) +obj/initstate.o: libgambatte/src/initstate.cpp $(call rwildcard,common/) $(call rwildcard,libgambatte/) +obj/interrupter.o: libgambatte/src/interrupter.cpp $(call rwildcard,common/) $(call rwildcard,libgambatte/) +obj/memory.o: libgambatte/src/memory.cpp $(call rwildcard,common/) $(call rwildcard,libgambatte/) +obj/rtc.o: libgambatte/src/rtc.cpp $(call rwildcard,common/) $(call rwildcard,libgambatte/) +obj/sound.o: libgambatte/src/sound.cpp $(call rwildcard,common/) $(call rwildcard,libgambatte/) +obj/state_osd_elements.o: libgambatte/src/state_osd_elements.cpp $(call rwildcard,common/) $(call rwildcard,libgambatte/) +obj/statesaver.o: libgambatte/src/statesaver.cpp $(call rwildcard,common/) $(call rwildcard,libgambatte/) +obj/video.o: libgambatte/src/video.cpp $(call rwildcard,common/) $(call rwildcard,libgambatte/) + +obj/channel1.o: libgambatte/src/sound/channel1.cpp $(call rwildcard,common/) $(call rwildcard,libgambatte/) +obj/channel2.o: libgambatte/src/sound/channel2.cpp $(call rwildcard,common/) $(call rwildcard,libgambatte/) +obj/channel3.o: libgambatte/src/sound/channel3.cpp $(call rwildcard,common/) $(call rwildcard,libgambatte/) +obj/channel4.o: libgambatte/src/sound/channel4.cpp $(call rwildcard,common/) $(call rwildcard,libgambatte/) +obj/duty_unit.o: libgambatte/src/sound/duty_unit.cpp $(call rwildcard,common/) $(call rwildcard,libgambatte/) +obj/envelope_unit.o: libgambatte/src/sound/envelope_unit.cpp $(call rwildcard,common/) $(call rwildcard,libgambatte/) +obj/length_counter.o: libgambatte/src/sound/length_counter.cpp $(call rwildcard,common/) $(call rwildcard,libgambatte/) + +obj/basic_add_event.o: libgambatte/src/video/basic_add_event.cpp $(call rwildcard,common/) $(call rwildcard,libgambatte/) +obj/break_event.o: libgambatte/src/video/break_event.cpp $(call rwildcard,common/) $(call rwildcard,libgambatte/) +obj/irq_event.o: libgambatte/src/video/irq_event.cpp $(call rwildcard,common/) $(call rwildcard,libgambatte/) +obj/ly_counter.o: libgambatte/src/video/ly_counter.cpp $(call rwildcard,common/) $(call rwildcard,libgambatte/) +obj/lyc_irq.o: libgambatte/src/video/lyc_irq.cpp $(call rwildcard,common/) $(call rwildcard,libgambatte/) +obj/m3_extra_cycles.o: libgambatte/src/video/m3_extra_cycles.cpp $(call rwildcard,common/) $(call rwildcard,libgambatte/) +obj/mode3_event.o: libgambatte/src/video/mode3_event.cpp $(call rwildcard,common/) $(call rwildcard,libgambatte/) +obj/mode0_irq.o: libgambatte/src/video/mode0_irq.cpp $(call rwildcard,common/) $(call rwildcard,libgambatte/) +obj/mode1_irq.o: libgambatte/src/video/mode1_irq.cpp $(call rwildcard,common/) $(call rwildcard,libgambatte/) +obj/mode2_irq.o: libgambatte/src/video/mode2_irq.cpp $(call rwildcard,common/) $(call rwildcard,libgambatte/) +obj/sc_reader.o: libgambatte/src/video/sc_reader.cpp $(call rwildcard,common/) $(call rwildcard,libgambatte/) +obj/scx_reader.o: libgambatte/src/video/scx_reader.cpp $(call rwildcard,common/) $(call rwildcard,libgambatte/) +obj/sprite_mapper.o: libgambatte/src/video/sprite_mapper.cpp $(call rwildcard,common/) $(call rwildcard,libgambatte/) +obj/we_master_checker.o: libgambatte/src/video/we_master_checker.cpp $(call rwildcard,common/) $(call rwildcard,libgambatte/) +obj/we.o: libgambatte/src/video/we.cpp $(call rwildcard,common/) $(call rwildcard,libgambatte/) +obj/wx_reader.o: libgambatte/src/video/wx_reader.cpp $(call rwildcard,common/) $(call rwildcard,libgambatte/) +obj/wy.o: libgambatte/src/video/wy.cpp $(call rwildcard,common/) $(call rwildcard,libgambatte/) + +obj/catrom2x.o: libgambatte/src/video/filters/catrom2x.cpp $(call rwildcard,common/) $(call rwildcard,libgambatte/) +obj/catrom3x.o: libgambatte/src/video/filters/catrom3x.cpp $(call rwildcard,common/) $(call rwildcard,libgambatte/) +obj/kreed2xsai.o: libgambatte/src/video/filters/kreed2xsai.cpp $(call rwildcard,common/) $(call rwildcard,libgambatte/) +obj/maxsthq2x.o: libgambatte/src/video/filters/maxsthq2x.cpp $(call rwildcard,common/) $(call rwildcard,libgambatte/) +obj/maxsthq3x.o: libgambatte/src/video/filters/maxsthq3x.cpp $(call rwildcard,common/) $(call rwildcard,libgambatte/) + +obj/file.o: libgambatte/src/file/file.cpp $(call rwildcard,common/) $(call rwildcard,libgambatte/) + +############### +### targets ### +############### + +build: $(objects) +ifeq ($(platform),win) + $(cpp) $(link) -o supergameboy.dll -shared -Wl,--out-implib,libsupergameboy.a $(objects) $(qtlib) +else ifeq ($(platform),osx) + ar rcs libsupergameboy.a $(objects) + $(cpp) $(link) -o libsupergameboy.dylib -shared -dynamiclib $(objects) $(qtlib) +else + ar rcs libsupergameboy.a $(objects) + $(cpp) $(link) -o libsupergameboy.so -shared -Wl,-soname,libsupergameboy.so.1 $(objects) $(qtlib) +endif + +install: +ifeq ($(platform),osx) + cp libsupergameboy.dylib /usr/local/lib/libsupergameboy.dylib +else + install -D -m 755 libsupergameboy.a $(DESTDIR)$(prefix)/lib + install -D -m 755 libsupergameboy.so $(DESTDIR)$(prefix)/lib + ldconfig -n $(DESTDIR)$(prefix)/lib +endif + +clean: + -@$(call delete,obj/*.o) + -@$(call delete,libsupergameboy.a) + -@$(call delete,supergameboy.dll) + -@$(call delete,libsupergameboy.dylib) + -@$(call delete,libsupergameboy.so) diff --git a/supergameboy/cc.bat b/supergameboy/cc.bat new file mode 100644 index 00000000..7e2f36ad --- /dev/null +++ b/supergameboy/cc.bat @@ -0,0 +1,2 @@ +@mingw32-make +@pause diff --git a/supergameboy/clean.bat b/supergameboy/clean.bat new file mode 100644 index 00000000..d8bb7e0b --- /dev/null +++ b/supergameboy/clean.bat @@ -0,0 +1 @@ +@mingw32-make clean diff --git a/supergameboy/common/adaptivesleep.cpp b/supergameboy/common/adaptivesleep.cpp new file mode 100644 index 00000000..48c40979 --- /dev/null +++ b/supergameboy/common/adaptivesleep.cpp @@ -0,0 +1,56 @@ +/*************************************************************************** + * Copyright (C) 2008 by Sindre AamÃ¥s * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#include "adaptivesleep.h" + +usec_t AdaptiveSleep::sleepUntil(usec_t base, usec_t inc) { + usec_t now = getusecs(); + usec_t diff = now - base; + + if (diff >= inc) + return diff - inc; + + diff = inc - diff; + + if (diff > oversleep + oversleepVar) { + diff -= oversleep + oversleepVar; + usecsleep(diff); + const usec_t ideal = now + diff; + now = getusecs(); + + { + usec_t curOversleep = now - ideal; + + if (negate(curOversleep) < curOversleep) + curOversleep = 0; + + oversleepVar = (oversleepVar * 15 + (curOversleep < oversleep ? oversleep - curOversleep : curOversleep - oversleep)) >> 4; + oversleep = (oversleep * 15 + curOversleep) >> 4; + } + + noSleep = 60; + } else if (--noSleep == 0) { + noSleep = 60; + oversleep = oversleepVar = 0; + } + + while (now - base < inc) + now = getusecs(); + + return 0; +} diff --git a/supergameboy/common/adaptivesleep.h b/supergameboy/common/adaptivesleep.h new file mode 100644 index 00000000..de2010a0 --- /dev/null +++ b/supergameboy/common/adaptivesleep.h @@ -0,0 +1,34 @@ +/*************************************************************************** + * Copyright (C) 2008 by Sindre AamÃ¥s * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#ifndef ADAPTIVE_SLEEP_H +#define ADAPTIVE_SLEEP_H + +#include "usec.h" + +class AdaptiveSleep { + usec_t oversleep; + usec_t oversleepVar; + unsigned noSleep; + +public: + AdaptiveSleep() : oversleep(0), oversleepVar(0), noSleep(60) {} + usec_t sleepUntil(usec_t base, usec_t inc); +}; + +#endif diff --git a/supergameboy/common/array.h b/supergameboy/common/array.h new file mode 100644 index 00000000..f01806ea --- /dev/null +++ b/supergameboy/common/array.h @@ -0,0 +1,40 @@ +/*************************************************************************** + * Copyright (C) 2008 by Sindre Aam�s * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#ifndef ARRAY_H +#define ARRAY_H + +#include + +template +class Array { + T *a; + std::size_t sz; + + Array(const Array &ar); + +public: + Array(const std::size_t size = 0) : a(size ? new T[size] : 0), sz(size) {} + ~Array() { delete []a; } + void reset(const std::size_t size) { delete []a; a = size ? new T[size] : 0; sz = size; } + std::size_t size() const { return sz; } + operator T*() { return a; } + operator const T*() const { return a; } +}; + +#endif diff --git a/supergameboy/common/rateest.cpp b/supergameboy/common/rateest.cpp new file mode 100644 index 00000000..c1feba6c --- /dev/null +++ b/supergameboy/common/rateest.cpp @@ -0,0 +1,96 @@ +/*************************************************************************** + * Copyright (C) 2008 by Sindre AamÃ¥s * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#include "rateest.h" +#include + +void RateEst::SumQueue::reset() { + q.clear(); + samples_ = usecs_ = 0; +} + +void RateEst::SumQueue::push(const long samples, const usec_t usecs) { + q.push_back(pair_t(samples, usecs)); + samples_ += samples; + usecs_ += usecs; +} + +void RateEst::SumQueue::pop() { + const pair_t &f = q.front(); + samples_ -= f.first; + usecs_ -= f.second; + q.pop_front(); +} + +static usec_t sampleUsecs(long samples, long rate) { + return static_cast((samples * 1000000.0f) / (rate ? rate : 1) + 0.5f); +} + +static long limit(long est, const long reference) { + if (est > reference + (reference >> 6)) + est = reference + (reference >> 6); + else if (est < reference - (reference >> 6)) + est = reference - (reference >> 6); + + return est; +} + +void RateEst::init(long srate, long reference, const long maxSamplePeriod) { + maxPeriod = sampleUsecs(maxSamplePeriod, reference); + + srate <<= UPSHIFT; + reference <<= UPSHIFT; + + this->srate.est = limit(srate, reference); + this->srate.var = srate >> 12; + last = 0; + this->reference = reference; + samples = ((this->srate.est >> UPSHIFT) * 12) << 5; + usecs = 12000000 << 5; + sumq.reset(); +} + +void RateEst::feed(long samplesIn, const usec_t now) { + usec_t usecsIn = now - last; + + if (last && usecsIn < maxPeriod) { + sumq.push(samplesIn, usecsIn); + + while ((usecsIn = sumq.usecs()) > 100000) { + samplesIn = sumq.samples(); + sumq.pop(); + + if (std::abs(static_cast(samplesIn * (1000000.0f * UP) / usecsIn) - reference) < reference >> 1) { + samples += (samplesIn - sumq.samples()) << 5; + usecs += (usecsIn - sumq.usecs()) << 5; + + long est = static_cast(samples * (1000000.0f * UP) / usecs + 0.5f); + est = limit((srate.est * 31 + est + 16) >> 5, reference); + srate.var = (srate.var * 15 + std::abs(est - srate.est) + 8) >> 4; + srate.est = est; + + if (usecs > 16000000 << 5) { + samples = (samples * 3 + 2) >> 2; + usecs = (usecs * 3 + 2) >> 2; + } + } + } + } + + last = now; +} diff --git a/supergameboy/common/rateest.h b/supergameboy/common/rateest.h new file mode 100644 index 00000000..3e109541 --- /dev/null +++ b/supergameboy/common/rateest.h @@ -0,0 +1,73 @@ +/*************************************************************************** + * Copyright (C) 2008 by Sindre AamÃ¥s * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#ifndef RATEEST_H +#define RATEEST_H + +#include "usec.h" +#include +#include + +class RateEst { +public: + struct Result { + long est; + long var; + }; + +private: + class SumQueue { + typedef std::pair pair_t; + typedef std::deque q_t; + + q_t q; + long samples_; + usec_t usecs_; + + public: + SumQueue() : samples_(0), usecs_(0) {} + void reset(); + long samples() const { return samples_; } + usec_t usecs() const { return usecs_; } + void push(long samples, usec_t usecs); + void pop(); + }; + + enum { UPSHIFT = 5 }; + enum { UP = 1 << UPSHIFT }; + + Result srate; + SumQueue sumq; + usec_t last; + usec_t usecs; + usec_t maxPeriod; + long reference; + long samples; + +public: + RateEst(long srate = 0) { init(srate); } + RateEst(long srate, long reference) { init(srate, reference); } + void init(long srate) { init(srate, srate); } + void init(long srate, long reference) { init(srate, reference, reference); } + void init(long srate, long reference, long maxSamplePeriod); + void reset() { last = 0; } + void feed(long samples, usec_t usecs = getusecs()); + const Result result() const { const Result res = { (srate.est + UP / 2) >> UPSHIFT, (srate.var + UP / 2) >> UPSHIFT }; return res; } +}; + +#endif diff --git a/supergameboy/common/resample/blackmansinc.h b/supergameboy/common/resample/blackmansinc.h new file mode 100644 index 00000000..86578239 --- /dev/null +++ b/supergameboy/common/resample/blackmansinc.h @@ -0,0 +1,100 @@ +/*************************************************************************** + * Copyright (C) 2008 by Sindre AamÃ¥s * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#ifndef BLACKMANSINC_H +#define BLACKMANSINC_H + +#include "convoluter.h" +#include "subresampler.h" +#include "makesinckernel.h" +#include "cic4.h" +#include +#include + +template +class BlackmanSinc : public SubResampler { + PolyPhaseConvoluter convoluters[channels]; + short *kernel; + + static double blackmanWin(const long i, const long M) { + static const double PI = 3.14159265358979323846; + return 0.42 - 0.5 * std::cos(2 * PI * i / M) + 0.08 * std::cos(4 * PI * i / M); + } + + void init(unsigned div, unsigned phaseLen, double fc); + +public: + enum { MUL = phases }; + + typedef Cic4 Cic; + static float cicLimit() { return 4.7f; } + + class RollOff { + static unsigned toTaps(const float rollOffWidth) { + static const float widthTimesTaps = 4.5f; + return std::ceil(widthTimesTaps / rollOffWidth); + } + + static float toFc(const float rollOffStart, const int taps) { + static const float startToFcDeltaTimesTaps = 1.69f; + return startToFcDeltaTimesTaps / taps + rollOffStart; + } + + public: + const unsigned taps; + const float fc; + + RollOff(float rollOffStart, float rollOffWidth) : taps(toTaps(rollOffWidth)), fc(toFc(rollOffStart, taps)) {} + }; + + BlackmanSinc(unsigned div, unsigned phaseLen, double fc) { init(div, phaseLen, fc); } + BlackmanSinc(unsigned div, RollOff ro) { init(div, ro.taps, ro.fc); } + ~BlackmanSinc() { delete[] kernel; } + std::size_t resample(short *out, const short *in, std::size_t inlen); + void adjustDiv(unsigned div); + unsigned mul() const { return MUL; } + unsigned div() const { return convoluters[0].div(); } +}; + +template +void BlackmanSinc::init(const unsigned div, const unsigned phaseLen, const double fc) { + kernel = new short[phaseLen * phases]; + + makeSincKernel(kernel, phases, phaseLen, fc, blackmanWin); + + for (unsigned i = 0; i < channels; ++i) + convoluters[i].reset(kernel, phaseLen, div); +} + +template +std::size_t BlackmanSinc::resample(short *const out, const short *const in, const std::size_t inlen) { + std::size_t samplesOut; + + for (unsigned i = 0; i < channels; ++i) + samplesOut = convoluters[i].filter(out + i, in + i, inlen); + + return samplesOut; +} + +template +void BlackmanSinc::adjustDiv(const unsigned div) { + for (unsigned i = 0; i < channels; ++i) + convoluters[i].adjustDiv(div); +} + +#endif diff --git a/supergameboy/common/resample/chainresampler.cpp b/supergameboy/common/resample/chainresampler.cpp new file mode 100644 index 00000000..6836a05b --- /dev/null +++ b/supergameboy/common/resample/chainresampler.cpp @@ -0,0 +1,118 @@ +/*************************************************************************** + * Copyright (C) 2008 by Sindre AamÃ¥s * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#include "chainresampler.h" + +float ChainResampler::get2ChainMidRatio(const float ratio, const float rollOff) { + return std::sqrt(0.5f * rollOff * ratio) + 1; +} + +float ChainResampler::get2ChainCost(const float ratio, const float rollOff, const float midRatio) { + return midRatio * ratio / ((midRatio - 1) * 2) + midRatio / rollOff; +} + +float ChainResampler::get3ChainRatio1(float ratio1, const float rollOff, const float ratio) { + for (unsigned n = 8; n--;) { + ratio1 = std::sqrt(ratio - ratio / get3ChainRatio2(ratio1, rollOff)) + 1; + } + + return ratio1; +} + +float ChainResampler::get3ChainCost(const float ratio, const float rollOff, const float ratio1, const float ratio2) { + return ratio1 * ratio / ((ratio1 - 1) * 2) + ratio2 * ratio1 / ((ratio2 - 1) * 2) + ratio2 / rollOff; +} + +std::size_t ChainResampler::reallocateBuffer() { + std::size_t bufSz[2] = { 0, 0 }; + std::size_t inSz = periodSize; + int i = -1; + + for (list_t::iterator it = list.begin(); it != list.end(); ++it) { + inSz = (inSz * (*it)->mul() - 1) / (*it)->div() + 1; + + ++i; + + if (inSz > bufSz[i&1]) + bufSz[i&1] = inSz; + } + + if (inSz >= bufSz[i&1]) + bufSz[i&1] = 0; + + if (bufferSize < bufSz[0] + bufSz[1]) { + delete[] buffer; + buffer = (bufferSize = bufSz[0] + bufSz[1]) ? new short[bufferSize * channels] : NULL; + } + + buffer2 = bufSz[1] ? buffer + bufSz[0] * channels : NULL; + + return (maxOut_ = inSz); +} + +void ChainResampler::adjustRate(const long inRate, const long outRate) { + unsigned long mul, div; + + exactRatio(mul, div); + + bigSinc->adjustDiv(static_cast(inRate) * mul / (static_cast(div / bigSinc->div()) * outRate) + 0.5); + + reallocateBuffer(); + setRate(inRate, outRate); +} + +void ChainResampler::exactRatio(unsigned long &mul, unsigned long &div) const { + mul = 1; + div = 1; + + for (list_t::const_iterator it = list.begin(); it != list.end(); ++it) { + mul *= (*it)->mul(); + div *= (*it)->div(); + } +} + +std::size_t ChainResampler::resample(short *const out, const short *const in, std::size_t inlen) { + assert(inlen <= periodSize); + + short *const buf = buffer != buffer2 ? buffer : out; + short *const buf2 = buffer2 ? buffer2 : out; + + const short *inbuf = in; + short *outbuf = NULL; + + for (list_t::iterator it = list.begin(); it != list.end(); ++it) { + outbuf = ++list_t::iterator(it) == list.end() ? out : (inbuf == buf ? buf2 : buf); + inlen = (*it)->resample(outbuf, inbuf, inlen); + inbuf = outbuf; + } + + return inlen; +} + +void ChainResampler::uninit() { + delete[] buffer; + buffer2 = buffer = NULL; + bufferSize = 0; + periodSize = 0; + bigSinc = NULL; + + for (list_t::iterator it = list.begin(); it != list.end(); ++it) + delete *it; + + list.clear(); +} diff --git a/supergameboy/common/resample/chainresampler.h b/supergameboy/common/resample/chainresampler.h new file mode 100644 index 00000000..aeb52d6c --- /dev/null +++ b/supergameboy/common/resample/chainresampler.h @@ -0,0 +1,189 @@ +/*************************************************************************** + * Copyright (C) 2008 by Sindre AamÃ¥s * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#ifndef CHAINRESAMPLER_H +#define CHAINRESAMPLER_H + +#include +#include +#include +#include +#include +#include "subresampler.h" +#include "resampler.h" +#include "upsampler.h" + +class ChainResampler : public Resampler { + enum { channels = 2 }; + + typedef std::list list_t; + + list_t list; + SubResampler *bigSinc; + short *buffer; + short *buffer2; + std::size_t bufferSize; + std::size_t periodSize; + std::size_t maxOut_; + + static float get1ChainCost(const float ratio, const float rollOff) { + return ratio / rollOff; + } + + static float get2ChainMidRatio(float ratio, float rollOff); + static float get2ChainCost(float ratio, float rollOff, float midRatio); + + static float get3ChainRatio2(const float ratio1, const float rollOff) { + return get2ChainMidRatio(ratio1, rollOff); + } + + static float get3ChainRatio1(float ratio1, float rollOff, float ratio); + static float get3ChainCost(float ratio, float rollOff, float ratio1, float ratio2); + + template class Sinc> + std::size_t downinit(long inRate, long outRate, std::size_t periodSize); + + std::size_t reallocateBuffer(); + + template class Sinc> + std::size_t upinit(long inRate, long outRate, std::size_t periodSize); + +public: + ChainResampler() : bigSinc(NULL), buffer(NULL), buffer2(NULL), bufferSize(0), periodSize(0) {} + ~ChainResampler() { uninit(); } + + void adjustRate(long inRate, long outRate); + void exactRatio(unsigned long &mul, unsigned long &div) const; + + template class Sinc> + std::size_t init(long inRate, long outRate, std::size_t periodSize); + std::size_t maxOut(std::size_t /*inlen*/) const { return maxOut_; } + std::size_t resample(short *out, const short *in, std::size_t inlen); + void uninit(); +}; + +template class Sinc> +std::size_t ChainResampler::init(const long inRate, const long outRate, const std::size_t periodSize) { + setRate(inRate, outRate); + + if (outRate > inRate) + return upinit(inRate, outRate, periodSize); + else + return downinit(inRate, outRate, periodSize); +} + +template class Sinc> +std::size_t ChainResampler::downinit(const long inRate, const long outRate, const std::size_t periodSize) { + typedef Sinc BigSinc; + typedef Sinc SmallSinc; + + uninit(); + this->periodSize = periodSize; + + + const float rollOff = std::max((outRate - 36000.0f + outRate - 40000.0f) / outRate, 0.2f); + + double ratio = static_cast(inRate) / outRate; + + while (ratio >= BigSinc::cicLimit() * 2) { + const int div = std::min(static_cast(ratio / BigSinc::cicLimit()), BigSinc::Cic::MAX_DIV); + + list.push_back(new typename BigSinc::Cic(div)); + ratio /= div; + } + + { + int div_2c = ratio * SmallSinc::MUL / get2ChainMidRatio(ratio, rollOff) + 0.5f; + double ratio_2c = ratio * SmallSinc::MUL / div_2c; + float cost_2c = get2ChainCost(ratio, rollOff, ratio_2c); + + if (cost_2c < get1ChainCost(ratio, rollOff)) { + const int div1_3c = ratio * SmallSinc::MUL / get3ChainRatio1(ratio_2c, rollOff, ratio) + 0.5f; + const double ratio1_3c = ratio * SmallSinc::MUL / div1_3c; + const int div2_3c = ratio1_3c * SmallSinc::MUL / get3ChainRatio2(ratio1_3c, rollOff) + 0.5f; + const double ratio2_3c = ratio1_3c * SmallSinc::MUL / div2_3c; + + if (get3ChainCost(ratio, rollOff, ratio1_3c, ratio2_3c) < cost_2c) { + list.push_back(new SmallSinc(div1_3c, typename SmallSinc::RollOff(0.5f / ratio, (ratio1_3c - 1) / ratio))); + ratio = ratio1_3c; + div_2c = div2_3c; + ratio_2c = ratio2_3c; + } + + list.push_back(new SmallSinc(div_2c, typename SmallSinc::RollOff(0.5f / ratio, (ratio_2c - 1) / ratio))); + ratio = ratio_2c; + } + } + + list.push_back(bigSinc = new BigSinc(BigSinc::MUL * ratio + 0.5, + typename BigSinc::RollOff(0.5f * (1 + std::max((outRate - 40000.0f) / outRate, 0.0f) - rollOff) / ratio, 0.5f * rollOff / ratio))); + + return reallocateBuffer(); +} + +template class Sinc> +std::size_t ChainResampler::upinit(const long inRate, const long outRate, const std::size_t periodSize) { + typedef Sinc BigSinc; + typedef Sinc SmallSinc; + + uninit(); + this->periodSize = periodSize; + + const float rollOff = std::max((inRate - 36000.0f) / inRate, 0.2f); + + double ratio = static_cast(outRate) / inRate; + + // Spectral images above 20 kHz assumed inaudible + { + const int div = outRate / std::max(inRate, 40000l); + + if (div >= 2) { + list.push_front(new Upsampler(div)); + ratio /= div; + } + } + + { + int div_2c = get2ChainMidRatio(ratio, rollOff) * SmallSinc::MUL / ratio + 0.5f; + double ratio_2c = ratio * div_2c / SmallSinc::MUL; + float cost_2c = get2ChainCost(ratio, rollOff, ratio_2c); + + if (cost_2c < get1ChainCost(ratio, rollOff)) { + const int div1_3c = get3ChainRatio1(ratio_2c, rollOff, ratio) * SmallSinc::MUL / ratio + 0.5f; + const double ratio1_3c = ratio * div1_3c / SmallSinc::MUL; + const int div2_3c = get3ChainRatio2(ratio1_3c, rollOff) * SmallSinc::MUL / ratio1_3c + 0.5f; + const double ratio2_3c = ratio1_3c * div2_3c / SmallSinc::MUL; + + if (get3ChainCost(ratio, rollOff, ratio1_3c, ratio2_3c) < cost_2c) { + list.push_front(new SmallSinc(div1_3c, typename SmallSinc::RollOff(0.5f / ratio1_3c, (ratio1_3c - 1) / ratio1_3c))); + ratio = ratio1_3c; + div_2c = div2_3c; + ratio_2c = ratio2_3c; + } + + list.push_front(new SmallSinc(div_2c, typename SmallSinc::RollOff(0.5f / ratio_2c, (ratio_2c - 1) / ratio_2c))); + ratio = ratio_2c; + } + } + + list.push_front(bigSinc = new BigSinc(BigSinc::MUL / ratio + 0.5, typename BigSinc::RollOff(0.5f * (1 - rollOff), 0.5f * rollOff))); + + return reallocateBuffer(); +} + +#endif diff --git a/supergameboy/common/resample/cic2.h b/supergameboy/common/resample/cic2.h new file mode 100644 index 00000000..1f12bfc9 --- /dev/null +++ b/supergameboy/common/resample/cic2.h @@ -0,0 +1,198 @@ +/*************************************************************************** + * Copyright (C) 2008 by Sindre AamÃ¥s * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#ifndef CIC2_H +#define CIC2_H + +#include "subresampler.h" + +template +class Cic2Core { +// enum { BUFLEN = 64 }; +// unsigned long buf[BUFLEN]; + unsigned long sum1; + unsigned long sum2; + unsigned long prev1; + unsigned long prev2; + unsigned div_; + unsigned nextdivn; +// unsigned bufpos; + +public: + Cic2Core(const unsigned div = 2) { + reset(div); + } + + unsigned div() const { return div_; } + std::size_t filter(short *out, const short *in, std::size_t inlen); + void reset(unsigned div); +}; + +template +void Cic2Core::reset(const unsigned div) { + sum2 = sum1 = 0; + prev2 = prev1 = 0; + this->div_ = div; + nextdivn = div; +// bufpos = div - 1; +} + +template +std::size_t Cic2Core::filter(short *out, const short *const in, std::size_t inlen) { +// const std::size_t produced = (inlen + div_ - (bufpos + 1)) / div_; + const std::size_t produced = (inlen + div_ - nextdivn) / div_; + const long mul = 0x10000 / (div_ * div_); // trouble if div is too large, may be better to only support power of 2 div + const short *s = in; + + /*unsigned long sm1 = sum1; + unsigned long sm2 = sum2; + + while (inlen >> 2) { + unsigned n = (inlen < BUFLEN ? inlen >> 2 : BUFLEN >> 2); + const unsigned end = n * 4; + unsigned i = 0; + + do { + unsigned long s1 = sm1 += static_cast(*s); + s += channels; + sm1 += static_cast(*s); + s += channels; + buf[i++] = sm2 += s1; + buf[i++] = sm2 += sm1; + s1 = sm1 += static_cast(*s); + s += channels; + sm1 += static_cast(*s); + s += channels; + buf[i++] = sm2 += s1; + buf[i++] = sm2 += sm1; + } while (--n); + + while (bufpos < end) { + const unsigned long out2 = buf[bufpos] - prev2; + prev2 = buf[bufpos]; + bufpos += div_; + + *out = static_cast(out2 - prev1) * mul / 0x10000; + prev1 = out2; + out += channels; + } + + bufpos -= end; + inlen -= end; + } + + if (inlen) { + unsigned n = inlen; + unsigned i = 0; + + do { + sm1 += static_cast(*s); + s += channels; + buf[i++] = sm2 += sm1; + } while (--n); + + while (bufpos < inlen) { + const unsigned long out2 = buf[bufpos] - prev2; + prev2 = buf[bufpos]; + bufpos += div_; + + *out = static_cast(out2 - prev1) * mul / 0x10000; + prev1 = out2; + out += channels; + } + + bufpos -= inlen; + } + + sum1 = sm1; + sum2 = sm2;*/ + + unsigned long sm1 = sum1; + unsigned long sm2 = sum2; + + if (inlen >= nextdivn) { + unsigned divn = nextdivn; + std::size_t n = produced; + + do { + do { + sm1 += static_cast(*s); + s += channels; + sm2 += sm1; + } while (--divn); + + const unsigned long out2 = sm2 - prev2; + prev2 = sm2; + + *out = static_cast(out2 - prev1) * mul / 0x10000; + prev1 = out2; + out += channels; + + divn = div_; + } while (--n); + + nextdivn = div_; + } + + { + unsigned divn = (in + inlen * channels - s) / channels; + nextdivn -= divn; + + while (divn--) { + sm1 += static_cast(*s); + s += channels; + sm2 += sm1; + } + } + + sum1 = sm1; + sum2 = sm2; + + return produced; +} + +template +class Cic2 : public SubResampler { + Cic2Core cics[channels]; + +public: + enum { MAX_DIV = 64 }; + Cic2(unsigned div); + std::size_t resample(short *out, const short *in, std::size_t inlen); + unsigned mul() const { return 1; } + unsigned div() const { return cics[0].div(); } +}; + +template +Cic2::Cic2(const unsigned div) { + for (unsigned i = 0; i < channels; ++i) + cics[i].reset(div); +} + +template +std::size_t Cic2::resample(short *const out, const short *const in, const std::size_t inlen) { + std::size_t samplesOut; + + for (unsigned i = 0; i < channels; ++i) { + samplesOut = cics[i].filter(out + i, in + i, inlen); + } + + return samplesOut; +} + +#endif diff --git a/supergameboy/common/resample/cic3.h b/supergameboy/common/resample/cic3.h new file mode 100644 index 00000000..85b9dcee --- /dev/null +++ b/supergameboy/common/resample/cic3.h @@ -0,0 +1,382 @@ +/*************************************************************************** + * Copyright (C) 2008 by Sindre AamÃ¥s * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#ifndef CIC3_H +#define CIC3_H + +#include "subresampler.h" + +template +class Cic3Core { +// enum { BUFLEN = 64 }; +// unsigned long buf[BUFLEN]; + unsigned long sum1; + unsigned long sum2; + unsigned long sum3; + unsigned long prev1; + unsigned long prev2; + unsigned long prev3; + unsigned div_; + unsigned nextdivn; +// unsigned bufpos; + +public: + Cic3Core(const unsigned div = 1) { + reset(div); + } + + unsigned div() const { return div_; } + std::size_t filter(short *out, const short *in, std::size_t inlen); + void reset(unsigned div); +}; + +template +void Cic3Core::reset(const unsigned div) { + sum3 = sum2 = sum1 = 0; + prev3 = prev2 = prev1 = 0; + this->div_ = div; + nextdivn = div; +// bufpos = div - 1; +} + +template +std::size_t Cic3Core::filter(short *out, const short *const in, std::size_t inlen) { +// const std::size_t produced = (inlen + div_ - (bufpos + 1)) / div_; + const std::size_t produced = (inlen + div_ - nextdivn) / div_; + const long mul = 0x10000 / (div_ * div_ * div_); // trouble if div is too large, may be better to only support power of 2 div + const short *s = in; + + /*unsigned long sm1 = sum1; + unsigned long sm2 = sum2; + unsigned long sm3 = sum3; + + while (inlen >> 1) { + unsigned n = (inlen < BUFLEN ? inlen >> 1 : BUFLEN >> 1); + const unsigned end = n * 2; + unsigned i = 0; + + do { + unsigned long s1 = sm1 += static_cast(*s); + s += channels; + sm1 += static_cast(*s); + s += channels; + unsigned long s2 = sm2 += s1; + sm2 += sm1; + buf[i++] = sm3 += s2; + buf[i++] = sm3 += sm2; + } while (--n); + + while (bufpos < end) { + const unsigned long out3 = buf[bufpos] - prev3; + prev3 = buf[bufpos]; + bufpos += div_; + + const unsigned long out2 = out3 - prev2; + prev2 = out3; + + *out = static_cast(out2 - prev1) * mul / 0x10000; + prev1 = out2; + out += channels; + } + + bufpos -= end; + inlen -= end; + } + + if (inlen) { + unsigned n = inlen; + unsigned i = 0; + + do { + sm1 += static_cast(*s); + s += channels; + sm2 += sm1; + buf[i++] = sm3 += sm2; + } while (--n); + + while (bufpos < inlen) { + const unsigned long out3 = buf[bufpos] - prev3; + prev3 = buf[bufpos]; + bufpos += div_; + + const unsigned long out2 = out3 - prev2; + prev2 = out3; + + *out = static_cast(out2 - prev1) * mul / 0x10000; + prev1 = out2; + out += channels; + } + + bufpos -= inlen; + } + + sum1 = sm1; + sum2 = sm2; + sum3 = sm3;*/ + + + unsigned long sm1 = sum1; + unsigned long sm2 = sum2; + unsigned long sm3 = sum3; + + if (inlen >= nextdivn) { + unsigned divn = nextdivn; + std::size_t n = produced; + + do { + do { + sm1 += static_cast(*s); + sm2 += sm1; + sm3 += sm2; + s += channels; + } while (--divn); + + const unsigned long out3 = sm3 - prev3; + prev3 = sm3; + + const unsigned long out2 = out3 - prev2; + prev2 = out3; + + *out = static_cast(out2 - prev1) * mul / 0x10000; + prev1 = out2; + out += channels; + + divn = div_; + } while (--n); + + nextdivn = div_; + } + + { + unsigned divn = (in + inlen * channels - s) / channels; + nextdivn -= divn; + + while (divn--) { + sm1 += static_cast(*s); + sm2 += sm1; + sm3 += sm2; + s += channels; + } + } + + sum1 = sm1; + sum2 = sm2; + sum3 = sm3; + + return produced; +} + +/*template +class Cic3EvenOddCore { + unsigned long sum1; + unsigned long sum2; + unsigned long sum3; + unsigned long prev1; + unsigned long prev2; + unsigned long prev3; + unsigned div_; + unsigned nextdivn; + + static int getMul(unsigned div) { + return 0x10000 / (div * div * div); // trouble if div is too large, may be better to only support power of 2 div + } + + void filterEven(short *out, const short *s, std::size_t n); + void filterOdd(short *out, const short *s, std::size_t n); + +public: + Cic3EvenOddCore(const unsigned div = 2) { + reset(div); + } + + unsigned div() const { return div_; } + std::size_t filter(short *out, const short *in, std::size_t inlen); + void reset(unsigned div); +}; + +template +void Cic3EvenOddCore::reset(const unsigned div) { + sum3 = sum2 = sum1 = 0; + prev3 = prev2 = prev1 = 0; + this->div_ = div; + nextdivn = div; +} + +template +void Cic3EvenOddCore::filterEven(short *out, const short *s, std::size_t n) { + const int mul = getMul(div_); + unsigned long sm1 = sum1; + unsigned long sm2 = sum2; + unsigned long sm3 = sum3; + + while (n--) { + { + unsigned sn = div_ >> 1; + + do { + unsigned long s1 = sm1 += static_cast(*s); + s += channels; + sm1 += static_cast(*s); + s += channels; + unsigned long s2 = sm2 += s1; + sm2 += sm1; + sm3 += s2; + sm3 += sm2; + } while (--sn); + } + + const unsigned long out3 = sm3 - prev3; + prev3 = sm3; + const unsigned long out2 = out3 - prev2; + prev2 = out3; + *out = static_cast(out2 - prev1) * mul / 0x10000; + prev1 = out2; + out += channels; + } + + sum1 = sm1; + sum2 = sm2; + sum3 = sm3; +} + +template +void Cic3EvenOddCore::filterOdd(short *out, const short *s, std::size_t n) { + const int mul = getMul(div_); + unsigned long sm1 = sum1; + unsigned long sm2 = sum2; + unsigned long sm3 = sum3; + + while (n--) { + { + unsigned sn = div_ >> 1; + + do { + unsigned long s1 = sm1 += static_cast(*s); + s += channels; + sm1 += static_cast(*s); + s += channels; + unsigned long s2 = sm2 += s1; + sm2 += sm1; + sm3 += s2; + sm3 += sm2; + } while (--sn); + } + + sm1 += static_cast(*s); + s += channels; + sm2 += sm1; + sm3 += sm2; + + const unsigned long out3 = sm3 - prev3; + prev3 = sm3; + const unsigned long out2 = out3 - prev2; + prev2 = out3; + *out = static_cast(out2 - prev1) * mul / 0x10000; + prev1 = out2; + out += channels; + } + + sum1 = sm1; + sum2 = sm2; + sum3 = sm3; +} + +template +std::size_t Cic3EvenOddCore::filter(short *out, const short *const in, std::size_t inlen) { + short *const outStart = out; + const short *s = in; + + if (inlen >= nextdivn) { + { + { + unsigned divn = nextdivn; + + do { + sum1 += static_cast(*s); + s += channels; + sum2 += sum1; + sum3 += sum2; + } while (--divn); + } + + const unsigned long out3 = sum3 - prev3; + prev3 = sum3; + const unsigned long out2 = out3 - prev2; + prev2 = out3; + *out = static_cast(out2 - prev1) * getMul(div_) / 0x10000; + prev1 = out2; + out += channels; + } + + std::size_t n = (inlen - nextdivn) / div_; + + if (div_ & 1) + filterOdd(out, s, n); + else + filterEven(out, s, n); + + s += n * div_ * channels; + out += n * channels; + nextdivn = div_; + } + + { + unsigned divn = inlen - (s - in) / channels; + nextdivn -= divn; + + while (divn--) { + sum1 += static_cast(*s); + s += channels; + sum2 += sum1; + sum3 += sum2; + } + } + + return (out - outStart) / channels; +}*/ + +template +class Cic3 : public SubResampler { + Cic3Core cics[channels]; + +public: + enum { MAX_DIV = 23 }; + Cic3(unsigned div); + std::size_t resample(short *out, const short *in, std::size_t inlen); + unsigned mul() const { return 1; } + unsigned div() const { return cics[0].div(); } +}; + +template +Cic3::Cic3(const unsigned div) { + for (unsigned i = 0; i < channels; ++i) + cics[i].reset(div); +} + +template +std::size_t Cic3::resample(short *const out, const short *const in, const std::size_t inlen) { + std::size_t samplesOut; + + for (unsigned i = 0; i < channels; ++i) { + samplesOut = cics[i].filter(out + i, in + i, inlen); + } + + return samplesOut; +} + +#endif diff --git a/supergameboy/common/resample/cic4.h b/supergameboy/common/resample/cic4.h new file mode 100644 index 00000000..430cb03d --- /dev/null +++ b/supergameboy/common/resample/cic4.h @@ -0,0 +1,237 @@ +/*************************************************************************** + * Copyright (C) 2008 by Sindre AamÃ¥s * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#ifndef CIC4_H +#define CIC4_H + +#include "subresampler.h" + +template +class Cic4Core { + enum { BUFLEN = 64 }; + unsigned long buf[BUFLEN]; + unsigned long sum1; + unsigned long sum2; + unsigned long sum3; + unsigned long sum4; + unsigned long prev1; + unsigned long prev2; + unsigned long prev3; + unsigned long prev4; + unsigned div_; +// unsigned nextdivn; + unsigned bufpos; + +public: + Cic4Core(const unsigned div = 1) { + reset(div); + } + + unsigned div() const { return div_; } + std::size_t filter(short *out, const short *in, std::size_t inlen); + void reset(unsigned div); +}; + +template +void Cic4Core::reset(const unsigned div) { + sum4 = sum3 = sum2 = sum1 = 0; + prev4 = prev3 = prev2 = prev1 = 0; + this->div_ = div; +// nextdivn = div; + bufpos = div - 1; +} + +template +std::size_t Cic4Core::filter(short *out, const short *const in, std::size_t inlen) { + const std::size_t produced = (inlen + div_ - (bufpos + 1)) / div_; +// const std::size_t produced = (inlen + div_ - nextdivn) / div_; + const long mul = 0x10000 / (div_ * div_ * div_ * div_); // trouble if div is too large, may be better to only support power of 2 div + const short *s = in; + + unsigned long sm1 = sum1; + unsigned long sm2 = sum2; + unsigned long sm3 = sum3; + unsigned long sm4 = sum4; + + while (inlen >> 2) { + unsigned n = (inlen < BUFLEN ? inlen >> 2 : BUFLEN >> 2); + const unsigned end = n * 4; + unsigned i = 0; + + do { + unsigned long s1 = sm1 += static_cast(*s); + s += channels; + sm1 += static_cast(*s); + s += channels; + unsigned long s2 = sm2 += s1; + sm2 += sm1; + unsigned long s3 = sm3 += s2; + sm3 += sm2; + buf[i++] = sm4 += s3; + buf[i++] = sm4 += sm3; + s1 = sm1 += static_cast(*s); + s += channels; + sm1 += static_cast(*s); + s += channels; + s2 = sm2 += s1; + sm2 += sm1; + s3 = sm3 += s2; + sm3 += sm2; + buf[i++] = sm4 += s3; + buf[i++] = sm4 += sm3; + } while (--n); + + while (bufpos < end) { + const unsigned long out4 = buf[bufpos] - prev4; + prev4 = buf[bufpos]; + bufpos += div_; + + const unsigned long out3 = out4 - prev3; + prev3 = out4; + const unsigned long out2 = out3 - prev2; + prev2 = out3; + + *out = static_cast(out2 - prev1) * mul / 0x10000; + prev1 = out2; + out += channels; + } + + bufpos -= end; + inlen -= end; + } + + if (inlen) { + unsigned n = inlen; + unsigned i = 0; + + do { + sm1 += static_cast(*s); + s += channels; + sm2 += sm1; + sm3 += sm2; + buf[i++] = sm4 += sm3; + } while (--n); + + while (bufpos < inlen) { + const unsigned long out4 = buf[bufpos] - prev4; + prev4 = buf[bufpos]; + bufpos += div_; + + const unsigned long out3 = out4 - prev3; + prev3 = out4; + const unsigned long out2 = out3 - prev2; + prev2 = out3; + + *out = static_cast(out2 - prev1) * mul / 0x10000; + prev1 = out2; + out += channels; + } + + bufpos -= inlen; + } + + sum1 = sm1; + sum2 = sm2; + sum3 = sm3; + sum4 = sm4; + + /*unsigned long sm1 = sum1; + unsigned long sm2 = sum2; + unsigned long sm3 = sum3; + unsigned long sm4 = sum4; + + if (produced) { + unsigned divn = nextdivn; + std::size_t n = produced; + + do { + do { + sm1 += static_cast(*s); + s += channels; + sm2 += sm1; + sm3 += sm2; + sm4 += sm3; + } while (--divn); + + const unsigned long out4 = sm4 - prev4; + prev4 = sm4; + const unsigned long out3 = out4 - prev3; + prev3 = out4; + const unsigned long out2 = out3 - prev2; + prev2 = out3; + *out = static_cast(out2 - prev1) * mul / 0x10000; + prev1 = out2; + out += channels; + + divn = div_; + } while (--n); + + nextdivn = div_; + } + + { + unsigned divn = (in + inlen * channels - s) / channels; + nextdivn -= divn; + + while (divn--) { + sm1 += static_cast(*s); + s += channels; + sm2 += sm1; + sm3 += sm2; + sm4 += sm3; + } + } + + sum1 = sm1; + sum2 = sm2; + sum3 = sm3; + sum4 = sm4;*/ + + return produced; +} + +template +class Cic4 : public SubResampler { + Cic4Core cics[channels]; + +public: + enum { MAX_DIV = 13 }; + Cic4(unsigned div); + std::size_t resample(short *out, const short *in, std::size_t inlen); + unsigned mul() const { return 1; } + unsigned div() const { return cics[0].div(); } +}; + +template +Cic4::Cic4(const unsigned div) { + for (unsigned i = 0; i < channels; ++i) + cics[i].reset(div); +} + +template +std::size_t Cic4::resample(short *const out, const short *const in, const std::size_t inlen) { + std::size_t samplesOut; + + for (unsigned i = 0; i < channels; ++i) { + samplesOut = cics[i].filter(out + i, in + i, inlen); + } + + return samplesOut; +} + +#endif diff --git a/supergameboy/common/resample/convoluter.h b/supergameboy/common/resample/convoluter.h new file mode 100644 index 00000000..41fab0d0 --- /dev/null +++ b/supergameboy/common/resample/convoluter.h @@ -0,0 +1,156 @@ +/*************************************************************************** + * Copyright (C) 2008 by Sindre AamÃ¥s * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#ifndef CONVOLUTER_H +#define CONVOLUTER_H + +#include +#include + +template +class PolyPhaseConvoluter { + const short *kernel; + short *prevbuf; + + unsigned phaseLen; + unsigned div_; + unsigned x_; + +public: + PolyPhaseConvoluter() : kernel(NULL), prevbuf(NULL), phaseLen(0), div_(0), x_(0) {} + PolyPhaseConvoluter(const short *kernel, unsigned phaseLen, unsigned div) { reset(kernel, phaseLen, div); } + ~PolyPhaseConvoluter() { delete[] prevbuf; } + void reset(const short *kernel, unsigned phaseLen, unsigned div); + std::size_t filter(short *out, const short *in, std::size_t inlen); + void adjustDiv(const unsigned div) { this->div_ = div; } + unsigned div() const { return div_; } +}; + +template +void PolyPhaseConvoluter::reset(const short *const kernel, const unsigned phaseLen, const unsigned div) { + this->kernel = kernel; + this->phaseLen = phaseLen; + this->div_ = div; + x_ = 0; + delete[] prevbuf; + prevbuf = new short[phaseLen]; + std::fill(prevbuf, prevbuf + phaseLen, 0); +} + +template +std::size_t PolyPhaseConvoluter::filter(short *out, const short *const in, std::size_t inlen) { + if (!kernel || !inlen) + return 0; + + /*for (std::size_t x = 0; x < inlen + M; ++x) { + const int end = x < inlen ? M + 1 : inlen + M - x; + int j = x < M ? M - x : 0; + j += (phases - (x - M + j) % phases) % phases; // adjust j so we don't start on a virtual 0 sample + + for (; j < end; j += phases) { + buffer[x] += kernel[j] * start[(x - M + j) / phases]; + } + }*/ + + /*for (std::size_t x = 0; x < inlen + M; ++x) { + const int end = x < inlen ? M + 1 : inlen + M - x; + int j = x < M ? M - x : 0; + j += (phases - (x - M + j) % phases) % phases; // adjust j so we don't start on a virtual 0 sample + const short *k = kernel + (j % phases) * phaseLen + j / phases; + const short *s = start + (x - M + j) / phases; + int n = ((end - j) + phases - 1) / phases; + + do { + buffer[x] += *k++ * *s++; + } while (--n); + }*/ + + const std::size_t M = phaseLen * phases - 1; + inlen *= phases; + std::size_t x = x_; + + for (; x < (M < inlen ? M : inlen); x += div_) { + long acc = 0; + const unsigned phase = (phases - (x + 1) % phases) % phases; // adjust phase so we don't start on a virtual 0 sample + const short *s = prevbuf + (x + 1 + phase) / phases; + const short *k = kernel + phase * phaseLen; + unsigned n = prevbuf + phaseLen - s; + + while (n--) { + acc += *k++ * *s++; + } + + s = in; + n = x / phases + 1; + + do { + acc += *k++ * *s; + s += channels; + } while (--n); + + *out = acc / 0x10000; + out += channels; + } + + for (; x < inlen; x += div_) { + long acc = 0; + const unsigned phase = (phases - (x - M) % phases) % phases; // adjust phase so we don't start on a virtual 0 sample + const short *s = in + ((x - M + phase) / phases) * channels; + const short *k = kernel + phase * phaseLen; +// unsigned n = (M + 1/* - phase + phases - 1*/) / phases; + unsigned n = phaseLen; + + do { + acc += *k++ * *s; + s += channels; + } while (--n); + + *out = acc / 0x10000; + out += channels; + } + + const std::size_t produced = (x - x_) / div_; + x_ = x - inlen; + + inlen /= phases; + + { + short *p = prevbuf; + const short *s = in + (inlen - phaseLen) * channels; + unsigned n = phaseLen; + + if (inlen < phaseLen) { + const unsigned i = phaseLen - inlen; + + std::memmove(p, p + inlen, i * sizeof(short)); + + p += i; + n -= i; + s = in; + } + + do { + *p++ = *s; + s += channels; + } while (--n); + } + + return produced; +} + +#endif diff --git a/supergameboy/common/resample/hammingsinc.h b/supergameboy/common/resample/hammingsinc.h new file mode 100644 index 00000000..bb50daee --- /dev/null +++ b/supergameboy/common/resample/hammingsinc.h @@ -0,0 +1,100 @@ +/*************************************************************************** + * Copyright (C) 2008 by Sindre AamÃ¥s * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#ifndef HAMMINGSINC_H +#define HAMMINGSINC_H + +#include "convoluter.h" +#include "subresampler.h" +#include "makesinckernel.h" +#include "cic3.h" +#include +#include + +template +class HammingSinc : public SubResampler { + PolyPhaseConvoluter convoluters[channels]; + short *kernel; + + static double hammingWin(const long i, const long M) { + static const double PI = 3.14159265358979323846; + return 0.53836 - 0.46164 * std::cos(2 * PI * i / M); + } + + void init(unsigned div, unsigned phaseLen, double fc); + +public: + enum { MUL = phases }; + + typedef Cic3 Cic; + static float cicLimit() { return 4.2f; } + + class RollOff { + static unsigned toTaps(const float rollOffWidth) { + static const float widthTimesTaps = 3.0f; + return std::ceil(widthTimesTaps / rollOffWidth); + } + + static float toFc(const float rollOffStart, const int taps) { + static const float startToFcDeltaTimesTaps = 1.27f; + return startToFcDeltaTimesTaps / taps + rollOffStart; + } + + public: + const unsigned taps; + const float fc; + + RollOff(float rollOffStart, float rollOffWidth) : taps(toTaps(rollOffWidth)), fc(toFc(rollOffStart, taps)) {} + }; + + HammingSinc(unsigned div, unsigned phaseLen, double fc) { init(div, phaseLen, fc); } + HammingSinc(unsigned div, RollOff ro) { init(div, ro.taps, ro.fc); } + ~HammingSinc() { delete[] kernel; } + std::size_t resample(short *out, const short *in, std::size_t inlen); + void adjustDiv(unsigned div); + unsigned mul() const { return MUL; } + unsigned div() const { return convoluters[0].div(); } +}; + +template +void HammingSinc::init(const unsigned div, const unsigned phaseLen, const double fc) { + kernel = new short[phaseLen * phases]; + + makeSincKernel(kernel, phases, phaseLen, fc, hammingWin); + + for (unsigned i = 0; i < channels; ++i) + convoluters[i].reset(kernel, phaseLen, div); +} + +template +std::size_t HammingSinc::resample(short *const out, const short *const in, const std::size_t inlen) { + std::size_t samplesOut; + + for (unsigned i = 0; i < channels; ++i) + samplesOut = convoluters[i].filter(out + i, in + i, inlen); + + return samplesOut; +} + +template +void HammingSinc::adjustDiv(const unsigned div) { + for (unsigned i = 0; i < channels; ++i) + convoluters[i].adjustDiv(div); +} + +#endif diff --git a/supergameboy/common/resample/linint.h b/supergameboy/common/resample/linint.h new file mode 100644 index 00000000..0c6d8cb2 --- /dev/null +++ b/supergameboy/common/resample/linint.h @@ -0,0 +1,129 @@ +/*************************************************************************** + * Copyright (C) 2008 by Sindre AamÃ¥s * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#ifndef LININT_H +#define LININT_H + +#include +#include "resampler.h" +#include "u48div.h" + +template +class LinintCore { + unsigned long ratio; + std::size_t pos_; + unsigned fracPos_; + int prevSample_; + +public: + LinintCore(long inRate = 1, long outRate = 1) { init(inRate, outRate); } + void adjustRate(long inRate, long outRate) { ratio = (static_cast(inRate) / outRate) * 0x10000 + 0.5; } + void exactRatio(unsigned long &mul, unsigned long &div) const { mul = 0x10000; div = ratio; } + void init(long inRate, long outRate); + std::size_t maxOut(std::size_t inlen) const { return inlen ? u48div(inlen - 1, 0xFFFF, ratio) + 1 : 0; } + std::size_t resample(short *out, const short *in, std::size_t inlen); +}; + +template +void LinintCore::init(const long inRate, const long outRate) { + adjustRate(inRate, outRate); + pos_ = (ratio >> 16) + 1; + fracPos_ = ratio & 0xFFFF; + prevSample_ = 0; +} + +template +std::size_t LinintCore::resample(short *const out, const short *const in, const std::size_t inlen) { + std::size_t opos = 0; + std::size_t pos = pos_; + unsigned fracPos = fracPos_; + int prevSample = prevSample_; + + if (pos < inlen) { + if (pos != 0) + prevSample = in[(pos-1) * channels]; + + for (;;) { + out[opos] = prevSample + (in[pos * channels] - prevSample) * static_cast(fracPos) / 0x10000; + opos += channels; + + { + const unsigned long next = ratio + fracPos; + + pos += next >> 16; + fracPos = next & 0xFFFF; + } + + if (pos < inlen) { + prevSample = in[(pos-1) * channels]; + } else + break; + } + + if (pos == inlen) + prevSample = in[(pos-1) * channels]; + } + +// const std::size_t produced = ((pos - pos_) * 0x10000 + fracPos - fracPos_) / ratio; + + pos_ = pos - inlen; + fracPos_ = fracPos; + prevSample_ = prevSample; + + return opos / channels; +} + +template +class Linint : public Resampler { + LinintCore cores[channels]; + +public: + Linint(long inRate, long outRate); + void adjustRate(long inRate, long outRate); + void exactRatio(unsigned long &mul, unsigned long &div) const { cores[0].exactRatio(mul, div); } + std::size_t maxOut(std::size_t inlen) const { return cores[0].maxOut(inlen); } + std::size_t resample(short *out, const short *in, std::size_t inlen); +}; + +template +Linint::Linint(const long inRate, const long outRate) { + setRate(inRate, outRate); + + for (unsigned i = 0; i < channels; ++i) + cores[i].init(inRate, outRate); +} + +template +void Linint::adjustRate(const long inRate, const long outRate) { + setRate(inRate, outRate); + + for (unsigned i = 0; i < channels; ++i) + cores[i].adjustRate(inRate, outRate); +} + +template +std::size_t Linint::resample(short *const out, const short *const in, const std::size_t inlen) { + std::size_t outlen = 0; + + for (unsigned i = 0; i < channels; ++i) + outlen = cores[i].resample(out + i, in + i, inlen); + + return outlen; +} + +#endif diff --git a/supergameboy/common/resample/makesinckernel.h b/supergameboy/common/resample/makesinckernel.h new file mode 100644 index 00000000..c6515f2d --- /dev/null +++ b/supergameboy/common/resample/makesinckernel.h @@ -0,0 +1,152 @@ +/*************************************************************************** + * Copyright (C) 2008 by Sindre AamÃ¥s * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#ifndef MAKE_SINC_KERNEL_H +#define MAKE_SINC_KERNEL_H + +#include +#include + +template +void makeSincKernel(short *const kernel, const unsigned phases, const unsigned phaseLen, double fc, Window win) { + static const double PI = 3.14159265358979323846; + fc /= phases; + + /*{ + double *const dkernel = new double[phaseLen * phases]; + const long M = static_cast(phaseLen) * phases - 1; + + for (long i = 0; i < M + 1; ++i) { + const double sinc = i * 2 == M ? + PI * fc : + std::sin(PI * fc * (i * 2 - M)) / (i * 2 - M); + + dkernel[(i % phases) * phaseLen + i / phases] = win(i, M) * sinc; + } + + double maxabsgain = 0; + + for (unsigned ph = 0; ph < phases; ++ph) { + double gain = 0; + double absgain = 0; + + for (unsigned i = 0; i < phaseLen; ++i) { + gain += dkernel[ph * phaseLen + i]; + absgain += std::abs(dkernel[ph * phaseLen + i]); + } + + gain = 1.0 / gain; + + // Per phase normalization to avoid DC fluctuations. + for (unsigned i = 0; i < phaseLen; ++i) + dkernel[ph * phaseLen + i] *= gain; + + absgain *= gain; + + if (absgain > maxabsgain) + maxabsgain = absgain; + } + + const double gain = 0x10000 / maxabsgain; + + for (long i = 0; i < M + 1; ++i) + kernel[i] = std::floor(dkernel[i] * gain + 0.5); + + delete[] dkernel; + }*/ + + // The following is equivalent to the more readable version above + + const long M = static_cast(phaseLen) * phases - 1; + + double *const dkernel = new double[M / 2 + 1]; + + { + double *dk = dkernel; + + for (unsigned ph = 0; ph < phases; ++ph) { + for (long i = ph; i < M / 2 + 1; i += phases) { + const double sinc = i * 2 == M ? + PI * fc : + std::sin(PI * fc * (i * 2 - M)) / (i * 2 - M); + + *dk++ = win(i, M) * sinc; + } + } + } + + double maxabsgain = 0.0; + + { + double *dkp1 = dkernel; + double *dkp2 = dkernel + M / 2; + + for (unsigned ph = 0; ph < (phases + 1) / 2; ++ph) { + double gain = 0.0; + double absgain = 0.0; + + { + const double *kp1 = dkp1; + const double *kp2 = dkp2; + long i = ph; + + for (; i < M / 2 + 1; i += phases) { + gain += *kp1; + absgain += std::abs(*kp1++); + } + + for (; i < M + 1; i += phases) { + gain += *kp2; + absgain += std::abs(*kp2--); + } + } + + gain = 1.0 / gain; + + long i = ph; + + for (; i < M / 2 + 1; i += phases) + *dkp1++ *= gain; + + if (dkp1 < dkp2) { + for (; i < M + 1; i += phases) + *dkp2-- *= gain; + } + + absgain *= gain; + + if (absgain > maxabsgain) + maxabsgain = absgain; + } + } + + const double gain = 0x10000 / maxabsgain; + const double *dk = dkernel; + + for (unsigned ph = 0; ph < phases; ++ph) { + short *k = kernel + ph * phaseLen; + short *km = kernel + M - ph * phaseLen; + + for (long i = ph; i < M / 2 + 1; i += phases) + *km-- = *k++ = std::floor(*dk++ * gain + 0.5); + } + + delete[] dkernel; +} + +#endif diff --git a/supergameboy/common/resample/rectsinc.h b/supergameboy/common/resample/rectsinc.h new file mode 100644 index 00000000..9f99ed6b --- /dev/null +++ b/supergameboy/common/resample/rectsinc.h @@ -0,0 +1,99 @@ +/*************************************************************************** + * Copyright (C) 2008 by Sindre AamÃ¥s * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#ifndef RECTSINC_H +#define RECTSINC_H + +#include "convoluter.h" +#include "subresampler.h" +#include "makesinckernel.h" +#include "cic2.h" +#include +#include + +template +class RectSinc : public SubResampler { + PolyPhaseConvoluter convoluters[channels]; + short *kernel; + + static double rectWin(const long /*i*/, const long /*M*/) { + return 1; + } + + void init(unsigned div, unsigned phaseLen, double fc); + +public: + enum { MUL = phases }; + + typedef Cic2 Cic; + static float cicLimit() { return 2.0f; } + + class RollOff { + static unsigned toTaps(const float rollOffWidth) { + static const float widthTimesTaps = 0.9f; + return std::ceil(widthTimesTaps / rollOffWidth); + } + + static float toFc(const float rollOffStart, const int taps) { + static const float startToFcDeltaTimesTaps = 0.43f; + return startToFcDeltaTimesTaps / taps + rollOffStart; + } + + public: + const unsigned taps; + const float fc; + + RollOff(float rollOffStart, float rollOffWidth) : taps(toTaps(rollOffWidth)), fc(toFc(rollOffStart, taps)) {} + }; + + RectSinc(unsigned div, unsigned phaseLen, double fc) { init(div, phaseLen, fc); } + RectSinc(unsigned div, RollOff ro) { init(div, ro.taps, ro.fc); } + ~RectSinc() { delete[] kernel; } + std::size_t resample(short *out, const short *in, std::size_t inlen); + void adjustDiv(unsigned div); + unsigned mul() const { return MUL; } + unsigned div() const { return convoluters[0].div(); } +}; + +template +void RectSinc::init(const unsigned div, const unsigned phaseLen, const double fc) { + kernel = new short[phaseLen * phases]; + + makeSincKernel(kernel, phases, phaseLen, fc, rectWin); + + for (unsigned i = 0; i < channels; ++i) + convoluters[i].reset(kernel, phaseLen, div); +} + +template +std::size_t RectSinc::resample(short *const out, const short *const in, const std::size_t inlen) { + std::size_t samplesOut; + + for (unsigned i = 0; i < channels; ++i) + samplesOut = convoluters[i].filter(out + i, in + i, inlen); + + return samplesOut; +} + +template +void RectSinc::adjustDiv(const unsigned div) { + for (unsigned i = 0; i < channels; ++i) + convoluters[i].adjustDiv(div); +} + +#endif diff --git a/supergameboy/common/resample/resampler.h b/supergameboy/common/resample/resampler.h new file mode 100644 index 00000000..f3d448d9 --- /dev/null +++ b/supergameboy/common/resample/resampler.h @@ -0,0 +1,43 @@ +/*************************************************************************** + * Copyright (C) 2008 by Sindre AamÃ¥s * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#ifndef RESAMPLER_H +#define RESAMPLER_H + +#include + +class Resampler { + long inRate_; + long outRate_; + +protected: + void setRate(const long inRate, const long outRate) { inRate_ = inRate; outRate_ = outRate; } + +public: + Resampler() : inRate_(0), outRate_(0) {} + long inRate() const { return inRate_; } + long outRate() const { return outRate_; } + + virtual void adjustRate(long inRate, long outRate) = 0; + virtual void exactRatio(unsigned long &mul, unsigned long &div) const = 0; + virtual std::size_t maxOut(std::size_t inlen) const = 0; + virtual std::size_t resample(short *out, const short *in, std::size_t inlen) = 0; + virtual ~Resampler() {} +}; + +#endif diff --git a/supergameboy/common/resample/resamplerinfo.cpp b/supergameboy/common/resample/resamplerinfo.cpp new file mode 100644 index 00000000..3abcdaf8 --- /dev/null +++ b/supergameboy/common/resample/resamplerinfo.cpp @@ -0,0 +1,61 @@ +/*************************************************************************** + * Copyright (C) 2008 by Sindre AamÃ¥s * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#include "resamplerinfo.h" +#include "chainresampler.h" +#include "hammingsinc.h" +#include "blackmansinc.h" +#include "rectsinc.h" +#include "linint.h" + +struct LinintInfo { + static Resampler* create(long inRate, long outRate, std::size_t) { return new Linint<2>(inRate, outRate); } +}; + +struct RectsincInfo { + static Resampler* create(long inRate, long outRate, std::size_t periodSz) { + ChainResampler *r = new ChainResampler; + r->init(inRate, outRate, periodSz); + return r; + } +}; + +struct HammingsincInfo { + static Resampler* create(long inRate, long outRate, std::size_t periodSz) { + ChainResampler *r = new ChainResampler; + r->init(inRate, outRate, periodSz); + return r; + } +}; + +struct BlackmansincInfo { + static Resampler* create(long inRate, long outRate, std::size_t periodSz) { + ChainResampler *r = new ChainResampler; + r->init(inRate, outRate, periodSz); + return r; + } +}; + +const ResamplerInfo ResamplerInfo::resamplers[] = { + { "2-tap linear interpolation", LinintInfo::create }, + { "Rectangular windowed sinc (~20 dB SNR)", RectsincInfo::create }, + { "Hamming windowed sinc (~50 dB SNR)", HammingsincInfo::create }, + { "Blackman windowed sinc (~70 dB SNR)", BlackmansincInfo::create } +}; + +const unsigned ResamplerInfo::num_ = sizeof(ResamplerInfo::resamplers) / sizeof(ResamplerInfo); diff --git a/supergameboy/common/resample/resamplerinfo.h b/supergameboy/common/resample/resamplerinfo.h new file mode 100644 index 00000000..23f4a545 --- /dev/null +++ b/supergameboy/common/resample/resamplerinfo.h @@ -0,0 +1,36 @@ +/*************************************************************************** + * Copyright (C) 2008 by Sindre AamÃ¥s * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#ifndef RESAMPLER_INFO_H +#define RESAMPLER_INFO_H + +#include "resampler.h" + +struct ResamplerInfo { + const char *desc; + Resampler* (*create)(long inRate, long outRate, std::size_t periodSz); + + static unsigned num() { return num_; } + static const ResamplerInfo& get(unsigned n) { return resamplers[n]; } + +private: + static const ResamplerInfo resamplers[]; + static const unsigned num_; +}; + +#endif diff --git a/supergameboy/common/resample/subresampler.h b/supergameboy/common/resample/subresampler.h new file mode 100644 index 00000000..134ec80b --- /dev/null +++ b/supergameboy/common/resample/subresampler.h @@ -0,0 +1,33 @@ +/*************************************************************************** + * Copyright (C) 2008 by Sindre AamÃ¥s * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#ifndef SUBRESAMPLER_H +#define SUBRESAMPLER_H + +#include + +class SubResampler { +public: + virtual std::size_t resample(short *out, const short *in, std::size_t inlen) = 0; + virtual unsigned mul() const = 0; + virtual unsigned div() const = 0; + virtual void adjustDiv(unsigned /*div*/) {} + virtual ~SubResampler() {} +}; + +#endif diff --git a/supergameboy/common/resample/u48div.cpp b/supergameboy/common/resample/u48div.cpp new file mode 100644 index 00000000..077ddfd9 --- /dev/null +++ b/supergameboy/common/resample/u48div.cpp @@ -0,0 +1,54 @@ +/*************************************************************************** + * Copyright (C) 2008 by Sindre AamÃ¥s * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#include "u48div.h" + +unsigned long u48div(unsigned long num1, unsigned num2, const unsigned long den) { + unsigned long res = 0; + unsigned s = 16; + + do { + if (num1 < 0x10000) { + num1 <<= s; + num1 |= num2 & ((1 << s) - 1); + s = 0; + } else { + if (num1 < 0x1000000) { + const unsigned maxs = s < 8 ? s : 8; + num1 <<= maxs; + num1 |= (num2 >> (s -= maxs)) & ((1 << maxs) - 1); + } + + if (num1 < 0x10000000) { + const unsigned maxs = s < 4 ? s : 4; + num1 <<= maxs; + num1 |= (num2 >> (s -= maxs)) & ((1 << maxs) - 1); + } + + while (num1 < den && s) { + num1 <<= 1; // if this overflows we're screwed + num1 |= num2 >> --s & 1; + } + } + + res += (num1 / den) << s; + num1 = (num1 % den); + } while (s); + + return res; +} diff --git a/supergameboy/common/resample/u48div.h b/supergameboy/common/resample/u48div.h new file mode 100644 index 00000000..26b16af4 --- /dev/null +++ b/supergameboy/common/resample/u48div.h @@ -0,0 +1,24 @@ +/*************************************************************************** + * Copyright (C) 2008 by Sindre AamÃ¥s * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#ifndef U48DIV_H +#define U48DIV_H + +unsigned long u48div(unsigned long num1, unsigned num2, unsigned long den); + +#endif diff --git a/supergameboy/common/resample/upsampler.h b/supergameboy/common/resample/upsampler.h new file mode 100644 index 00000000..8bf88d8a --- /dev/null +++ b/supergameboy/common/resample/upsampler.h @@ -0,0 +1,51 @@ +/*************************************************************************** + * Copyright (C) 2008 by Sindre AamÃ¥s * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#ifndef UPSAMPLER_H +#define UPSAMPLER_H + +#include "subresampler.h" +#include + +template +class Upsampler : public SubResampler { + unsigned mul_; + +public: + Upsampler(const unsigned mul) : mul_(mul) {} + std::size_t resample(short *out, const short *in, std::size_t inlen); + unsigned mul() const { return mul_; } + unsigned div() const { return 1; } +}; + +template +std::size_t Upsampler::resample(short *out, const short *in, std::size_t inlen) { + if (inlen) { + std::memset(out, 0, inlen * mul_ * sizeof(short) * channels); + + do { + std::memcpy(out, in, sizeof(short) * channels); + in += channels; + out += mul_ * channels; + } while (--inlen); + } + + return inlen * mul_; +} + +#endif diff --git a/supergameboy/common/ringbuffer.h b/supergameboy/common/ringbuffer.h new file mode 100644 index 00000000..34f22bfe --- /dev/null +++ b/supergameboy/common/ringbuffer.h @@ -0,0 +1,112 @@ +/*************************************************************************** + * Copyright (C) 2008 by Sindre AamÃ¥s * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#ifndef RINGBUFFER_H +#define RINGBUFFER_H + +#include "array.h" +#include +#include +#include + +template +class RingBuffer { + Array buf; + std::size_t sz; + std::size_t rpos; + std::size_t wpos; + +public: + RingBuffer(const std::size_t sz_in = 0) : sz(0), rpos(0), wpos(0) { reset(sz_in); } + + std::size_t avail() const { + return (wpos < rpos ? 0 : sz) + rpos - wpos - 1; + } + + void clear() { + wpos = rpos = 0; + } + + void fill(T value); + + void read(T *out, std::size_t num); + + void reset(std::size_t sz_in); + + std::size_t size() const { + return sz - 1; + } + + std::size_t used() const { + return (wpos < rpos ? sz : 0) + wpos - rpos; + } + + void write(const T *in, std::size_t num); +}; + +template +void RingBuffer::fill(const T value) { + std::fill(buf + 0, buf + sz, value); + rpos = 0; + wpos = sz - 1; +} + +template +void RingBuffer::read(T *out, std::size_t num) { + if (rpos + num > sz) { + const std::size_t n = sz - rpos; + + std::memcpy(out, buf + rpos, n * sizeof(T)); + + rpos = 0; + num -= n; + out += n; + } + + std::memcpy(out, buf + rpos, num * sizeof(T)); + + if ((rpos += num) == sz) + rpos = 0; +} + +template +void RingBuffer::reset(const std::size_t sz_in) { + sz = sz_in + 1; + rpos = wpos = 0; + buf.reset(sz_in ? sz : 0); +} + +template +void RingBuffer::write(const T *in, std::size_t num) { + if (wpos + num > sz) { + const std::size_t n = sz - wpos; + + std::memcpy(buf + wpos, in, n * sizeof(T)); + + wpos = 0; + num -= n; + in += n; + } + + std::memcpy(buf + wpos, in, num * sizeof(T)); + + if ((wpos += num) == sz) + wpos = 0; +} + +#endif diff --git a/supergameboy/common/usec.h b/supergameboy/common/usec.h new file mode 100644 index 00000000..2bc889cf --- /dev/null +++ b/supergameboy/common/usec.h @@ -0,0 +1,31 @@ +/*************************************************************************** + * Copyright (C) 2008 by Sindre AamÃ¥s * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#ifndef USEC_H +#define USEC_H + +typedef unsigned long usec_t; + +static inline usec_t negate(usec_t t) { + return usec_t(0) - t; +} + +usec_t getusecs(); +void usecsleep(usec_t usecs); + +#endif diff --git a/supergameboy/interface/interface.cpp b/supergameboy/interface/interface.cpp new file mode 100644 index 00000000..b726b147 --- /dev/null +++ b/supergameboy/interface/interface.cpp @@ -0,0 +1,373 @@ +SuperGameBoy supergameboy; + +//==================== +//SuperGameBoy::Packet +//==================== + +const char SuperGameBoy::command_name[32][64] = { + "PAL01", "PAL23", "PAL03", "PAL12", + "ATTR_BLK", "ATTR_LIN", "ATTR_DIV", "ATTR_CHR", + "SOUND", "SOU_TRN", "PAL_SET", "PAL_TRN", + "ATRC_EN", "TEST_EN", "ICON_EN", "DATA_SND", + "DATA_TRN", "MLT_REQ", "JUMP", "CHR_TRN", + "PCT_TRN", "ATTR_TRN", "ATTR_SET", "MASK_EN", + "OBJ_TRN", "19_???", "1A_???", "1B_???", + "1C_???", "1D_???", "1E_ROM", "1F_???", +}; + +void SuperGameBoy::joyp_write(bool p15, bool p14) { + //=============== + //joypad handling + //=============== + + if(p15 == 1 && p14 == 1) { + if(joyp15lock == 0 && joyp14lock == 0) { + joyp15lock = 1; + joyp14lock = 1; + joyp_id = (joyp_id + 1) & 3; + } + } + + if(p15 == 0 && p14 == 1) joyp15lock = 0; + if(p15 == 1 && p14 == 0) joyp14lock = 0; + + //=============== + //packet handling + //=============== + + if(p15 == 0 && p14 == 0) { + //pulse + pulselock = false; + packetoffset = 0; + bitoffset = 0; + strobelock = true; + packetlock = false; + return; + } + + if(pulselock) return; + + if(p15 == 1 && p14 == 1) { + strobelock = false; + return; + } + + if(strobelock) { + if(p15 == 1 || p14 == 1) { + //malformed packet + packetlock = false; + pulselock = true; + bitoffset = 0; + packetoffset = 0; + } else { + return; + } + } + + //p15:1, p14:0 = 0 + //p15:0, p14:1 = 1 + bool bit = (p15 == 0); + strobelock = true; + + if(packetlock) { + if(p15 == 1 && p14 == 0) { + if((joyp_packet[0] >> 3) == 0x11) { + mmio.mlt_req = joyp_packet[1] & 3; + if(mmio.mlt_req == 2) mmio.mlt_req = 3; + joyp_id = 0; + } + + if(packetsize < 64) packet[packetsize++] = joyp_packet; + packetlock = false; + pulselock = true; + } + return; + } + + bitdata = (bit << 7) | (bitdata >> 1); + if(++bitoffset < 8) return; + + bitoffset = 0; + joyp_packet[packetoffset] = bitdata; + if(++packetoffset < 16) return; + packetlock = true; +} + +//================== +//SuperGameBoy::Core +//================== + +static uint8_t null_rom[32768]; + +bool SuperGameBoy::init(bool version_) { + if(!romdata) { romdata = null_rom; romsize = 32768; } + version = version_; + + gambatte = new Gambatte::GB; + gambatte->setVideoBlitter(this); + gambatte->setInputStateGetter(this); + + return true; +} + +void SuperGameBoy::term() { + if(gambatte) { + delete gambatte; + gambatte = 0; + } +} + +unsigned SuperGameBoy::run(uint32_t *samplebuffer, unsigned samples) { + if((mmio.r6003 & 0x80) == 0) { + //Gameboy is inactive + samplebuffer[0] = 0; + return 1; + } + + return gambatte->runFor(samplebuffer, samples); +} + +void SuperGameBoy::save() { + gambatte->saveSavedata(); +} + +void SuperGameBoy::serialize(nall::serializer &s) { + s.integer(vram_row); + s.array(vram); + + s.integer(mmio.r6000); + s.integer(mmio.r6003); + s.integer(mmio.r6004); + s.integer(mmio.r6005); + s.integer(mmio.r6006); + s.integer(mmio.r6007); + s.array(mmio.r7000); + s.integer(mmio.r7800); + s.integer(mmio.mlt_req); + + for(unsigned i = 0; i < 64; i++) s.array(packet[i].data); + s.integer(packetsize); + + s.integer(joyp_id); + s.integer(joyp15lock); + s.integer(joyp14lock); + s.integer(pulselock); + s.integer(strobelock); + s.integer(packetlock); + s.array(joyp_packet.data); + s.integer(packetoffset); + s.integer(bitdata); + s.integer(bitoffset); + + uint8_t *savestate = new uint8_t[256 * 1024]; + if(s.mode() == serializer::Load) { + s.array(savestate, 256 * 1024); + + file fp; + if(fp.open("supergameboy-state.tmp", file::mode_write)) { + fp.write(savestate, 256 * 1024); + fp.close(); + + gambatte->loadState("supergameboy-state.tmp"); + unlink("supergameboy-state.tmp"); + } + } else if(s.mode() == serializer::Save) { + gambatte->saveState("supergameboy-state.tmp"); + + file fp; + if(fp.open("supergameboy-state.tmp", file::mode_read)) { + fp.read(savestate, fp.size() < 256 * 1024 ? fp.size() : 256 * 1024); + fp.close(); + } + + unlink("supergameboy-state.tmp"); + s.array(savestate, 256 * 1024); + } else if(s.mode() == serializer::Size) { + s.array(savestate, 256 * 1024); + } + delete[] savestate; +} + +void SuperGameBoy::power() { + gambatte->load(true); + mmio_reset(); +} + +void SuperGameBoy::reset() { + gambatte->reset(); + mmio_reset(); +} + +void SuperGameBoy::row(unsigned row) { + mmio.r7800 = 0; + vram_row = row; + render(vram_row); +} + +uint8_t SuperGameBoy::read(uint16_t addr) { + //LY counter + if(addr == 0x6000) { + return gambatte->lyCounter(); + } + + //command ready port + if(addr == 0x6002) { + bool data = packetsize > 0; + if(data) { + for(unsigned i = 0; i < 16; i++) mmio.r7000[i] = packet[0][i]; + packetsize--; + for(unsigned i = 0; i < packetsize; i++) packet[i] = packet[i + 1]; + } + return data; + } + + //command port + if((addr & 0xfff0) == 0x7000) { + return mmio.r7000[addr & 15]; + } + + if(addr == 0x7800) { + uint8_t data = vram[mmio.r7800]; + mmio.r7800 = (mmio.r7800 + 1) % 320; + return data; + } + + return 0x00; +} + +void SuperGameBoy::write(uint16_t addr, uint8_t data) { + //control port + //d7 = /RESET line (0 = stop, 1 = run) + if(addr == 0x6003) { + if((mmio.r6003 & 0x80) == 0x00 && (data & 0x80) == 0x80) { + reset(); + command_1e(); + } + + mmio.r6003 = data; + return; + } + + if(addr == 0x6004) { mmio.r6004 = data; return; } //joypad 1 state + if(addr == 0x6005) { mmio.r6005 = data; return; } //joypad 2 state + if(addr == 0x6006) { mmio.r6006 = data; return; } //joypad 3 state + if(addr == 0x6007) { mmio.r6007 = data; return; } //joypad 4 state +} + +void SuperGameBoy::mmio_reset() { + mmio.r6000 = 0x00; + mmio.r6003 = 0x00; + mmio.r6004 = 0xff; + mmio.r6005 = 0xff; + mmio.r6006 = 0xff; + mmio.r6007 = 0xff; + for(unsigned n = 0; n < 16; n++) mmio.r7000[n] = 0; + mmio.r7800 = 0; + mmio.mlt_req = 0; + + packetsize = 0; + + vram_row = 0; + memset(vram, 0, 320); + + joyp_id = 3; + joyp15lock = 0; + joyp14lock = 0; + pulselock = true; +} + +//simulate 256-byte internal SGB BIOS on /RESET +void SuperGameBoy::command_1e() { + for(unsigned i = 0; i < 6; i++) { + Packet p; + p[0] = 0xf1 + (i << 1); + p[1] = 0; + for(unsigned n = 2; n < 16; n++) { + uint8_t data = romdata[0x0104 + (i * 14) + (n - 2)]; + p[1] += data; + p[n] = data; + } + if(packetsize < 64) packet[packetsize++] = p; + } +} + +void SuperGameBoy::render(unsigned row) { + gambatte->updateVideo(); + + uint32_t *source = buffer + row * 160 * 8; + memset(vram, 0x00, 320); + + for(unsigned y = row * 8; y < row * 8 + 8; y++) { + for(unsigned x = 0; x < 160; x++) { + unsigned pixel = *source++ / 0x555555; + pixel ^= 3; + + unsigned addr = (x / 8 * 16) + ((y & 7) * 2); + vram[addr + 0] |= ((pixel & 1) >> 0) << (7 - (x & 7)); + vram[addr + 1] |= ((pixel & 2) >> 1) << (7 - (x & 7)); + } + } +} + +//====================== +//Gambatte::VideoBlitter +//====================== + +//should always be 160x144, as no filters are used +void SuperGameBoy::setBufferDimensions(unsigned width, unsigned height) { + if(buffer) delete[] buffer; + buffer = new uint32_t[width * height]; + bufferWidth = width; + bufferHeight = height; +} + +const Gambatte::PixelBuffer SuperGameBoy::inBuffer() { + Gambatte::PixelBuffer pixelBuffer; + pixelBuffer.pixels = (void*)buffer; + pixelBuffer.format = Gambatte::PixelBuffer::RGB32; + pixelBuffer.pitch = bufferWidth; + return pixelBuffer; +} + +void SuperGameBoy::blit() { +} + +//========================== +//Gambatte::InputStateGetter +//========================== + +const Gambatte::InputState& SuperGameBoy::operator()() { + inputState.joypadId = 0x0f - (joyp_id & mmio.mlt_req); + + unsigned data = 0x00; + switch(joyp_id & mmio.mlt_req) { + case 0: data = mmio.r6004; break; + case 1: data = mmio.r6005; break; + case 2: data = mmio.r6006; break; + case 3: data = mmio.r6007; break; + } + + inputState.startButton = !(data & 0x80); + inputState.selectButton = !(data & 0x40); + inputState.bButton = !(data & 0x20); + inputState.aButton = !(data & 0x10); + inputState.dpadDown = !(data & 0x08); + inputState.dpadUp = !(data & 0x04); + inputState.dpadLeft = !(data & 0x02); + inputState.dpadRight = !(data & 0x01); + + return inputState; +} + +//========================== +//SuperGameBoy::Construction +//========================== + +SuperGameBoy::SuperGameBoy() : gambatte(0), buffer(0) { + romdata = ramdata = rtcdata = 0; + romsize = ramsize = rtcsize = 0; +} + +SuperGameBoy::~SuperGameBoy() { + if(buffer) delete[] buffer; +} diff --git a/supergameboy/interface/interface.hpp b/supergameboy/interface/interface.hpp new file mode 100644 index 00000000..d369a281 --- /dev/null +++ b/supergameboy/interface/interface.hpp @@ -0,0 +1,80 @@ +class SuperGameBoy : public Gambatte::VideoBlitter, public Gambatte::InputStateGetter { +public: + Gambatte::GB *gambatte; + +//SuperGameBoy::MMIO + unsigned vram_row; + uint8_t vram[320]; + + struct MMIO { + uint8_t r6000; + uint8_t r6003; + uint8_t r6004; + uint8_t r6005; + uint8_t r6006; + uint8_t r6007; + uint8_t r7000[16]; + unsigned r7800; + uint8_t mlt_req; + } mmio; + +//SuperGameBoy::Packet + static const char command_name[32][64]; + + struct Packet { + uint8_t data[16]; + uint8_t& operator[](unsigned addr) { return data[addr & 15]; } + }; + Packet packet[64]; + unsigned packetsize; + + unsigned joyp_id; + bool joyp15lock; + bool joyp14lock; + bool pulselock; + bool strobelock; + bool packetlock; + Packet joyp_packet; + uint8_t packetoffset; + uint8_t bitdata, bitoffset; + + void joyp_write(bool p15, bool p14); + +//SuperGameBoy::Core + uint8_t *romdata, *ramdata, *rtcdata; + unsigned romsize, ramsize, rtcsize; + bool version; + + bool init(bool version); + void term(); + unsigned run(uint32_t *samplebuffer, unsigned samples); + void save(); + void serialize(nall::serializer &s); + void power(); + void reset(); + void row(unsigned row); + uint8_t read(uint16_t addr); + void write(uint16_t addr, uint8_t data); + + void mmio_reset(); + void command_1e(); + void render(unsigned row); + + SuperGameBoy(); + ~SuperGameBoy(); + +//Gambatte::VideoBlitter + unsigned bufferWidth, bufferHeight; + uint32_t *buffer; + + void setBufferDimensions(unsigned width, unsigned height); + const Gambatte::PixelBuffer inBuffer(); + void blit(); + +//Gambatte::InputStateGetter + Gambatte::InputState inputState; + + const Gambatte::InputState& operator()(); +}; + +extern SuperGameBoy supergameboy; diff --git a/supergameboy/libgambatte/SConstruct b/supergameboy/libgambatte/SConstruct new file mode 100644 index 00000000..47087ba0 --- /dev/null +++ b/supergameboy/libgambatte/SConstruct @@ -0,0 +1,64 @@ +global_cflags = ARGUMENTS.get('CFLAGS', '-Wall -Wextra -O2 -fomit-frame-pointer') +global_cxxflags = ARGUMENTS.get('CXXFLAGS', global_cflags + ' -fno-exceptions -fno-rtti') +global_defines = ' -DHAVE_STDINT_H -DCHAR_WIDTH_8' + +env = Environment(CPPPATH = ['src', 'include', '../common'], + CFLAGS = global_cflags + global_defines, + CXXFLAGS = global_cxxflags + global_defines) + +sourceFiles = Split(''' + src/bitmap_font.cpp + src/colorconversion.cpp + src/cpu.cpp + src/gambatte.cpp + src/initstate.cpp + src/interrupter.cpp + src/memory.cpp + src/rtc.cpp + src/sound.cpp + src/state_osd_elements.cpp + src/statesaver.cpp + src/video.cpp + src/sound/channel1.cpp + src/sound/channel2.cpp + src/sound/channel3.cpp + src/sound/channel4.cpp + src/sound/duty_unit.cpp + src/sound/envelope_unit.cpp + src/sound/length_counter.cpp + src/video/basic_add_event.cpp + src/video/break_event.cpp + src/video/irq_event.cpp + src/video/ly_counter.cpp + src/video/lyc_irq.cpp + src/video/m3_extra_cycles.cpp + src/video/mode3_event.cpp + src/video/mode0_irq.cpp + src/video/mode1_irq.cpp + src/video/mode2_irq.cpp + src/video/sc_reader.cpp + src/video/scx_reader.cpp + src/video/sprite_mapper.cpp + src/video/we_master_checker.cpp + src/video/we.cpp + src/video/wx_reader.cpp + src/video/wy.cpp + src/video/filters/catrom2x.cpp + src/video/filters/catrom3x.cpp + src/video/filters/kreed2xsai.cpp + src/video/filters/maxsthq2x.cpp + src/video/filters/maxsthq3x.cpp + ''') + +conf = env.Configure() + +if conf.CheckHeader('zlib.h') and conf.CheckLib('z'): + sourceFiles.append('src/file/unzip/unzip.c') + sourceFiles.append('src/file/unzip/ioapi.c') + sourceFiles.append('src/file/file_zip.cpp') +else: + sourceFiles.append('src/file/file.cpp') + +conf.Finish() + +env.Library('gambatte', sourceFiles) diff --git a/supergameboy/libgambatte/include/filterinfo.h b/supergameboy/libgambatte/include/filterinfo.h new file mode 100644 index 00000000..ab5a4726 --- /dev/null +++ b/supergameboy/libgambatte/include/filterinfo.h @@ -0,0 +1,32 @@ +/*************************************************************************** + * Copyright (C) 2007 by Sindre Aam�s * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#ifndef GAMBATTE_FILTERINFO_H +#define GAMBATTE_FILTERINFO_H + +#include + +namespace Gambatte { +struct FilterInfo { + std::string handle; + unsigned int outWidth; + unsigned int outHeight; +}; +} + +#endif diff --git a/supergameboy/libgambatte/include/gambatte.h b/supergameboy/libgambatte/include/gambatte.h new file mode 100644 index 00000000..fc787d76 --- /dev/null +++ b/supergameboy/libgambatte/include/gambatte.h @@ -0,0 +1,82 @@ +/*************************************************************************** + * Copyright (C) 2007 by Sindre Aam�s * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#ifndef GAMBATTE_H +#define GAMBATTE_H + +class CPU; + +#include "videoblitter.h" +#include "inputstate.h" +#include "inputstategetter.h" +#include "filterinfo.h" +#include "int.h" +#include + +namespace Gambatte { +class GB { + CPU *const z80; + int stateNo; + + void loadState(const char *filepath, bool osdMessage); + +public: + GB(); + ~GB(); + bool load(bool forceDmg = false); + + /** Emulates until at least 'samples' stereo sound samples are produced in the supplied buffer. + * There are 35112 stereo sound samples in a video frame. + * May run for uptil 2064 stereo samples too long. + * A stereo sample consists of two native endian 2s complement 16-bit PCM samples, + * with the left sample preceding the right one. Usually casting soundBuf to/from + * short* is OK and recommended. The reason for not using a short* in the interface + * is to avoid implementation defined behaviour without compromising performance. + * + * @param soundBuf buffer with space >= samples + 2064 + * @param samples number of stereo samples to produce + * @return actual number of samples produced + */ + unsigned runFor(Gambatte::uint_least32_t *soundBuf, unsigned samples); + void updateVideo(); + unsigned lyCounter(); + + void reset(); + void setVideoBlitter(VideoBlitter *vb); + void videoBufferChange(); + unsigned videoWidth() const; + unsigned videoHeight() const; + void setDmgPaletteColor(unsigned palNum, unsigned colorNum, unsigned rgb32); + + void setVideoFilter(unsigned n); + std::vector filterInfo() const; + void setInputStateGetter(InputStateGetter *getInput); + + void set_savedir(const char *sdir); + bool isCgb() const; + void saveSavedata(); + void saveState(); + void loadState(); + void saveState(const char *filepath); + void loadState(const char *filepath); + void selectState(int n); + int currentState() const { return stateNo; } +}; +} + +#endif diff --git a/supergameboy/libgambatte/include/inputstate.h b/supergameboy/libgambatte/include/inputstate.h new file mode 100644 index 00000000..bdfec44f --- /dev/null +++ b/supergameboy/libgambatte/include/inputstate.h @@ -0,0 +1,30 @@ +/*************************************************************************** + * Copyright (C) 2007 by Sindre Aam�s * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#ifndef GAMBATTE_INPUTSTATE_H +#define GAMBATTE_INPUTSTATE_H + +namespace Gambatte { +struct InputState { + unsigned joypadId; + bool startButton, selectButton, bButton, aButton; + bool dpadDown, dpadUp, dpadLeft, dpadRight; +}; +} + +#endif diff --git a/supergameboy/libgambatte/include/inputstategetter.h b/supergameboy/libgambatte/include/inputstategetter.h new file mode 100644 index 00000000..375dad5e --- /dev/null +++ b/supergameboy/libgambatte/include/inputstategetter.h @@ -0,0 +1,30 @@ +/*************************************************************************** + * Copyright (C) 2007 by Sindre Aam�s * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#ifndef GAMBATTE_INPUTSTATEGETTER_H +#define GAMBATTE_INPUTSTATEGETTER_H + +namespace Gambatte { +class InputStateGetter { +public: + virtual ~InputStateGetter() {}; + virtual const InputState& operator()() = 0; +}; +} + +#endif diff --git a/supergameboy/libgambatte/include/int.h b/supergameboy/libgambatte/include/int.h new file mode 100644 index 00000000..116ab8b7 --- /dev/null +++ b/supergameboy/libgambatte/include/int.h @@ -0,0 +1,29 @@ +/*************************************************************************** + * Copyright (C) 2007 by Sindre Aam�s * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#ifndef GAMBATTE_INT_H +#define GAMBATTE_INT_H + +#include + +namespace Gambatte { +typedef uint32_t uint_least32_t; +typedef uint16_t uint_least16_t; +} + +#endif diff --git a/supergameboy/libgambatte/include/videoblitter.h b/supergameboy/libgambatte/include/videoblitter.h new file mode 100644 index 00000000..d2a9c335 --- /dev/null +++ b/supergameboy/libgambatte/include/videoblitter.h @@ -0,0 +1,44 @@ +/*************************************************************************** + * Copyright (C) 2007 by Sindre Aam�s * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#ifndef GAMBATTE_VIDEOBLITTER_H +#define GAMBATTE_VIDEOBLITTER_H + +namespace Gambatte { + +struct PixelBuffer { + enum Format { RGB32, RGB16, UYVY }; + + void *pixels; + Format format; + unsigned pitch; + + PixelBuffer() : pixels(0), format(RGB32), pitch(0) {} +}; + +class VideoBlitter { +public: + virtual void setBufferDimensions(unsigned width, unsigned height) = 0; + virtual const PixelBuffer inBuffer() = 0; + virtual void blit() = 0; + virtual ~VideoBlitter() {} +}; + +} + +#endif diff --git a/supergameboy/libgambatte/src/bitmap_font.cpp b/supergameboy/libgambatte/src/bitmap_font.cpp new file mode 100644 index 00000000..b644c06f --- /dev/null +++ b/supergameboy/libgambatte/src/bitmap_font.cpp @@ -0,0 +1,328 @@ +/*************************************************************************** + * Copyright (C) 2008 by Sindre Aamås * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +/* + The following font bitmaps (static const unsigned char *_bits[]), only used + as data and included in this source file for convenience, are derived from + the Bitstream Vera Sans font, which is distributed under the following + copyright: + + Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream Vera + is a trademark of Bitstream, Inc. + + Permission is hereby granted, free of charge, to any person obtaining a + copy of the fonts accompanying this license ("Fonts") and associated + documentation files (the "Font Software"), to reproduce and distribute the + Font Software, including without limitation the rights to use, copy, merge, + publish, distribute, and/or sell copies of the Font Software, and to permit + persons to whom the Font Software is furnished to do so, subject to the + following conditions: + + The above copyright and trademark notices and this permission notice shall + be included in all copies of one or more of the Font Software typefaces. + + The Font Software may be modified, altered, or added to, and in particular + the designs of glyphs or characters in the Fonts may be modified and + additional glyphs or characters may be added to the Fonts, only if the fonts + are renamed to names not containing either the words "Bitstream" or the word + "Vera". + + This License becomes null and void to the extent applicable to Fonts or Font + Software that has been modified and is distributed under the "Bitstream Vera" + names. + + The Font Software may be sold as part of a larger software package but no + copy of one or more of the Font Software typefaces may be sold by itself. + + THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF + COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL BITSTREAM + OR THE GNOME FOUNDATION BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR + CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM + OTHER DEALINGS IN THE FONT SOFTWARE. + + Except as contained in this notice, the names of Gnome, the Gnome + Foundation, and Bitstream Inc., shall not be used in advertising or + otherwise to promote the sale, use or other dealings in this Font Software + without prior written authorization from the Gnome Foundation or + Bitstream Inc., respectively. For further information, contact: fonts at + gnome dot org. +*/ + +#include "bitmap_font.h" + +static const unsigned char n0_bits[] = { 0x68, + 0x00, 0x1c, 0x22, 0x22, 0x22, 0x22, 0x22, 0x1c }; + +static const unsigned char n1_bits[] = { 0x68, + 0x00, 0x0e, 0x08, 0x08, 0x08, 0x08, 0x08, 0x3e }; + +static const unsigned char n2_bits[] = { 0x68, + 0x00, 0x1c, 0x22, 0x20, 0x10, 0x08, 0x04, 0x3e }; + +static const unsigned char n3_bits[] = { 0x68, + 0x00, 0x1c, 0x22, 0x20, 0x1c, 0x20, 0x22, 0x1c }; + +static const unsigned char n4_bits[] = { 0x68, + 0x00, 0x18, 0x18, 0x14, 0x12, 0x3e, 0x10, 0x10 }; + +static const unsigned char n5_bits[] = { 0x68, + 0x00, 0x1e, 0x02, 0x1e, 0x20, 0x20, 0x20, 0x1e }; + +static const unsigned char n6_bits[] = { 0x68, + 0x00, 0x3c, 0x06, 0x02, 0x1e, 0x22, 0x22, 0x1c }; + +static const unsigned char n7_bits[] = { 0x68, + 0x00, 0x3e, 0x20, 0x10, 0x10, 0x08, 0x08, 0x04 }; + +static const unsigned char n8_bits[] = { 0x68, + 0x00, 0x1c, 0x22, 0x22, 0x1c, 0x22, 0x22, 0x1c }; + +static const unsigned char n9_bits[] = { 0x68, + 0x00, 0x1c, 0x22, 0x22, 0x3c, 0x20, 0x30, 0x1e }; + +static const unsigned char A_bits[] = { 0x78, + 0x00, 0x08, 0x14, 0x14, 0x22, 0x3e, 0x22, 0x41 }; + +static const unsigned char a_bits[] = { 0x68, + 0x00, 0x00, 0x00, 0x1c, 0x20, 0x3c, 0x22, 0x3e }; + +static const unsigned char B_bits[] = { 0x78, + 0x00, 0x1e, 0x22, 0x22, 0x1e, 0x22, 0x22, 0x1e }; + +static const unsigned char b_bits[] = { 0x68, + 0x02, 0x02, 0x02, 0x1e, 0x22, 0x22, 0x22, 0x1e }; + +static const unsigned char C_bits[] = { 0x88, + 0x00, 0x38, 0x44, 0x02, 0x02, 0x02, 0x44, 0x38 }; + +static const unsigned char c_bits[] = { 0x58, + 0x00, 0x00, 0x00, 0x1c, 0x02, 0x02, 0x02, 0x1c }; + +static const unsigned char D_bits[] = { 0x88, + 0x00, 0x3e, 0x62, 0x42, 0x42, 0x42, 0x62, 0x3e }; + +static const unsigned char d_bits[] = { 0x68, + 0x20, 0x20, 0x20, 0x3c, 0x22, 0x22, 0x22, 0x3c }; + +static const unsigned char E_bits[] = { 0x78, + 0x00, 0x3e, 0x02, 0x02, 0x3e, 0x02, 0x02, 0x3e }; + +static const unsigned char e_bits[] = { 0x68, + 0x00, 0x00, 0x00, 0x1c, 0x22, 0x3e, 0x02, 0x3c }; + +static const unsigned char F_bits[] = { 0x68, + 0x00, 0x1e, 0x02, 0x02, 0x1e, 0x02, 0x02, 0x02 }; + +static const unsigned char f_bits[] = { 0x48, + 0x0e, 0x02, 0x02, 0x07, 0x02, 0x02, 0x02, 0x02 }; + +static const unsigned char G_bits[] = { 0x88, + 0x00, 0x3c, 0x46, 0x02, 0x72, 0x42, 0x46, 0x3c }; + +static const unsigned char g_bits[] = { 0x6a, + 0x00, 0x00, 0x00, 0x3c, 0x22, 0x22, 0x22, 0x3c, 0x20, 0x1c }; + +static const unsigned char H_bits[] = { 0x88, + 0x00, 0x42, 0x42, 0x42, 0x7e, 0x42, 0x42, 0x42 }; + +static const unsigned char h_bits[] = { 0x68, + 0x02, 0x02, 0x02, 0x1e, 0x22, 0x22, 0x22, 0x22 }; + +static const unsigned char I_bits[] = { 0x38, + 0x00, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02 }; + +static const unsigned char i_bits[] = { 0x28, + 0x02, 0x00, 0x00, 0x02, 0x02, 0x02, 0x02, 0x02 }; + +static const unsigned char J_bits[] = { 0x4a, + 0x00, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x03 }; + +static const unsigned char j_bits[] = { 0x2a, + 0x02, 0x00, 0x00, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x03 }; + +static const unsigned char K_bits[] = { 0x78, + 0x00, 0x22, 0x12, 0x0a, 0x06, 0x0a, 0x12, 0x22 }; + +static const unsigned char k_bits[] = { 0x58, + 0x02, 0x02, 0x02, 0x12, 0x0a, 0x06, 0x0a, 0x12 }; + +static const unsigned char L_bits[] = { 0x68, + 0x00, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x3e }; + +static const unsigned char l_bits[] = { 0x28, + 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02 }; + +static const unsigned char M_bits[] = { 0x98, + 0x00, 0x00, 0x82, 0x00, 0xc6, 0x00, 0xc6, 0x00, 0xaa, 0x00, 0xaa, 0x00, + 0x92, 0x00, 0x82, 0x00 }; + +static const unsigned char m_bits[] = { 0xa8, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xde, 0x01, 0x22, 0x02, 0x22, 0x02, + 0x22, 0x02, 0x22, 0x02 }; + +static const unsigned char N_bits[] = { 0x88, + 0x00, 0x42, 0x46, 0x4a, 0x4a, 0x52, 0x62, 0x42 }; + +static const unsigned char n_bits[] = { 0x68, + 0x00, 0x00, 0x00, 0x1e, 0x22, 0x22, 0x22, 0x22 }; + +static const unsigned char O_bits[] = { 0x88, + 0x00, 0x3c, 0x66, 0x42, 0x42, 0x42, 0x66, 0x3c }; + +static const unsigned char o_bits[] = { 0x68, + 0x00, 0x00, 0x00, 0x1c, 0x22, 0x22, 0x22, 0x1c }; + +static const unsigned char P_bits[] = { 0x78, + 0x00, 0x1e, 0x22, 0x22, 0x1e, 0x02, 0x02, 0x02 }; + +static const unsigned char p_bits[] = { 0x6a, + 0x00, 0x00, 0x00, 0x1e, 0x22, 0x22, 0x22, 0x1e, 0x02, 0x02 }; + +static const unsigned char Q_bits[] = { 0x89, + 0x00, 0x3c, 0x66, 0x42, 0x42, 0x42, 0x26, 0x1c, 0x20 }; + +static const unsigned char q_bits[] = { 0x6a, + 0x00, 0x00, 0x00, 0x3c, 0x22, 0x22, 0x22, 0x3c, 0x20, 0x20 }; + +static const unsigned char R_bits[] = { 0x78, + 0x00, 0x1e, 0x22, 0x22, 0x1e, 0x12, 0x22, 0x42 }; + +static const unsigned char r_bits[] = { 0x48, + 0x00, 0x00, 0x00, 0x0e, 0x02, 0x02, 0x02, 0x02 }; + +static const unsigned char S_bits[] = { 0x78, + 0x00, 0x1c, 0x22, 0x02, 0x1c, 0x20, 0x22, 0x1c }; + +static const unsigned char s_bits[] = { 0x58, + 0x00, 0x00, 0x00, 0x1e, 0x02, 0x1c, 0x10, 0x1e }; + +static const unsigned char T_bits[] = { 0x58, + 0x00, 0x1f, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04 }; + +static const unsigned char t_bits[] = { 0x48, + 0x00, 0x02, 0x02, 0x0f, 0x02, 0x02, 0x02, 0x0e }; + +static const unsigned char U_bits[] = { 0x88, + 0x00, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x3c }; + +static const unsigned char u_bits[] = { 0x68, + 0x00, 0x00, 0x00, 0x22, 0x22, 0x22, 0x22, 0x3c }; + +static const unsigned char V_bits[] = { 0x78, + 0x00, 0x41, 0x41, 0x22, 0x22, 0x14, 0x14, 0x08 }; + +static const unsigned char v_bits[] = { 0x68, + 0x00, 0x00, 0x00, 0x22, 0x22, 0x14, 0x14, 0x08 }; + +static const unsigned char W_bits[] = { 0x98, + 0x00, 0x00, 0x11, 0x01, 0x11, 0x01, 0xaa, 0x00, 0xaa, 0x00, 0xaa, 0x00, + 0x44, 0x00, 0x44, 0x00 }; + +static const unsigned char w_bits[] = { 0x88, + 0x00, 0x00, 0x00, 0x92, 0xaa, 0xaa, 0x44, 0x44 }; + +static const unsigned char X_bits[] = { 0x68, + 0x00, 0x21, 0x12, 0x0c, 0x0c, 0x0c, 0x12, 0x21 }; + +static const unsigned char x_bits[] = { 0x68, + 0x00, 0x00, 0x00, 0x22, 0x14, 0x08, 0x14, 0x22 }; + +static const unsigned char Y_bits[] = { 0x78, + 0x00, 0x41, 0x22, 0x14, 0x08, 0x08, 0x08, 0x08 }; + +static const unsigned char y_bits[] = { 0x6a, + 0x00, 0x00, 0x00, 0x22, 0x22, 0x14, 0x14, 0x08, 0x08, 0x06 }; + +static const unsigned char Z_bits[] = { 0x68, + 0x00, 0x3f, 0x10, 0x08, 0x0c, 0x04, 0x02, 0x3f }; + +static const unsigned char z_bits[] = { 0x58, + 0x00, 0x00, 0x00, 0x1e, 0x10, 0x08, 0x04, 0x1e }; + +static const unsigned char SPC_bits[] = { 0x38, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + +namespace BitmapFont { +const unsigned char *const font[] = { + 0, + n0_bits, n1_bits, n2_bits, n3_bits, n4_bits, n5_bits, n6_bits, n7_bits, n8_bits, n9_bits, + A_bits, B_bits, C_bits, D_bits, E_bits, F_bits, G_bits, H_bits, I_bits, J_bits, K_bits, L_bits, M_bits, + N_bits, O_bits, P_bits, Q_bits, R_bits, S_bits, T_bits, U_bits, V_bits, W_bits, X_bits, Y_bits, Z_bits, + a_bits, b_bits, c_bits, d_bits, e_bits, f_bits, g_bits, h_bits, i_bits, j_bits, k_bits, l_bits, m_bits, + n_bits, o_bits, p_bits, q_bits, r_bits, s_bits, t_bits, u_bits, v_bits, w_bits, x_bits, y_bits, z_bits, + SPC_bits +}; + +unsigned getWidth(const char *chars) { + unsigned w = 0; + + while (const int character = *chars++) { + w += *font[character] >> 4; + } + + return w; +} + +class Rgb32Fill { + const unsigned long color; + +public: + Rgb32Fill(unsigned long color) : color(color) {} + + void operator()(Gambatte::uint_least32_t *dest, unsigned /*pitch*/) { + *dest = color; + } +}; + +void print(Gambatte::uint_least32_t *dest, const unsigned pitch, const unsigned long color, const char *chars) { + print(dest, pitch, Rgb32Fill(color), chars); +} + +static void reverse(char *first, char *last) { + while (first < last) { + const int tmp = *first; + + *first = *last; + *last = tmp; + + ++first; + --last; + } +} + +void utoa(unsigned u, char *a) { + char *aa = a; + + while (u > 9) { + const unsigned div = u / 10; + const unsigned rem = u % 10; + + u = div; + *aa++ = rem + N0; + } + + *aa = u + N0; + + reverse(a, aa); +} +} diff --git a/supergameboy/libgambatte/src/bitmap_font.h b/supergameboy/libgambatte/src/bitmap_font.h new file mode 100644 index 00000000..8217cf61 --- /dev/null +++ b/supergameboy/libgambatte/src/bitmap_font.h @@ -0,0 +1,87 @@ +/*************************************************************************** + * Copyright (C) 2008 by Sindre Aamås * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#ifndef BITMAP_FONT_H +#define BITMAP_FONT_H + +#include "int.h" + +namespace BitmapFont { +enum Char { + NUL, + N0, N1, N2, N3, N4, N5, N6, N7, N8, N9, + A, B, C, D, E, F, G, H, I, J, K, L, M, + N, O, P, Q, R, S, T, U, V, W, X, Y, Z, + a, b, c, d, e, f, g, h, i, j, k, l, m, + n, o, p, q, r, s, t, u, v, w, x, y, z, + SPC +}; + +enum { HEIGHT = 10 }; +enum { MAX_WIDTH = 9 }; +enum { NUMBER_WIDTH = 6 }; + +unsigned getWidth(const char *chars); + +// struct Fill { void operator()(RandomAccessIterator dest, unsigned pitch) { fill pixels at dest } } +template +void print(RandomAccessIterator dest, unsigned pitch, Fill fill, const char *chars); + +void print(Gambatte::uint_least32_t *dest, unsigned pitch, unsigned long color, const char *chars); +void utoa(unsigned u, char *a); + +// --- INTERFACE END --- + + + +extern const unsigned char *const font[]; + +template +void print(RandomAccessIterator dest, const unsigned pitch, Fill fill, const char *chars) { + while (const int character = *chars++) { + RandomAccessIterator dst = dest; + const unsigned char *s = font[character]; + + const unsigned width = *s >> 4; + unsigned h = *s++ & 0xF; + + while (h--) { + RandomAccessIterator d = dst; + + unsigned line = *s++; + + if (width > 8) + line |= *s++ << 8; + + while (line) { + if (line & 1) + fill(d, pitch); + + line >>= 1; + ++d; + } + + dst += pitch; + } + + dest += width; + } +} +} + +#endif diff --git a/supergameboy/libgambatte/src/colorconversion.cpp b/supergameboy/libgambatte/src/colorconversion.cpp new file mode 100644 index 00000000..d76b0aee --- /dev/null +++ b/supergameboy/libgambatte/src/colorconversion.cpp @@ -0,0 +1,96 @@ +/*************************************************************************** + * Copyright (C) 2008 by Sindre Aam�s * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#include "colorconversion.h" +#include + +Rgb32ToUyvy::Rgb32ToUyvy() { +#ifdef WORDS_BIGENDIAN + const CacheUnit c = { 0, 128ul << 24 | 16ul << 16 | 128 << 8 | 16 }; +#else + const CacheUnit c = { 0, 16ul << 24 | 128ul << 16 | 16 << 8 | 128 }; +#endif + std::fill(cache, cache + cache_size, c); +} + +void Rgb32ToUyvy::operator()(const Gambatte::uint_least32_t *s, Gambatte::uint_least32_t *d, const unsigned w, unsigned h, const unsigned d_pitch) { + while (h--) { + for (const Gambatte::uint_least32_t *const ends = s + w; s != ends;) { + if ((cache[*s & cache_mask].rgb32 - *s) | (cache[*(s+1) & cache_mask].rgb32 - *(s+1))) { + cache[*s & cache_mask].rgb32 = *s; + cache[*(s+1) & cache_mask].rgb32 = *(s+1); + + const unsigned long r = (*s >> 16 & 0x000000FF) | (*(s+1) & 0x00FF0000); + const unsigned long g = (*s >> 8 & 0x000000FF) | (*(s+1) << 8 & 0x00FF0000); + const unsigned long b = (*s & 0x000000FF) | (*(s+1) << 16 & 0x00FF0000); + + const unsigned long y = r * 66 + g * 129 + b * 25 + (16 * 256 + 128) * 0x00010001ul; + const unsigned long u = b * 112 - r * 38 - g * 74 + (128 * 256 + 128) * 0x00010001ul; + const unsigned long v = r * 112 - g * 94 - b * 18 + (128 * 256 + 128) * 0x00010001ul; + +#ifdef WORDS_BIGENDIAN + *d++ = cache[*s & cache_mask].uyvy = (u << 16 & 0xFF000000) | (y << 8 & 0x00FF0000) | (v & 0x0000FF00) | (y >> 8 & 0x000000FF); + *d++ = cache[*(s+1) & cache_mask].uyvy = (u & 0xFF000000) | (y >> 8 & 0x00FF0000) | (v >> 16 & 0x0000FF00) | y >> 24; +#else + *d++ = cache[*s & cache_mask].uyvy = (y << 16 & 0xFF000000) | (v << 8 & 0x00FF0000) | (y & 0x0000FF00) | (u >> 8 & 0x000000FF); + *d++ = cache[*(s+1) & cache_mask].uyvy = (y & 0xFF000000) | (v >> 8 & 0x00FF0000) | (y >> 16 & 0x0000FF00) | u >> 24; +#endif + } else { + *d++ = cache[*s & cache_mask].uyvy; + *d++ = cache[*(s+1) & cache_mask].uyvy; + } + + s += 2; + } + + d += d_pitch - w; + } +} + +unsigned long rgb32ToUyvy(unsigned long rgb32) { + const unsigned r = rgb32 >> 16 & 0xFF; + const unsigned g = rgb32 >> 8 & 0xFF; + const unsigned b = rgb32 & 0xFF; + + const unsigned long y = (r * 66 + g * 129 + b * 25 + 16 * 256 + 128) >> 8; + const unsigned long u = (b * 112 - r * 38 - g * 74 + 128 * 256 + 128) >> 8; + const unsigned long v = (r * 112 - g * 94 - b * 18 + 128 * 256 + 128) >> 8; + +#ifdef WORDS_BIGENDIAN + return u << 24 | y << 16 | v << 8 | y; +#else + return y << 24 | v << 16 | y << 8 | u; +#endif +} + +void rgb32ToRgb16(const Gambatte::uint_least32_t *s, Gambatte::uint_least16_t *d, const unsigned w, unsigned h, const unsigned dstPitch) { + do { + unsigned n = w; + + do { + *d++ = (*s >> 8 & 0xF800) | (*s >> 5 & 0x07E0) | (*s >> 3 & 0x001F); + ++s; + } while (--n); + + d += dstPitch - w; + } while (--h); +} + +unsigned rgb32ToRgb16(const unsigned long rgb32) { + return (rgb32 >> 8 & 0xF800) | (rgb32 >> 5 & 0x07E0) | (rgb32 >> 3 & 0x001F); +} diff --git a/supergameboy/libgambatte/src/colorconversion.h b/supergameboy/libgambatte/src/colorconversion.h new file mode 100644 index 00000000..9323015e --- /dev/null +++ b/supergameboy/libgambatte/src/colorconversion.h @@ -0,0 +1,46 @@ +/*************************************************************************** + * Copyright (C) 2008 by Sindre Aamås * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#ifndef COLORCONVERSION_H +#define COLORCONVERSION_H + +#include "int.h" +#include + +class Rgb32ToUyvy { + struct CacheUnit { + Gambatte::uint_least32_t rgb32; + Gambatte::uint_least32_t uyvy; + }; + + enum { cache_size = 0x100 }; + enum { cache_mask = cache_size - 1 }; + + CacheUnit cache[cache_size]; + +public: + Rgb32ToUyvy(); + void operator()(const Gambatte::uint_least32_t *s, Gambatte::uint_least32_t *d, unsigned w, unsigned h, unsigned dstPitch); +}; + +unsigned long rgb32ToUyvy(unsigned long rgb32); + +void rgb32ToRgb16(const Gambatte::uint_least32_t *s, Gambatte::uint_least16_t *d, unsigned w, unsigned h, unsigned dstPitch); +unsigned rgb32ToRgb16(unsigned long rgb32); + +#endif diff --git a/supergameboy/libgambatte/src/cpu.cpp b/supergameboy/libgambatte/src/cpu.cpp new file mode 100644 index 00000000..c44b1239 --- /dev/null +++ b/supergameboy/libgambatte/src/cpu.cpp @@ -0,0 +1,2842 @@ +/*************************************************************************** + * Copyright (C) 2007 by Sindre Aam�s * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#include "cpu.h" +#include "memory.h" +#include "savestate.h" + +CPU::CPU() : +memory(Interrupter(SP, PC_, halted)), +cycleCounter_(0), +PC_(0x100), +SP(0xFFFE), +HF1(0xF), +HF2(0xF), +ZF(0), +CF(0x100), +A_(0x01), +B(0x00), +C(0x13), +D(0x00), +E(0xD8), +H(0x01), +L(0x4D), +skip(false), +halted(false) +{} + +void CPU::runFor(const unsigned long cycles) { + process(cycles/* << memory.isDoubleSpeed()*/); + + if (cycleCounter_ & 0x80000000) + cycleCounter_ = memory.resetCounters(cycleCounter_); +} + +bool CPU::load(const bool forceDmg) { + bool tmp = memory.loadROM(forceDmg); + + return tmp; +} + +/*void CPU::halt() { + while (halted) { + const uint_fast32_t cycles = memory.next_eventtime - memory.CycleCounter; + memory.CycleCounter += cycles + ((4 - (cycles & 3)) & 3); + memory.event(); + } +}*/ + +//Push address of next instruction onto stack and then jump to interrupt address (0x40-0x60): +/*unsigned CPU::interrupt(const unsigned address, unsigned cycleCounter) { + if (halted && memory.isCgb()) + cycleCounter += 4; + + halted = false; + cycleCounter += 8; + memory.write(--SP, PC_ >> 8, cycleCounter); + cycleCounter += 4; + memory.write(--SP, PC_ & 0xFF, cycleCounter); + PC_ = address; + cycleCounter += 8; + + return cycleCounter; +}*/ + +// (HF2 & 0x200) == true means HF is set. +// (HF2 & 0x400) marks the subtract flag. +// (HF2 & 0x800) is set for inc/dec. +// (HF2 & 0x100) is set if there's a carry to add. +static void calcHF(const unsigned HF1, unsigned& HF2) { + unsigned arg1 = HF1 & 0xF; + unsigned arg2 = (HF2 & 0xF) + (HF2 >> 8 & 1); + + if (HF2 & 0x800) { + arg1 = arg2; + arg2 = 1; + } + + if (HF2 & 0x400) + arg1 -= arg2; + else + arg1 = (arg1 + arg2) << 5; + + HF2 |= arg1 & 0x200; +} + +#define F() (((HF2 & 0x600) | (CF & 0x100)) >> 4 | ((ZF & 0xFF) ? 0 : 0x80)) + +#define FROM_F(f_in) do { \ + unsigned from_f_var = f_in; \ +\ + ZF = ~from_f_var & 0x80; \ + HF2 = from_f_var << 4 & 0x600; \ + CF = from_f_var << 4 & 0x100; \ +} while (0) + +void CPU::setStatePtrs(SaveState &state) { + memory.setStatePtrs(state); +} + +void CPU::saveState(SaveState &state) { + cycleCounter_ = memory.saveState(state, cycleCounter_); + + calcHF(HF1, HF2); + + state.cpu.cycleCounter = cycleCounter_; + state.cpu.PC = PC_; + state.cpu.SP = SP; + state.cpu.A = A_; + state.cpu.B = B; + state.cpu.C = C; + state.cpu.D = D; + state.cpu.E = E; + state.cpu.F = F(); + state.cpu.H = H; + state.cpu.L = L; + state.cpu.skip = skip; + state.cpu.halted = halted; +} + +void CPU::loadState(const SaveState &state) { + memory.loadState(state, cycleCounter_); + + cycleCounter_ = state.cpu.cycleCounter; + PC_ = state.cpu.PC; + SP = state.cpu.SP; + A_ = state.cpu.A; + B = state.cpu.B; + C = state.cpu.C; + D = state.cpu.D; + E = state.cpu.E; + FROM_F(state.cpu.F); + H = state.cpu.H; + L = state.cpu.L; + skip = state.cpu.skip; + halted = state.cpu.halted; +} + +#define BC() ( B << 8 | C ) +#define DE() ( D << 8 | E ) +#define HL() ( H << 8 | L ) + +#define READ(dest, addr) do { (dest) = memory.read(addr, cycleCounter); cycleCounter += 4; } while (0) +// #define PC_READ(dest, addr) do { (dest) = memory.pc_read(addr, cycleCounter); cycleCounter += 4; } while (0) +#define PC_READ(dest) do { (dest) = memory.read(PC, cycleCounter); PC = (PC + 1) & 0xFFFF; cycleCounter += 4; } while (0) +#define FF_READ(dest, addr) do { (dest) = memory.ff_read(addr, cycleCounter); cycleCounter += 4; } while (0) + +#define WRITE(addr, data) do { memory.write(addr, data, cycleCounter); cycleCounter += 4; } while (0) +#define FF_WRITE(addr, data) do { memory.ff_write(addr, data, cycleCounter); cycleCounter += 4; } while (0) + +#define PC_MOD(data) do { PC = data; cycleCounter += 4; } while (0) + +#define PUSH(r1, r2) do { \ + SP = (SP - 1) & 0xFFFF; \ + WRITE(SP, (r1)); \ + SP = (SP - 1) & 0xFFFF; \ + WRITE(SP, (r2)); \ +} while (0) + +//CB OPCODES (Shifts, rotates and bits): +//swap r (8 cycles): +//Swap upper and lower nibbles of 8-bit register, reset flags, check zero flag: +#define swap_r(r) do { \ + CF = HF2 = 0; \ + ZF = (r); \ + (r) = (ZF << 4 | ZF >> 4) & 0xFF; \ +} while (0) + +//rlc r (8 cycles): +//Rotate 8-bit register left, store old bit7 in CF. Reset SF and HCF, Check ZF: +#define rlc_r(r) do { \ + CF = (r) << 1; \ + ZF = CF | CF >> 8; \ + (r) = ZF & 0xFF; \ + HF2 = 0; \ +} while (0) + +//rl r (8 cycles): +//Rotate 8-bit register left through CF, store old bit7 in CF, old CF value becomes bit0. Reset SF and HCF, Check ZF: +#define rl_r(r) do { \ + const unsigned rl_r_var_oldcf = CF >> 8 & 1; \ + CF = (r) << 1; \ + ZF = CF | rl_r_var_oldcf; \ + (r) = ZF & 0xFF; \ + HF2 = 0; \ +} while (0) + +//rrc r (8 cycles): +//Rotate 8-bit register right, store old bit0 in CF. Reset SF and HCF, Check ZF: +#define rrc_r(r) do { \ + ZF = (r); \ + CF = ZF << 8; \ + (r) = (ZF | CF) >> 1 & 0xFF; \ + HF2 = 0; \ +} while (0) + +//rr r (8 cycles): +//Rotate 8-bit register right through CF, store old bit0 in CF, old CF value becomes bit7. Reset SF and HCF, Check ZF: +#define rr_r(r) do { \ + const unsigned rr_r_var_oldcf = CF & 0x100; \ + CF = (r) << 8; \ + (r) = ZF = ((r) | rr_r_var_oldcf) >> 1; \ + HF2 = 0; \ +} while (0) + +//sla r (8 cycles): +//Shift 8-bit register left, store old bit7 in CF. Reset SF and HCF, Check ZF: +#define sla_r(r) do { \ + ZF = CF = (r) << 1; \ + (r) = ZF & 0xFF; \ + HF2 = 0; \ +} while (0) + +//sra r (8 cycles): +//Shift 8-bit register right, store old bit0 in CF. bit7=old bit7. Reset SF and HCF, Check ZF: +#define sra_r(r) do { \ + CF = (r) << 8; \ + ZF = (r) >> 1; \ + (r) = ZF | ((r) & 0x80); \ + HF2 = 0; \ +} while (0) + +//srl r (8 cycles): +//Shift 8-bit register right, store old bit0 in CF. Reset SF and HCF, Check ZF: +#define srl_r(r) do { \ + ZF = (r); \ + CF = (r) << 8; \ + ZF >>= 1; \ + (r) = ZF; \ + HF2 = 0; \ +} while (0) + +//bit n,r (8 cycles): +//bit n,(hl) (12 cycles): +//Test bitn in 8-bit value, check ZF, unset SF, set HCF: +#define bitn_u8(bitmask, u8) do { \ + ZF = (u8) & (bitmask); \ + HF2 = 0x200; \ +} while (0) + +#define bit0_u8(u8) bitn_u8(1, (u8)) +#define bit1_u8(u8) bitn_u8(2, (u8)) +#define bit2_u8(u8) bitn_u8(4, (u8)) +#define bit3_u8(u8) bitn_u8(8, (u8)) +#define bit4_u8(u8) bitn_u8(0x10, (u8)) +#define bit5_u8(u8) bitn_u8(0x20, (u8)) +#define bit6_u8(u8) bitn_u8(0x40, (u8)) +#define bit7_u8(u8) bitn_u8(0x80, (u8)) + +//set n,r (8 cycles): +//Set bitn of 8-bit register: +#define set0_r(r) ( (r) |= 0x1 ) +#define set1_r(r) ( (r) |= 0x2 ) +#define set2_r(r) ( (r) |= 0x4 ) +#define set3_r(r) ( (r) |= 0x8 ) +#define set4_r(r) ( (r) |= 0x10 ) +#define set5_r(r) ( (r) |= 0x20 ) +#define set6_r(r) ( (r) |= 0x40 ) +#define set7_r(r) ( (r) |= 0x80 ) + +//set n,(hl) (16 cycles): +//Set bitn of value at address stored in HL: +#define setn_mem_hl(n) do { \ + const unsigned setn_mem_hl_var_addr = HL(); \ + unsigned setn_mem_hl_var_tmp; \ +\ + READ(setn_mem_hl_var_tmp, setn_mem_hl_var_addr); \ + setn_mem_hl_var_tmp |= 1 << (n); \ +\ + WRITE(setn_mem_hl_var_addr, setn_mem_hl_var_tmp); \ +} while (0) + +//res n,r (8 cycles): +//Unset bitn of 8-bit register: +#define res0_r(r) ( (r) &= 0xFE ) +#define res1_r(r) ( (r) &= 0xFD ) +#define res2_r(r) ( (r) &= 0xFB ) +#define res3_r(r) ( (r) &= 0xF7 ) +#define res4_r(r) ( (r) &= 0xEF ) +#define res5_r(r) ( (r) &= 0xDF ) +#define res6_r(r) ( (r) &= 0xBF ) +#define res7_r(r) ( (r) &= 0x7F ) + +//res n,(hl) (16 cycles): +//Unset bitn of value at address stored in HL: +#define resn_mem_hl(n) do { \ + const unsigned resn_mem_hl_var_addr = HL(); \ + unsigned resn_mem_hl_var_tmp; \ +\ + READ(resn_mem_hl_var_tmp, resn_mem_hl_var_addr); \ + resn_mem_hl_var_tmp &= ~(1 << (n)); \ +\ + WRITE(resn_mem_hl_var_addr, resn_mem_hl_var_tmp); \ +} while (0) + + +//16-BIT LOADS: +//ld rr,nn (12 cycles) +//set rr to 16-bit value of next 2 bytes in memory +#define ld_rr_nn(r1, r2) do { \ + PC_READ(r2); \ + PC_READ(r1); \ +} while (0) + +//push rr (16 cycles): +//Push value of register pair onto stack: +#define push_rr(r1, r2) do { \ + PUSH(r1, r2); \ + cycleCounter += 4; \ +} while (0) + +//pop rr (12 cycles): +//Pop two bytes off stack into register pair: +#define pop_rr(r1, r2) do { \ + READ(r2, SP); \ + SP = (SP + 1) & 0xFFFF; \ + READ(r1, SP); \ + SP = (SP + 1) & 0xFFFF; \ +} while (0) + +//8-BIT ALU: +//add a,r (4 cycles): +//add a,(addr) (8 cycles): +//Add 8-bit value to A, check flags: +#define add_a_u8(u8) do { \ + HF1 = A; \ + HF2 = u8; \ + ZF = CF = A + HF2; \ + A = ZF & 0xFF; \ +} while (0) + +//adc a,r (4 cycles): +//adc a,(addr) (8 cycles): +//Add 8-bit value+CF to A, check flags: +#define adc_a_u8(u8) do { \ + HF1 = A; \ + HF2 = (CF & 0x100) | (u8); \ + ZF = CF = (CF >> 8 & 1) + (u8) + A; \ + A = ZF & 0xFF; \ +} while (0) + +//sub a,r (4 cycles): +//sub a,(addr) (8 cycles): +//Subtract 8-bit value from A, check flags: +#define sub_a_u8(u8) do { \ + HF1 = A; \ + HF2 = u8; \ + ZF = CF = A - HF2; \ + A = ZF & 0xFF; \ + HF2 |= 0x400; \ +} while (0) + +//sbc a,r (4 cycles): +//sbc a,(addr) (8 cycles): +//Subtract CF and 8-bit value from A, check flags: +#define sbc_a_u8(u8) do { \ + HF1 = A; \ + HF2 = 0x400 | (CF & 0x100) | (u8); \ + ZF = CF = A - ((CF >> 8) & 1) - (u8); \ + A = ZF & 0xFF; \ +} while (0) + +//and a,r (4 cycles): +//and a,(addr) (8 cycles): +//bitwise and 8-bit value into A, check flags: +#define and_a_u8(u8) do { \ + HF2 = 0x200; \ + CF = 0; \ + A &= (u8); \ + ZF = A; \ +} while (0) + +//or a,r (4 cycles): +//or a,(hl) (8 cycles): +//bitwise or 8-bit value into A, check flags: +#define or_a_u8(u8) do { \ + CF = HF2 = 0; \ + A |= (u8); \ + ZF = A; \ +} while (0) + +//xor a,r (4 cycles): +//xor a,(hl) (8 cycles): +//bitwise xor 8-bit value into A, check flags: +#define xor_a_u8(u8) do { \ + CF = HF2 = 0; \ + A ^= (u8); \ + ZF = A; \ +} while (0) + +//cp a,r (4 cycles): +//cp a,(addr) (8 cycles): +//Compare (subtract without storing result) 8-bit value to A, check flags: +#define cp_a_u8(u8) do { \ + HF1 = A; \ + HF2 = u8; \ + ZF = CF = A - HF2; \ + HF2 |= 0x400; \ +} while (0) + +//inc r (4 cycles): +//Increment value of 8-bit register, check flags except CF: +#define inc_r(r) do { \ + HF2 = (r) | 0x800; \ + ZF = (r) + 1; \ + (r) = ZF & 0xFF; \ +} while (0) + +//dec r (4 cycles): +//Decrement value of 8-bit register, check flags except CF: +#define dec_r(r) do { \ + HF2 = (r) | 0xC00; \ + ZF = (r) - 1; \ + (r) = ZF & 0xFF; \ +} while (0) + +//16-BIT ARITHMETIC +//add hl,rr (8 cycles): +//add 16-bit register to HL, check flags except ZF: +/*#define add_hl_rr(rh, rl) do { \ + L = HF1 = L + (rl); \ + HF1 >>= 8; \ + HF1 += H; \ + HF2 = (rh); \ + H = CF = HF1 + (rh); \ + cycleCounter += 4; \ +} while (0)*/ + +#define add_hl_rr(rh, rl) do { \ + CF = L + (rl); \ + L = CF & 0xFF; \ + HF1 = H; \ + HF2 = (CF & 0x100) | (rh); \ + CF = H + (CF >> 8) + (rh); \ + H = CF & 0xFF; \ + cycleCounter += 4; \ +} while (0) + +//inc rr (8 cycles): +//Increment 16-bit register: +#define inc_rr(rh, rl) do { \ + const unsigned inc_rr_var_tmp = (rl) + 1; \ + (rl) = inc_rr_var_tmp & 0xFF; \ + (rh) = ((rh) + (inc_rr_var_tmp >> 8)) & 0xFF; \ + cycleCounter += 4; \ +} while (0) + +//dec rr (8 cycles): +//Decrement 16-bit register: +#define dec_rr(rh, rl) do { \ + const unsigned dec_rr_var_tmp = (rl) - 1; \ + (rl) = dec_rr_var_tmp & 0xFF; \ + (rh) = ((rh) - (dec_rr_var_tmp >> 8 & 1)) & 0xFF; \ + cycleCounter += 4; \ +} while (0) + +#define sp_plus_n(sumout) do { \ + unsigned sp_plus_n_var_n; \ + PC_READ(sp_plus_n_var_n); \ + sp_plus_n_var_n = (sp_plus_n_var_n ^ 0x80) - 0x80; \ + \ + const unsigned sp_plus_n_var_sum = SP + sp_plus_n_var_n; \ + CF = SP ^ sp_plus_n_var_n ^ sp_plus_n_var_sum; \ + HF2 = CF << 5 & 0x200; \ + ZF = 1; \ + cycleCounter += 4; \ + (sumout) = sp_plus_n_var_sum & 0xFFFF; \ +} while (0) + +//JUMPS: +//jp nn (16 cycles): +//Jump to address stored in the next two bytes in memory: +#define jp_nn() do { \ + unsigned jp_nn_var_l, jp_nn_var_h; \ +\ + PC_READ(jp_nn_var_l); \ + PC_READ(jp_nn_var_h); \ +\ + PC_MOD(jp_nn_var_h << 8 | jp_nn_var_l); \ +} while (0) + +//jr disp (12 cycles): +//Jump to value of next (signed) byte in memory+current address: +#define jr_disp() do { \ + unsigned jr_disp_var_tmp; \ +\ + PC_READ(jr_disp_var_tmp); \ + jr_disp_var_tmp = (jr_disp_var_tmp ^ 0x80) - 0x80; \ +\ + PC_MOD((PC + jr_disp_var_tmp) & 0xFFFF); \ +} while (0) + +//CALLS, RESTARTS AND RETURNS: +//call nn (24 cycles): +//Push address of next instruction onto stack and then jump to address stored in next two bytes in memory: +#define call_nn() do { \ + PUSH(((PC + 2) >> 8) & 0xFF, (PC + 2) & 0xFF); \ + jp_nn(); \ +} while (0) + +//rst n (16 Cycles): +//Push present address onto stack, jump to address n (one of 00h,08h,10h,18h,20h,28h,30h,38h): +#define rst_n(n) do { \ + PUSH(PC >> 8, PC & 0xFF); \ + PC_MOD(n); \ +} while (0) + +//ret (16 cycles): +//Pop two bytes from the stack and jump to that address: +#define ret() do { \ + unsigned ret_var_l, ret_var_h; \ +\ + pop_rr(ret_var_h, ret_var_l); \ +\ + PC_MOD(ret_var_h << 8 | ret_var_l); \ +} while (0) + +void CPU::process(const unsigned long cycles) { + memory.setEndtime(cycleCounter_, cycles); + + unsigned char A = A_; + unsigned long cycleCounter = cycleCounter_; + + while (memory.isActive()) { + unsigned short PC = PC_; + + if (halted) { + if (cycleCounter < memory.getNextEventTime()) { + const unsigned long cycles = memory.getNextEventTime() - cycleCounter; + cycleCounter += cycles + ((4 - (cycles & 3)) & 3); + } + } else while (cycleCounter < memory.getNextEventTime()) { + unsigned char opcode; + + PC_READ(opcode); + + if (skip) { + PC = (PC - 1) & 0xFFFF; + skip = false; + } + + switch (opcode) { + //nop (4 cycles): + //Do nothing for 4 cycles: + case 0x00: + break; + case 0x01: + ld_rr_nn(B, C); + break; + case 0x02: + WRITE(BC(), A); + break; + case 0x03: + inc_rr(B, C); + break; + case 0x04: + inc_r(B); + break; + case 0x05: + dec_r(B); + break; + case 0x06: + PC_READ(B); + break; + + //rlca (4 cycles): + //Rotate 8-bit register A left, store old bit7 in CF. Reset SF, HCF, ZF: + case 0x07: + CF = A << 1; + A = (CF | CF >> 8) & 0xFF; + HF2 = 0; + ZF = 1; + break; + + //ld (nn),SP (20 cycles): + //Put value of SP into address given by next 2 bytes in memory: + case 0x08: + { + unsigned l, h; + + PC_READ(l); + PC_READ(h); + + const unsigned addr = h << 8 | l; + + WRITE(addr, SP & 0xFF); + WRITE((addr + 1) & 0xFFFF, SP >> 8); + } + break; + + case 0x09: + add_hl_rr(B, C); + break; + case 0x0A: + READ(A, BC()); + break; + case 0x0B: + dec_rr(B, C); + break; + case 0x0C: + inc_r(C); + break; + case 0x0D: + dec_r(C); + break; + case 0x0E: + PC_READ(C); + break; + + //rrca (4 cycles): + //Rotate 8-bit register A right, store old bit0 in CF. Reset SF, HCF, ZF: + case 0x0F: + CF = A << 8 | A; + A = CF >> 1 & 0xFF; + HF2 = 0; + ZF = 1; + break; + + //stop (4 cycles): + //Halt CPU and LCD display until button pressed: + case 0x10: + memory.speedChange(cycleCounter); + PC = (PC + 1) & 0xFFFF; + break; + case 0x11: + ld_rr_nn(D, E); + break; + case 0x12: + WRITE(DE(), A); + break; + case 0x13: + inc_rr(D, E); + break; + case 0x14: + inc_r(D); + break; + case 0x15: + dec_r(D); + break; + case 0x16: + PC_READ(D); + break; + + //rla (4 cycles): + //Rotate 8-bit register A left through CF, store old bit7 in CF, old CF value becomes bit0. Reset SF, HCF, ZF: + case 0x17: + { + const unsigned oldcf = CF >> 8 & 1; + CF = A << 1; + A = (CF | oldcf) & 0xFF; + } + + HF2 = 0; + ZF = 1; + break; + + case 0x18: + jr_disp(); + break; + case 0x19: + add_hl_rr(D, E); + break; + case 0x1A: + READ(A, DE()); + break; + case 0x1B: + dec_rr(D, E); + break; + case 0x1C: + inc_r(E); + break; + case 0x1D: + dec_r(E); + break; + case 0x1E: + PC_READ(E); + break; + + //rra (4 cycles): + //Rotate 8-bit register A right through CF, store old bit0 in CF, old CF value becomes bit7. Reset SF, HCF, ZF: + case 0x1F: + { + const unsigned oldcf = CF & 0x100; + CF = A << 8; + A = (A | oldcf) >> 1; + } + + HF2 = 0; + ZF = 1; + break; + + //jr nz,disp (12;8 cycles): + //Jump to value of next (signed) byte in memory+current address if ZF is unset: + case 0x20: + if (ZF & 0xFF) { + jr_disp(); + } else { + PC_MOD((PC + 1) & 0xFFFF); + } + break; + + case 0x21: + ld_rr_nn(H, L); + break; + + //ldi (hl),a (8 cycles): + //Put A into memory address in hl. Increment HL: + case 0x22: + { + unsigned addr = HL(); + + WRITE(addr, A); + + addr = (addr + 1) & 0xFFFF; + L = addr; + H = addr >> 8; + } + break; + + case 0x23: + inc_rr(H, L); + break; + case 0x24: + inc_r(H); + break; + case 0x25: + dec_r(H); + break; + case 0x26: + PC_READ(H); + break; + + + //daa (4 cycles): + //Adjust register A to correctly represent a BCD. Check ZF, HF and CF: + case 0x27: + /*{ + unsigned correction = ((A > 0x99) || (CF & 0x100)) ? 0x60 : 0x00; + + calcHF(HF1, HF2); + + if ((A & 0x0F) > 0x09 || (HF2 & 0x200)) + correction |= 0x06; + + HF1 = A; + HF2 = (HF2 & 0x400) | correction; + CF = (correction & 0x40) << 2; + A = (HF2 & 0x400) ? A - correction : (A + correction); + ZF = A; + }*/ + + calcHF(HF1, HF2); + + { + unsigned correction = (CF & 0x100) ? 0x60 : 0x00; + + if (HF2 & 0x200) + correction |= 0x06; + + if (!(HF2 &= 0x400)) { + if ((A & 0x0F) > 0x09) + correction |= 0x06; + + if (A > 0x99) + correction |= 0x60; + + A += correction; + } else + A -= correction; + + CF = correction << 2 & 0x100; + ZF = A; + A &= 0xFF; + } + break; + + //jr z,disp (12;8 cycles): + //Jump to value of next (signed) byte in memory+current address if ZF is set: + case 0x28: + if (ZF & 0xFF) { + PC_MOD((PC + 1) & 0xFFFF); + } else { + jr_disp(); + } + break; + + //add hl,hl (8 cycles): + //add 16-bit register HL to HL, check flags except ZF: + case 0x29: + add_hl_rr(H, L); + break; + + //ldi a,(hl) (8 cycles): + //Put value at address in hl into A. Increment HL: + case 0x2A: + { + unsigned addr = HL(); + + READ(A, addr); + + addr = (addr + 1) & 0xFFFF; + L = addr; + H = addr >> 8; + } + break; + + case 0x2B: + dec_rr(H, L); + break; + case 0x2C: + inc_r(L); + break; + case 0x2D: + dec_r(L); + break; + case 0x2E: + PC_READ(L); + break; + + //cpl (4 cycles): + //Complement register A. (Flip all bits), set SF and HCF: + case 0x2F: /*setSubtractFlag(); setHalfCarryFlag();*/ + HF2 = 0x600; + A ^= 0xFF; + break; + + //jr nc,disp (12;8 cycles): + //Jump to value of next (signed) byte in memory+current address if CF is unset: + case 0x30: + if (CF & 0x100) { + PC_MOD((PC + 1) & 0xFFFF); + } else { + jr_disp(); + } + break; + + //ld sp,nn (12 cycles) + //set sp to 16-bit value of next 2 bytes in memory + case 0x31: + { + unsigned l, h; + + PC_READ(l); + PC_READ(h); + + SP = h << 8 | l; + } + break; + + //ldd (hl),a (8 cycles): + //Put A into memory address in hl. Decrement HL: + case 0x32: + { + unsigned addr = HL(); + + WRITE(addr, A); + + addr = (addr - 1) & 0xFFFF; + L = addr; + H = addr >> 8; + } + break; + + case 0x33: + SP = (SP + 1) & 0xFFFF; + cycleCounter += 4; + break; + + //inc (hl) (12 cycles): + //Increment value at address in hl, check flags except CF: + case 0x34: + { + const unsigned addr = HL(); + + READ(HF2, addr); + ZF = HF2 + 1; + WRITE(addr, ZF & 0xFF); + HF2 |= 0x800; + } + break; + + //dec (hl) (12 cycles): + //Decrement value at address in hl, check flags except CF: + case 0x35: + { + const unsigned addr = HL(); + + READ(HF2, addr); + ZF = HF2 - 1; + WRITE(addr, ZF & 0xFF); + HF2 |= 0xC00; + } + break; + + //ld (hl),n (12 cycles): + //set memory at address in hl to value of next byte in memory: + case 0x36: + { + unsigned tmp; + + PC_READ(tmp); + WRITE(HL(), tmp); + } + break; + + //scf (4 cycles): + //Set CF. Unset SF and HCF: + case 0x37: /*setCarryFlag(); unsetSubtractFlag(); unsetHalfCarryFlag();*/ + CF = 0x100; + HF2 = 0; + break; + + //jr c,disp (12;8 cycles): + //Jump to value of next (signed) byte in memory+current address if CF is set: + case 0x38: //PC+=(((int8_t)memory.read(PC++))*CarryFlag()); Cycles(8); break; + if (CF & 0x100) { + jr_disp(); + } else { + PC_MOD((PC + 1) & 0xFFFF); + } + break; + + //add hl,sp (8 cycles): + //add SP to HL, check flags except ZF: + case 0x39: /*add_hl_rr(SP>>8, SP); break;*/ + CF = L + SP; + L = CF & 0xFF; + HF1 = H; + HF2 = ((CF ^ SP) & 0x100) | SP >> 8; + CF >>= 8; + CF += H; + H = CF & 0xFF; + cycleCounter += 4; + break; + + //ldd a,(hl) (8 cycles): + //Put value at address in hl into A. Decrement HL: + case 0x3A: + { + unsigned addr = HL(); + + A = memory.read(addr, cycleCounter); + cycleCounter += 4; + + addr = (addr - 1) & 0xFFFF; + L = addr; + H = addr >> 8; + } + break; + + case 0x3B: + SP = (SP - 1) & 0xFFFF; + cycleCounter += 4; + break; + case 0x3C: + inc_r(A); + break; + case 0x3D: + dec_r(A); + break; + case 0x3E: + PC_READ(A); + break; + + //ccf (4 cycles): + //Complement CF (unset if set vv.) Unset SF and HCF. + case 0x3F: /*complementCarryFlag(); unsetSubtractFlag(); unsetHalfCarryFlag();*/ + CF ^= 0x100; + HF2 = 0; + break; + + //ld r,r (4 cycles):next_irqEventTime + //ld r,(r) (8 cycles): + case 0x40: + B = B; + break; + case 0x41: + B = C; + break; + case 0x42: + B = D; + break; + case 0x43: + B = E; + break; + case 0x44: + B = H; + break; + case 0x45: + B = L; + break; + case 0x46: + READ(B, HL()); + break; + case 0x47: + B = A; + break; + case 0x48: + C = B; + break; + case 0x49: + C = C; + break; + case 0x4A: + C = D; + break; + case 0x4B: + C = E; + break; + case 0x4C: + C = H; + break; + case 0x4D: + C = L; + break; + case 0x4E: + READ(C, HL()); + break; + case 0x4F: + C = A; + break; + case 0x50: + D = B; + break; + case 0x51: + D = C; + break; + case 0x52: + D = D; + break; + case 0x53: + D = E; + break; + case 0x54: + D = H; + break; + case 0x55: + D = L; + break; + case 0x56: + READ(D, HL()); + break; + case 0x57: + D = A; + break; + case 0x58: + E = B; + break; + case 0x59: + E = C; + break; + case 0x5A: + E = D; + break; + case 0x5B: + E = E; + break; + case 0x5C: + E = H; + break; + case 0x5D: + E = L; + break; + case 0x5E: + READ(E, HL()); + break; + case 0x5F: + E = A; + break; + case 0x60: + H = B; + break; + case 0x61: + H = C; + break; + case 0x62: + H = D; + break; + case 0x63: + H = E; + break; + case 0x64: + H = H; + break; + case 0x65: + H = L; + break; + case 0x66: + READ(H, HL()); + break; + case 0x67: + H = A; + break; + case 0x68: + L = B; + break; + case 0x69: + L = C; + break; + case 0x6A: + L = D; + break; + case 0x6B: + L = E; + break; + case 0x6C: + L = H; + break; + case 0x6D: + L = L; + break; + case 0x6E: + READ(L, HL()); + break; + case 0x6F: + L = A; + break; + case 0x70: + WRITE(HL(), B); + break; + case 0x71: + WRITE(HL(), C); + break; + case 0x72: + WRITE(HL(), D); + break; + case 0x73: + WRITE(HL(), E); + break; + case 0x74: + WRITE(HL(), H); + break; + case 0x75: + WRITE(HL(), L); + break; + + //halt (4 cycles): + case 0x76: +// printf("halt\n"); + if (memory.getIME()/* || memory.next_eitime*/) { + halted = 1; + + if (cycleCounter < memory.getNextEventTime()) { + const unsigned long cycles = memory.getNextEventTime() - cycleCounter; + cycleCounter += cycles + ((4 - (cycles & 3)) & 3); + } + } else { + if ((memory.ff_read(0xFF0F, cycleCounter) & memory.ff_read(0xFFFF, cycleCounter)) & 0x1F) { + if (memory.isCgb()) + cycleCounter += 8; //two nops. + else + skip = true; + } else { + memory.schedule_unhalt(); + halted = 1; + + if (cycleCounter < memory.getNextEventTime()) { + const unsigned long cycles = memory.getNextEventTime() - cycleCounter; + cycleCounter += cycles + ((4 - (cycles & 3)) & 3); + } + } + } + break; + case 0x77: + WRITE(HL(), A); + break; + case 0x78: + A = B; + break; + case 0x79: + A = C; + break; + case 0x7A: + A = D; + break; + case 0x7B: + A = E; + break; + case 0x7C: + A = H; + break; + case 0x7D: + A = L; + break; + case 0x7E: + READ(A, HL()); + break; + case 0x7F: + A = A; + break; + case 0x80: + add_a_u8(B); + break; + case 0x81: + add_a_u8(C); + break; + case 0x82: + add_a_u8(D); + break; + case 0x83: + add_a_u8(E); + break; + case 0x84: + add_a_u8(H); + break; + case 0x85: + add_a_u8(L); + break; + case 0x86: + { + unsigned data; + + READ(data, HL()); + + add_a_u8(data); + } + break; + case 0x87: + add_a_u8(A); + break; + case 0x88: + adc_a_u8(B); + break; + case 0x89: + adc_a_u8(C); + break; + case 0x8A: + adc_a_u8(D); + break; + case 0x8B: + adc_a_u8(E); + break; + case 0x8C: + adc_a_u8(H); + break; + case 0x8D: + adc_a_u8(L); + break; + case 0x8E: + { + unsigned data; + + READ(data, HL()); + + adc_a_u8(data); + } + break; + case 0x8F: + adc_a_u8(A); + break; + case 0x90: + sub_a_u8(B); + break; + case 0x91: + sub_a_u8(C); + break; + case 0x92: + sub_a_u8(D); + break; + case 0x93: + sub_a_u8(E); + break; + case 0x94: + sub_a_u8(H); + break; + case 0x95: + sub_a_u8(L); + break; + case 0x96: + { + unsigned data; + + READ(data, HL()); + + sub_a_u8(data); + } + break; + //A-A is always 0: + case 0x97: + HF2 = 0x400; + CF = ZF = A = 0; + break; + case 0x98: + sbc_a_u8(B); + break; + case 0x99: + sbc_a_u8(C); + break; + case 0x9A: + sbc_a_u8(D); + break; + case 0x9B: + sbc_a_u8(E); + break; + case 0x9C: + sbc_a_u8(H); + break; + case 0x9D: + sbc_a_u8(L); + break; + case 0x9E: + { + unsigned data; + + READ(data, HL()); + + sbc_a_u8(data); + } + break; + case 0x9F: + sbc_a_u8(A); + break; + case 0xA0: + and_a_u8(B); + break; + case 0xA1: + and_a_u8(C); + break; + case 0xA2: + and_a_u8(D); + break; + case 0xA3: + and_a_u8(E); + break; + case 0xA4: + and_a_u8(H); + break; + case 0xA5: + and_a_u8(L); + break; + case 0xA6: + { + unsigned data; + + READ(data, HL()); + + and_a_u8(data); + } + break; + //A&A will always be A: + case 0xA7: + ZF = A; + CF = 0; + HF2 = 0x200; + break; + case 0xA8: + xor_a_u8(B); + break; + case 0xA9: + xor_a_u8(C); + break; + case 0xAA: + xor_a_u8(D); + break; + case 0xAB: + xor_a_u8(E); + break; + case 0xAC: + xor_a_u8(H); + break; + case 0xAD: + xor_a_u8(L); + break; + case 0xAE: + { + unsigned data; + + READ(data, HL()); + + xor_a_u8(data); + } + break; + //A^A will always be 0: + case 0xAF: + CF = HF2 = ZF = A = 0; + break; + case 0xB0: + or_a_u8(B); + break; + case 0xB1: + or_a_u8(C); + break; + case 0xB2: + or_a_u8(D); + break; + case 0xB3: + or_a_u8(E); + break; + case 0xB4: + or_a_u8(H); + break; + case 0xB5: + or_a_u8(L); + break; + case 0xB6: + { + unsigned data; + + READ(data, HL()); + + or_a_u8(data); + } + break; + //A|A will always be A: + case 0xB7: + ZF = A; + HF2 = CF = 0; + break; + case 0xB8: + cp_a_u8(B); + break; + case 0xB9: + cp_a_u8(C); + break; + case 0xBA: + cp_a_u8(D); + break; + case 0xBB: + cp_a_u8(E); + break; + case 0xBC: + cp_a_u8(H); + break; + case 0xBD: + cp_a_u8(L); + break; + case 0xBE: + { + unsigned data; + + READ(data, HL()); + + cp_a_u8(data); + } + break; + //A always equals A: + case 0xBF: + CF = ZF = 0; + HF2 = 0x400; + break; + + //ret nz (20;8 cycles): + //Pop two bytes from the stack and jump to that address, if ZF is unset: + case 0xC0: + cycleCounter += 4; + + if (ZF & 0xFF) { + ret(); + } + break; + + case 0xC1: + pop_rr(B, C); + break; + + //jp nz,nn (16;12 cycles): + //Jump to address stored in next two bytes in memory if ZF is unset: + case 0xC2: + if (ZF & 0xFF) { + jp_nn(); + } else { + PC_MOD((PC + 2) & 0xFFFF); + cycleCounter += 4; + } + break; + + case 0xC3: + jp_nn(); + break; + + //call nz,nn (24;12 cycles): + //Push address of next instruction onto stack and then jump to address stored in next two bytes in memory, if ZF is unset: + case 0xC4: + if (ZF & 0xFF) { + call_nn(); + } else { + PC_MOD((PC + 2) & 0xFFFF); + cycleCounter += 4; + } + break; + + case 0xC5: + push_rr(B, C); + break; + case 0xC6: + { + unsigned data; + + PC_READ(data); + + add_a_u8(data); + } + break; + case 0xC7: + rst_n(0x00); + break; + + //ret z (20;8 cycles): + //Pop two bytes from the stack and jump to that address, if ZF is set: + case 0xC8: + cycleCounter += 4; + + if (!(ZF & 0xFF)) { + ret(); + } + + break; + + //ret (16 cycles): + //Pop two bytes from the stack and jump to that address: + case 0xC9: + ret(); + break; + + //jp z,nn (16;12 cycles): + //Jump to address stored in next two bytes in memory if ZF is set: + case 0xCA: + if (ZF & 0xFF) { + PC_MOD((PC + 2) & 0xFFFF); + cycleCounter += 4; + } else { + jp_nn(); + } + break; + + + //CB OPCODES (Shifts, rotates and bits): + case 0xCB: + PC_READ(opcode); + + switch (opcode) { + case 0x00: + rlc_r(B); + break; + case 0x01: + rlc_r(C); + break; + case 0x02: + rlc_r(D); + break; + case 0x03: + rlc_r(E); + break; + case 0x04: + rlc_r(H); + break; + case 0x05: + rlc_r(L); + break; + //rlc (hl) (16 cycles): + //Rotate 8-bit value stored at address in HL left, store old bit7 in CF. Reset SF and HCF. Check ZF: + case 0x06: + { + const unsigned addr = HL(); + + READ(CF, addr); + CF <<= 1; + + ZF = CF | (CF >> 8); + + WRITE(addr, ZF & 0xFF); + + HF2 = 0; + } + break; + case 0x07: + rlc_r(A); + break; + case 0x08: + rrc_r(B); + break; + case 0x09: + rrc_r(C); + break; + case 0x0A: + rrc_r(D); + break; + case 0x0B: + rrc_r(E); + break; + case 0x0C: + rrc_r(H); + break; + case 0x0D: + rrc_r(L); + break; + //rrc (hl) (16 cycles): + //Rotate 8-bit value stored at address in HL right, store old bit0 in CF. Reset SF and HCF. Check ZF: + case 0x0E: + { + const unsigned addr = HL(); + + READ(ZF, addr); + + CF = ZF << 8; + + WRITE(addr, (ZF | CF) >> 1 & 0xFF); + + HF2 = 0; + } + break; + case 0x0F: + rrc_r(A); + break; + case 0x10: + rl_r(B); + break; + case 0x11: + rl_r(C); + break; + case 0x12: + rl_r(D); + break; + case 0x13: + rl_r(E); + break; + case 0x14: + rl_r(H); + break; + case 0x15: + rl_r(L); + break; + //rl (hl) (16 cycles): + //Rotate 8-bit value stored at address in HL left thorugh CF, store old bit7 in CF, old CF value becoms bit0. Reset SF and HCF. Check ZF: + case 0x16: + { + const unsigned addr = HL(); + const unsigned oldcf = CF >> 8 & 1; + + READ(CF, addr); + CF <<= 1; + + ZF = CF | oldcf; + + WRITE(addr, ZF & 0xFF); + + HF2 = 0; + } + break; + case 0x17: + rl_r(A); + break; + case 0x18: + rr_r(B); + break; + case 0x19: + rr_r(C); + break; + case 0x1A: + rr_r(D); + break; + case 0x1B: + rr_r(E); + break; + case 0x1C: + rr_r(H); + break; + case 0x1D: + rr_r(L); + break; + //rr (hl) (16 cycles): + //Rotate 8-bit value stored at address in HL right thorugh CF, store old bit0 in CF, old CF value becoms bit7. Reset SF and HCF. Check ZF: + case 0x1E: + { + const unsigned addr = HL(); + + READ(ZF, addr); + + const unsigned oldcf = CF & 0x100; + CF = ZF << 8; + ZF = (ZF | oldcf) >> 1; + + WRITE(addr, ZF); + + HF2 = 0; + } + break; + case 0x1F: + rr_r(A); + break; + case 0x20: + sla_r(B); + break; + case 0x21: + sla_r(C); + break; + case 0x22: + sla_r(D); + break; + case 0x23: + sla_r(E); + break; + case 0x24: + sla_r(H); + break; + case 0x25: + sla_r(L); + break; + //sla (hl) (16 cycles): + //Shift 8-bit value stored at address in HL left, store old bit7 in CF. Reset SF and HCF. Check ZF: + case 0x26: + { + const unsigned addr = HL(); + + READ(CF, addr); + CF <<= 1; + + ZF = CF; + + WRITE(addr, ZF & 0xFF); + + HF2 = 0; + } + break; + case 0x27: + sla_r(A); + break; + case 0x28: + sra_r(B); + break; + case 0x29: + sra_r(C); + break; + case 0x2A: + sra_r(D); + break; + case 0x2B: + sra_r(E); + break; + case 0x2C: + sra_r(H); + break; + case 0x2D: + sra_r(L); + break; + //sra (hl) (16 cycles): + //Shift 8-bit value stored at address in HL right, store old bit0 in CF, bit7=old bit7. Reset SF and HCF. Check ZF: + case 0x2E: + { + const unsigned addr = HL(); + + READ(CF, addr); + + ZF = CF >> 1; + + WRITE(addr, ZF | (CF & 0x80)); + + CF <<= 8; + HF2 = 0; + } + break; + case 0x2F: + sra_r(A); + break; + case 0x30: + swap_r(B); + break; + case 0x31: + swap_r(C); + break; + case 0x32: + swap_r(D); + break; + case 0x33: + swap_r(E); + break; + case 0x34: + swap_r(H); + break; + case 0x35: + swap_r(L); + break; + //swap (hl) (16 cycles): + //Swap upper and lower nibbles of 8-bit value stored at address in HL, reset flags, check zero flag: + case 0x36: + { + const unsigned addr = HL(); + + READ(ZF, addr); + + WRITE(addr, (ZF << 4 | ZF >> 4) & 0xFF); + + CF = HF2 = 0; + } + break; + case 0x37: + swap_r(A); + break; + case 0x38: + srl_r(B); + break; + case 0x39: + srl_r(C); + break; + case 0x3A: + srl_r(D); + break; + case 0x3B: + srl_r(E); + break; + case 0x3C: + srl_r(H); + break; + case 0x3D: + srl_r(L); + break; + //srl (hl) (16 cycles): + //Shift 8-bit value stored at address in HL right, store old bit0 in CF. Reset SF and HCF. Check ZF: + case 0x3E: + { + const unsigned addr = HL(); + + READ(CF, addr); + + ZF = CF >> 1; + + WRITE(addr, ZF); + + CF <<= 8; + HF2 = 0; + } + break; + case 0x3F: + srl_r(A); + break; + case 0x40: + bit0_u8(B); + break; + case 0x41: + bit0_u8(C); + break; + case 0x42: + bit0_u8(D); + break; + case 0x43: + bit0_u8(E); + break; + case 0x44: + bit0_u8(H); + break; + case 0x45: + bit0_u8(L); + break; + case 0x46: + { + unsigned data; + + READ(data, HL()); + + bit0_u8(data); + } + break; + case 0x47: + bit0_u8(A); + break; + case 0x48: + bit1_u8(B); + break; + case 0x49: + bit1_u8(C); + break; + case 0x4A: + bit1_u8(D); + break; + case 0x4B: + bit1_u8(E); + break; + case 0x4C: + bit1_u8(H); + break; + case 0x4D: + bit1_u8(L); + break; + case 0x4E: + { + unsigned data; + + READ(data, HL()); + + bit1_u8(data); + } + break; + case 0x4F: + bit1_u8(A); + break; + case 0x50: + bit2_u8(B); + break; + case 0x51: + bit2_u8(C); + break; + case 0x52: + bit2_u8(D); + break; + case 0x53: + bit2_u8(E); + break; + case 0x54: + bit2_u8(H); + break; + case 0x55: + bit2_u8(L); + break; + case 0x56: + { + unsigned data; + + READ(data, HL()); + + bit2_u8(data); + } + break; + case 0x57: + bit2_u8(A); + break; + case 0x58: + bit3_u8(B); + break; + case 0x59: + bit3_u8(C); + break; + case 0x5A: + bit3_u8(D); + break; + case 0x5B: + bit3_u8(E); + break; + case 0x5C: + bit3_u8(H); + break; + case 0x5D: + bit3_u8(L); + break; + case 0x5E: + { + unsigned data; + + READ(data, HL()); + + bit3_u8(data); + } + break; + case 0x5F: + bit3_u8(A); + break; + case 0x60: + bit4_u8(B); + break; + case 0x61: + bit4_u8(C); + break; + case 0x62: + bit4_u8(D); + break; + case 0x63: + bit4_u8(E); + break; + case 0x64: + bit4_u8(H); + break; + case 0x65: + bit4_u8(L); + break; + case 0x66: + { + unsigned data; + + READ(data, HL()); + + bit4_u8(data); + } + break; + case 0x67: + bit4_u8(A); + break; + case 0x68: + bit5_u8(B); + break; + case 0x69: + bit5_u8(C); + break; + case 0x6A: + bit5_u8(D); + break; + case 0x6B: + bit5_u8(E); + break; + case 0x6C: + bit5_u8(H); + break; + case 0x6D: + bit5_u8(L); + break; + case 0x6E: + { + unsigned data; + + READ(data, HL()); + + bit5_u8(data); + } + break; + case 0x6F: + bit5_u8(A); + break; + case 0x70: + bit6_u8(B); + break; + case 0x71: + bit6_u8(C); + break; + case 0x72: + bit6_u8(D); + break; + case 0x73: + bit6_u8(E); + break; + case 0x74: + bit6_u8(H); + break; + case 0x75: + bit6_u8(L); + break; + case 0x76: + { + unsigned data; + + READ(data, HL()); + + bit6_u8(data); + } + break; + case 0x77: + bit6_u8(A); + break; + case 0x78: + bit7_u8(B); + break; + case 0x79: + bit7_u8(C); + break; + case 0x7A: + bit7_u8(D); + break; + case 0x7B: + bit7_u8(E); + break; + case 0x7C: + bit7_u8(H); + break; + case 0x7D: + bit7_u8(L); + break; + case 0x7E: + { + unsigned data; + + READ(data, HL()); + + bit7_u8(data); + } + break; + case 0x7F: + bit7_u8(A); + break; + case 0x80: + res0_r(B); + break; + case 0x81: + res0_r(C); + break; + case 0x82: + res0_r(D); + break; + case 0x83: + res0_r(E); + break; + case 0x84: + res0_r(H); + break; + case 0x85: + res0_r(L); + break; + case 0x86: + resn_mem_hl(0); + break; + case 0x87: + res0_r(A); + break; + case 0x88: + res1_r(B); + break; + case 0x89: + res1_r(C); + break; + case 0x8A: + res1_r(D); + break; + case 0x8B: + res1_r(E); + break; + case 0x8C: + res1_r(H); + break; + case 0x8D: + res1_r(L); + break; + case 0x8E: + resn_mem_hl(1); + break; + case 0x8F: + res1_r(A); + break; + case 0x90: + res2_r(B); + break; + case 0x91: + res2_r(C); + break; + case 0x92: + res2_r(D); + break; + case 0x93: + res2_r(E); + break; + case 0x94: + res2_r(H); + break; + case 0x95: + res2_r(L); + break; + case 0x96: + resn_mem_hl(2); + break; + case 0x97: + res2_r(A); + break; + case 0x98: + res3_r(B); + break; + case 0x99: + res3_r(C); + break; + case 0x9A: + res3_r(D); + break; + case 0x9B: + res3_r(E); + break; + case 0x9C: + res3_r(H); + break; + case 0x9D: + res3_r(L); + break; + case 0x9E: + resn_mem_hl(3); + break; + case 0x9F: + res3_r(A); + break; + case 0xA0: + res4_r(B); + break; + case 0xA1: + res4_r(C); + break; + case 0xA2: + res4_r(D); + break; + case 0xA3: + res4_r(E); + break; + case 0xA4: + res4_r(H); + break; + case 0xA5: + res4_r(L); + break; + case 0xA6: + resn_mem_hl(4); + break; + case 0xA7: + res4_r(A); + break; + case 0xA8: + res5_r(B); + break; + case 0xA9: + res5_r(C); + break; + case 0xAA: + res5_r(D); + break; + case 0xAB: + res5_r(E); + break; + case 0xAC: + res5_r(H); + break; + case 0xAD: + res5_r(L); + break; + case 0xAE: + resn_mem_hl(5); + break; + case 0xAF: + res5_r(A); + break; + case 0xB0: + res6_r(B); + break; + case 0xB1: + res6_r(C); + break; + case 0xB2: + res6_r(D); + break; + case 0xB3: + res6_r(E); + break; + case 0xB4: + res6_r(H); + break; + case 0xB5: + res6_r(L); + break; + case 0xB6: + resn_mem_hl(6); + break; + case 0xB7: + res6_r(A); + break; + case 0xB8: + res7_r(B); + break; + case 0xB9: + res7_r(C); + break; + case 0xBA: + res7_r(D); + break; + case 0xBB: + res7_r(E); + break; + case 0xBC: + res7_r(H); + break; + case 0xBD: + res7_r(L); + break; + case 0xBE: + resn_mem_hl(7); + break; + case 0xBF: + res7_r(A); + break; + case 0xC0: + set0_r(B); + break; + case 0xC1: + set0_r(C); + break; + case 0xC2: + set0_r(D); + break; + case 0xC3: + set0_r(E); + break; + case 0xC4: + set0_r(H); + break; + case 0xC5: + set0_r(L); + break; + case 0xC6: + setn_mem_hl(0); + break; + case 0xC7: + set0_r(A); + break; + case 0xC8: + set1_r(B); + break; + case 0xC9: + set1_r(C); + break; + case 0xCA: + set1_r(D); + break; + case 0xCB: + set1_r(E); + break; + case 0xCC: + set1_r(H); + break; + case 0xCD: + set1_r(L); + break; + case 0xCE: + setn_mem_hl(1); + break; + case 0xCF: + set1_r(A); + break; + case 0xD0: + set2_r(B); + break; + case 0xD1: + set2_r(C); + break; + case 0xD2: + set2_r(D); + break; + case 0xD3: + set2_r(E); + break; + case 0xD4: + set2_r(H); + break; + case 0xD5: + set2_r(L); + break; + case 0xD6: + setn_mem_hl(2); + break; + case 0xD7: + set2_r(A); + break; + case 0xD8: + set3_r(B); + break; + case 0xD9: + set3_r(C); + break; + case 0xDA: + set3_r(D); + break; + case 0xDB: + set3_r(E); + break; + case 0xDC: + set3_r(H); + break; + case 0xDD: + set3_r(L); + break; + case 0xDE: + setn_mem_hl(3); + break; + case 0xDF: + set3_r(A); + break; + case 0xE0: + set4_r(B); + break; + case 0xE1: + set4_r(C); + break; + case 0xE2: + set4_r(D); + break; + case 0xE3: + set4_r(E); + break; + case 0xE4: + set4_r(H); + break; + case 0xE5: + set4_r(L); + break; + case 0xE6: + setn_mem_hl(4); + break; + case 0xE7: + set4_r(A); + break; + case 0xE8: + set5_r(B); + break; + case 0xE9: + set5_r(C); + break; + case 0xEA: + set5_r(D); + break; + case 0xEB: + set5_r(E); + break; + case 0xEC: + set5_r(H); + break; + case 0xED: + set5_r(L); + break; + case 0xEE: + setn_mem_hl(5); + break; + case 0xEF: + set5_r(A); + break; + case 0xF0: + set6_r(B); + break; + case 0xF1: + set6_r(C); + break; + case 0xF2: + set6_r(D); + break; + case 0xF3: + set6_r(E); + break; + case 0xF4: + set6_r(H); + break; + case 0xF5: + set6_r(L); + break; + case 0xF6: + setn_mem_hl(6); + break; + case 0xF7: + set6_r(A); + break; + case 0xF8: + set7_r(B); + break; + case 0xF9: + set7_r(C); + break; + case 0xFA: + set7_r(D); + break; + case 0xFB: + set7_r(E); + break; + case 0xFC: + set7_r(H); + break; + case 0xFD: + set7_r(L); + break; + case 0xFE: + setn_mem_hl(7); + break; + case 0xFF: + set7_r(A); + break; +// default: break; + } + break; + + + //call z,nn (24;12 cycles): + //Push address of next instruction onto stack and then jump to address stored in next two bytes in memory, if ZF is set: + case 0xCC: + if (ZF & 0xFF) { + PC_MOD((PC + 2) & 0xFFFF); + cycleCounter += 4; + } else { + call_nn(); + } + break; + + case 0xCD: + call_nn(); + break; + case 0xCE: + { + unsigned data; + + PC_READ(data); + + adc_a_u8(data); + } + break; + case 0xCF: + rst_n(0x08); + break; + + //ret nc (20;8 cycles): + //Pop two bytes from the stack and jump to that address, if CF is unset: + case 0xD0: + cycleCounter += 4; + + if (!(CF & 0x100)) { + ret(); + } + + break; + + case 0xD1: + pop_rr(D, E); + break; + + //jp nc,nn (16;12 cycles): + //Jump to address stored in next two bytes in memory if CF is unset: + case 0xD2: + if (CF & 0x100) { + PC_MOD((PC + 2) & 0xFFFF); + cycleCounter += 4; + } else { + jp_nn(); + } + break; + + case 0xD3: /*doesn't exist*/ + break; + + //call nc,nn (24;12 cycles): + //Push address of next instruction onto stack and then jump to address stored in next two bytes in memory, if CF is unset: + case 0xD4: + if (CF & 0x100) { + PC_MOD((PC + 2) & 0xFFFF); + cycleCounter += 4; + } else { + call_nn(); + } + break; + + case 0xD5: + push_rr(D, E); + break; + case 0xD6: + { + unsigned data; + + PC_READ(data); + + sub_a_u8(data); + } + break; + case 0xD7: + rst_n(0x10); + break; + + //ret c (20;8 cycles): + //Pop two bytes from the stack and jump to that address, if CF is set: + case 0xD8: + cycleCounter += 4; + + if (CF & 0x100) { + ret(); + } + + break; + + //reti (16 cycles): + //Pop two bytes from the stack and jump to that address, then enable interrupts: + case 0xD9: + { + unsigned l, h; + + pop_rr(h, l); + + memory.ei(cycleCounter); + + PC_MOD(h << 8 | l); + } + break; + + //jp c,nn (16;12 cycles): + //Jump to address stored in next two bytes in memory if CF is set: + case 0xDA: //PC=( ((PC+2)*(1-CarryFlag())) + (((memory.read(PC+1)<<8)+memory.read(PC))*CarryFlag()) ); Cycles(12); break; + if (CF & 0x100) { + jp_nn(); + } else { + PC_MOD((PC + 2) & 0xFFFF); + cycleCounter += 4; + } + break; + + case 0xDB: /*doesn't exist*/ + break; + + //call z,nn (24;12 cycles): + //Push address of next instruction onto stack and then jump to address stored in next two bytes in memory, if CF is set: + case 0xDC: + if (CF & 0x100) { + call_nn(); + } else { + PC_MOD((PC + 2) & 0xFFFF); + cycleCounter += 4; + } + break; + + case 0xDE: + { + unsigned data; + + PC_READ(data); + + sbc_a_u8(data); + } + break; + case 0xDF: + rst_n(0x18); + break; + + //ld ($FF00+n),a (12 cycles): + //Put value in A into address (0xFF00 + next byte in memory): + case 0xE0: + { + unsigned tmp; + + PC_READ(tmp); + + FF_WRITE(0xFF00 | tmp, A); + } + break; + + case 0xE1: + pop_rr(H, L); + break; + + //ld ($FF00+C),a (8 ycles): + //Put A into address (0xFF00 + register C): + case 0xE2: + FF_WRITE(0xFF00 | C, A); + break; + case 0xE3: /*doesn't exist*/ + break; + case 0xE4: /*doesn't exist*/ + break; + case 0xE5: + push_rr(H, L); + break; + case 0xE6: + { + unsigned data; + + PC_READ(data); + + and_a_u8(data); + } + break; + case 0xE7: + rst_n(0x20); + break; + + //add sp,n (16 cycles): + //Add next (signed) byte in memory to SP, reset ZF and SF, check HCF and CF: + case 0xE8: + /*{ + int8_t tmp = int8_t(memory.pc_read(PC++, cycleCounter)); + HF2 = (((SP & 0xFFF) + tmp) >> 3) & 0x200; + CF = SP + tmp; + SP = CF; + CF >>= 8; + ZF = 1; + cycleCounter += 12; + }*/ + sp_plus_n(SP); + cycleCounter += 4; + break; + + //jp hl (4 cycles): + //Jump to address in hl: + case 0xE9: + PC = HL(); + break; + + //ld (nn),a (16 cycles): + //set memory at address given by the next 2 bytes to value in A: + //Incrementing PC before call, because of possible interrupt. + case 0xEA: + { + unsigned l, h; + + PC_READ(l); + PC_READ(h); + + WRITE(h << 8 | l, A); + } + break; + + case 0xEB: /*doesn't exist*/ + break; + case 0xEC: /*doesn't exist*/ + break; + case 0xED: /*doesn't exist*/ + break; + case 0xEE: + { + unsigned data; + + PC_READ(data); + + xor_a_u8(data); + } + break; + case 0xEF: + rst_n(0x28); + break; + + //ld a,($FF00+n) (12 cycles): + //Put value at address (0xFF00 + next byte in memory) into A: + case 0xF0: + { + unsigned tmp; + + PC_READ(tmp); + + FF_READ(A, 0xFF00 | tmp); + } + break; + + case 0xF1: /*pop_rr(A, F); Cycles(12); break;*/ + { + unsigned F; + + pop_rr(A, F); + + FROM_F(F); + } + break; + + //ld a,($FF00+C) (8 cycles): + //Put value at address (0xFF00 + register C) into A: + case 0xF2: + FF_READ(A, 0xFF00 | C); + break; + + //di (4 cycles): + case 0xF3: + memory.di(); + break; + + case 0xF4: /*doesn't exist*/ + break; + case 0xF5: /*push_rr(A, F); Cycles(16); break;*/ + calcHF(HF1, HF2); + + { + unsigned F = F(); + + push_rr(A, F); + } + break; + + case 0xF6: + { + unsigned data; + + PC_READ(data); + + or_a_u8(data); + } + break; + case 0xF7: + rst_n(0x30); + break; + + //ldhl sp,n (12 cycles): + //Put (sp+next (signed) byte in memory) into hl (unsets ZF and SF, may enable HF and CF): + case 0xF8: + /*{ + int8_t tmp = int8_t(memory.pc_read(PC++, cycleCounter)); + HF2 = (((SP & 0xFFF) + tmp) >> 3) & 0x200; + CF = SP + tmp; + L = CF; + CF >>= 8; + H = CF; + ZF = 1; + cycleCounter += 8; + }*/ + { + unsigned sum; + sp_plus_n(sum); + L = sum & 0xFF; + H = sum >> 8; + } + break; + + //ld sp,hl (8 cycles): + //Put value in HL into SP + case 0xF9: + SP = HL(); + cycleCounter += 4; + break; + + //ld a,(nn) (16 cycles): + //set A to value in memory at address given by the 2 next bytes. + case 0xFA: + { + unsigned l, h; + + PC_READ(l); + PC_READ(h); + + READ(A, h << 8 | l); + } + break; + + //ei (4 cycles): + //Enable Interrupts after next instruction: + case 0xFB: + memory.ei(cycleCounter); + break; + + case 0xFC: /*doesn't exist*/ + break; + case 0xFD: /*doesn't exist*/ + break; + case 0xFE: + { + unsigned data; + + PC_READ(data); + + cp_a_u8(data); + } + break; + case 0xFF: + rst_n(0x38); + break; +// default: break; + } + } + + PC_ = PC; + cycleCounter = memory.event(cycleCounter); + } + + A_ = A; + cycleCounter_ = cycleCounter; +} diff --git a/supergameboy/libgambatte/src/cpu.h b/supergameboy/libgambatte/src/cpu.h new file mode 100644 index 00000000..300ba5fb --- /dev/null +++ b/supergameboy/libgambatte/src/cpu.h @@ -0,0 +1,115 @@ +/*************************************************************************** + * Copyright (C) 2007 by Sindre Aam�s * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#ifndef CPU_H +#define CPU_H + +class SaveState; + +#include "int.h" +#include "memory.h" + +class CPU { + Memory memory; + + unsigned long cycleCounter_; + + unsigned short PC_; + unsigned short SP; + + unsigned HF1, HF2, ZF, CF; + + unsigned char A_, B, C, D, E, /*F,*/ H, L; + + bool skip; + bool halted; + + void process(unsigned long cycles); + +public: + + CPU(); +// void halt(); + +// unsigned interrupt(unsigned address, unsigned cycleCounter); + + void updateVideo() { memory.updateVideo(cycleCounter_); } + unsigned lyCounter() { return memory.lyCounter(cycleCounter_); } + void setAccumulator(unsigned char value) { A_ = value; } + + void runFor(unsigned long cycles); + void setStatePtrs(SaveState &state); + void saveState(SaveState &state); + void loadState(const SaveState &state); + + void loadSavedata() { memory.loadSavedata(); } + void saveSavedata() { memory.saveSavedata(); } + + void setVideoBlitter(Gambatte::VideoBlitter *vb) { + memory.setVideoBlitter(vb); + } + + void videoBufferChange() { + memory.videoBufferChange(); + } + + unsigned int videoWidth() const { + return memory.videoWidth(); + } + + unsigned int videoHeight() const { + return memory.videoHeight(); + } + + void setVideoFilter(const unsigned int n) { + memory.setVideoFilter(n); + } + + std::vector filterInfo() const { + return memory.filterInfo(); + } + + void setInputStateGetter(Gambatte::InputStateGetter *getInput) { + memory.setInputStateGetter(getInput); + } + + void set_savedir(const char *sdir) { + memory.set_savedir(sdir); + } + + const std::string saveBasePath() const { + return memory.saveBasePath(); + } + + void setOsdElement(std::auto_ptr osdElement) { + memory.setOsdElement(osdElement); + } + + bool load(bool forceDmg); + + void setSoundBuffer(Gambatte::uint_least32_t *const buf) { memory.setSoundBuffer(buf); } + unsigned fillSoundBuffer() { return memory.fillSoundBuffer(cycleCounter_); } + + bool isCgb() const { return memory.isCgb(); } + + void setDmgPaletteColor(unsigned palNum, unsigned colorNum, unsigned rgb32) { + memory.setDmgPaletteColor(palNum, colorNum, rgb32); + } +}; + +#endif diff --git a/supergameboy/libgambatte/src/event_queue.h b/supergameboy/libgambatte/src/event_queue.h new file mode 100644 index 00000000..94fbebcf --- /dev/null +++ b/supergameboy/libgambatte/src/event_queue.h @@ -0,0 +1,160 @@ +/*************************************************************************** + * Copyright (C) 2007 by Sindre Aamås * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ +#ifndef EVENT_QUEUE_H +#define EVENT_QUEUE_H + +#include + +template +class event_queue { + Comparer comparer; + T *const a; + const std::size_t capacity_; + std::size_t size_; + + + std::size_t indexOf(T e); + void internalDec(std::size_t i, T e); + template void internalInc(std::size_t i, T e); + +public: + event_queue(std::size_t capacity, const Comparer &comparer); + ~event_queue(); + + std::size_t capacity() const { + return capacity_; + } + + void clear() { + size_ = 0; + } + + void dec(const T oldE, const T newE) { + internalDec(indexOf(oldE), newE); + } + + bool empty() const { + return size_ == 0; + } + + void inc(const T oldE, const T newE) { + internalInc(indexOf(oldE), newE); + } + + void modify_root(const T newRoot) { + internalInc(0, newRoot); + } + + void pop() { + internalInc(0, a[--size_]); + } + + void push(const T e) { + internalDec(size_++, e); + } + + void remove(T e); + + std::size_t size() const { + return size_; + } + + T top() const { + return a[0]; + } +}; + +template +event_queue::event_queue(const std::size_t capacity, const Comparer &comparer_in) : + comparer(comparer_in), + a(new T[capacity]), + capacity_(capacity), + size_(0) +{} + +template +event_queue::~event_queue() { + delete[] a; +} + +template +std::size_t event_queue::indexOf(const T e) { + std::size_t i = 0; + + while (a[i] != e) + ++i; + + return i; +} + +template +void event_queue::internalDec(std::size_t i, const T e) { + a[i] = e; + + while (i != 0) { + const std::size_t parentI = (i - 1) >> 1; + + if (!comparer.less(e, a[parentI])) + break; + + a[i] = a[parentI]; + a[parentI] = e; + i = parentI; + } +} + +template +template +void event_queue::internalInc(std::size_t i, const T e) { + a[i] = e; + + for (;;) { + std::size_t childI = i * 2 + 1; + + if (childI >= size_) + break; + + if ((!child2BoundsCheck || childI + 1 < size_) && comparer.less(a[childI + 1], a[childI])) + ++childI; + + if (!comparer.less(a[childI], e)) + break; + + a[i] = a[childI]; + a[childI] = e; + i = childI; + } +} + +template +void event_queue::remove(const T e) { + std::size_t i = indexOf(e); + + while (i != 0) { + const std::size_t parentI = (i - 1) >> 1; + + a[i] = a[parentI]; + a[parentI] = e; + i = parentI; + } + + pop(); +} + +#endif diff --git a/supergameboy/libgambatte/src/file/file.cpp b/supergameboy/libgambatte/src/file/file.cpp new file mode 100644 index 00000000..7a8f9966 --- /dev/null +++ b/supergameboy/libgambatte/src/file/file.cpp @@ -0,0 +1,73 @@ +/*************************************************************************** +Copyright (C) 2007 by Nach +http://nsrt.edgeemu.com + +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. + +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 version 2 for more details. + +You should have received a copy of the GNU General Public License +version 2 along with this program; if not, write to the +Free Software Foundation, Inc., +59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +***************************************************************************/ + +#include "file.h" + +using namespace std; + +static const unsigned int MAX_FILE_NAME = 512; + +File::File(const char *filename) : stream(filename, ios::in | ios::binary), is_zip(false), fsize(0), count(0) +{ + if (stream) + { + stream.seekg(0, ios::end); + fsize = stream.tellg(); + stream.seekg(0, ios::beg); + } +} + +File::~File() +{ + close(); +} + +void File::rewind() +{ + if (is_open()) + { + stream.seekg(0, ios::beg); + } +} + +bool File::is_open() +{ + return(stream.is_open()); +} + +void File::close() +{ + if (is_open()) + { + stream.close(); + } +} + +void File::read(char *buffer, size_t amount) +{ + if (is_open()) + { + stream.read(buffer, amount); + count = stream.gcount(); + } + else + { + count = 0; + } +} diff --git a/supergameboy/libgambatte/src/file/file.h b/supergameboy/libgambatte/src/file/file.h new file mode 100644 index 00000000..3435ef16 --- /dev/null +++ b/supergameboy/libgambatte/src/file/file.h @@ -0,0 +1,42 @@ +/*************************************************************************** +Copyright (C) 2007 by Nach +http://nsrt.edgeemu.com + +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. + +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 version 2 for more details. + +You should have received a copy of the GNU General Public License +version 2 along with this program; if not, write to the +Free Software Foundation, Inc., +59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +***************************************************************************/ + +#include + +class File { + private: + std::ifstream stream; + bool is_zip; //Change this to an enum later + std::size_t fsize, count; + void *zipfile; + bool zip_sub_open; + + void zip(const char *filename); + + public: + File(const char *filename); + ~File(); + void rewind(); + bool is_open(); + void close(); + std::size_t size() const { return fsize; }; + void read(char *buffer, std::size_t amount); + std::size_t gcount() const { return count; } + bool fail() const { return stream.fail(); } +}; diff --git a/supergameboy/libgambatte/src/file/file_zip.cpp b/supergameboy/libgambatte/src/file/file_zip.cpp new file mode 100644 index 00000000..c7fae6db --- /dev/null +++ b/supergameboy/libgambatte/src/file/file_zip.cpp @@ -0,0 +1,167 @@ +/*************************************************************************** +Copyright (C) 2007 by Nach +http://nsrt.edgeemu.com + +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. + +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 version 2 for more details. + +You should have received a copy of the GNU General Public License +version 2 along with this program; if not, write to the +Free Software Foundation, Inc., +59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +***************************************************************************/ + +#include "file.h" + +#include + +namespace zlib { +#include "unzip/unzip.h" +} + +using namespace std; +using namespace zlib; + +static const unsigned int MAX_FILE_NAME = 512; + +File::File(const char *filename) : stream(filename, ios::in | ios::binary), is_zip(false), fsize(0), count(0) +{ + if (stream) + { + char temp[4]; + stream.read(temp, sizeof(temp)); + + //check for standard zip 'magic number' + if ((temp[0] == 'P') && (temp[1] == 'K') && (temp[2] == 3) && (temp[3] == 4)) + { + stream.close(); + is_zip = true; + zip(filename); + } + else + { + stream.seekg(0, ios::end); + fsize = stream.tellg(); + stream.seekg(0, ios::beg); + } + } +} + +void File::zip(const char *filename) +{ + zipfile = unzOpen(filename); + if (zipfile) + { + zip_sub_open = false; + + unz_file_info cFileInfo; + char ourFile[MAX_FILE_NAME] = { '\n' }; + + for (int cFile = unzGoToFirstFile((unzFile)zipfile); cFile == UNZ_OK; cFile = unzGoToNextFile((unzFile)zipfile)) + { + //Temporary char array for file name + char cFileName[MAX_FILE_NAME]; + + //Gets info on current file, and places it in cFileInfo + unzGetCurrentFileInfo((unzFile)zipfile, &cFileInfo, cFileName, MAX_FILE_NAME, 0, 0, 0, 0); + + //Check for largest file which should be the ROM + if ((size_t)cFileInfo.uncompressed_size > fsize) + { + strcpy(ourFile, cFileName); + fsize = (size_t)cFileInfo.uncompressed_size; + } + } + + if (ourFile[0] != '\n') + { + //Sets current file to the file we liked before + unzLocateFile((unzFile)zipfile, ourFile, 1); + + if (unzOpenCurrentFile((unzFile)zipfile) == UNZ_OK) + { + zip_sub_open = true; + } + } + + if (!zip_sub_open) + { + unzClose((unzFile)zipfile); + zipfile = 0; + } + } +} + +File::~File() +{ + close(); +} + +void File::rewind() +{ + if (is_open()) + { + if (!is_zip) + { + stream.seekg(0, ios::beg); + } + else + { + unzCloseCurrentFile((unzFile)zipfile); + unzOpenCurrentFile((unzFile)zipfile); + } + } +} + +bool File::is_open() +{ + if (!is_zip) + { + return(stream.is_open()); + } + return(zipfile && zip_sub_open); +} + +void File::close() +{ + if (is_open()) + { + if (!is_zip) + { + stream.close(); + } + else + { + unzOpenCurrentFile((unzFile)zipfile); + unzClose((unzFile)zipfile); + zipfile = 0; + zip_sub_open = false; + } + } +} + +void File::read(char *buffer, size_t amount) +{ + if (is_open()) + { + if (!is_zip) + { + stream.read(buffer, amount); + count = stream.gcount(); + } + else + { + count = (size_t)unzReadCurrentFile((unzFile)zipfile, buffer, amount); + } + } + else + { + count = 0; + } +} diff --git a/supergameboy/libgambatte/src/file/unzip/crypt.h b/supergameboy/libgambatte/src/file/unzip/crypt.h new file mode 100644 index 00000000..622f4bc2 --- /dev/null +++ b/supergameboy/libgambatte/src/file/unzip/crypt.h @@ -0,0 +1,132 @@ +/* crypt.h -- base code for crypt/uncrypt ZIPfile + + + Version 1.01e, February 12th, 2005 + + Copyright (C) 1998-2005 Gilles Vollant + + This code is a modified version of crypting code in Infozip distribution + + The encryption/decryption parts of this source code (as opposed to the + non-echoing password parts) were originally written in Europe. The + whole source package can be freely distributed, including from the USA. + (Prior to January 2000, re-export from the US was a violation of US law.) + + This encryption code is a direct transcription of the algorithm from + Roger Schlafly, described by Phil Katz in the file appnote.txt. This + file (appnote.txt) is distributed with the PKZIP program (even in the + version without encryption capabilities). + + If you don't need crypting in your application, just define symbols + NOCRYPT and NOUNCRYPT. + + This code support the "Traditional PKWARE Encryption". + + The new AES encryption added on Zip format by Winzip (see the page + http://www.winzip.com/aes_info.htm ) and PKWare PKZip 5.x Strong + Encryption is not supported. +*/ + +#define CRC32(c, b) ((*(pcrc_32_tab+(((int)(c) ^ (b)) & 0xff))) ^ ((c) >> 8)) + +/*********************************************************************** + * Return the next byte in the pseudo-random sequence + */ +static int decrypt_byte(unsigned long* pkeys, const unsigned long* pcrc_32_tab) +{ + unsigned temp; /* POTENTIAL BUG: temp*(temp^1) may overflow in an + * unpredictable manner on 16-bit systems; not a problem + * with any known compiler so far, though */ + + temp = ((unsigned)(*(pkeys+2)) & 0xffff) | 2; + return (int)(((temp * (temp ^ 1)) >> 8) & 0xff); +} + +/*********************************************************************** + * Update the encryption keys with the next byte of plain text + */ +static int update_keys(unsigned long* pkeys,const unsigned long* pcrc_32_tab,int c) +{ + (*(pkeys+0)) = CRC32((*(pkeys+0)), c); + (*(pkeys+1)) += (*(pkeys+0)) & 0xff; + (*(pkeys+1)) = (*(pkeys+1)) * 134775813L + 1; + { + register int keyshift = (int)((*(pkeys+1)) >> 24); + (*(pkeys+2)) = CRC32((*(pkeys+2)), keyshift); + } + return c; +} + + +/*********************************************************************** + * Initialize the encryption keys and the random header according to + * the given password. + */ +static void init_keys(const char* passwd,unsigned long* pkeys,const unsigned long* pcrc_32_tab) +{ + *(pkeys+0) = 305419896L; + *(pkeys+1) = 591751049L; + *(pkeys+2) = 878082192L; + while (*passwd != '\0') { + update_keys(pkeys,pcrc_32_tab,(int)*passwd); + passwd++; + } +} + +#define zdecode(pkeys,pcrc_32_tab,c) \ + (update_keys(pkeys,pcrc_32_tab,c ^= decrypt_byte(pkeys,pcrc_32_tab))) + +#define zencode(pkeys,pcrc_32_tab,c,t) \ + (t=decrypt_byte(pkeys,pcrc_32_tab), update_keys(pkeys,pcrc_32_tab,c), t^(c)) + +#ifdef INCLUDECRYPTINGCODE_IFCRYPTALLOWED + +#define RAND_HEAD_LEN 12 + /* "last resort" source for second part of crypt seed pattern */ +# ifndef ZCR_SEED2 +# define ZCR_SEED2 3141592654UL /* use PI as default pattern */ +# endif + +static int crypthead(passwd, buf, bufSize, pkeys, pcrc_32_tab, crcForCrypting) + const char *passwd; /* password string */ + unsigned char *buf; /* where to write header */ + int bufSize; + unsigned long* pkeys; + const unsigned long* pcrc_32_tab; + unsigned long crcForCrypting; +{ + int n; /* index in random header */ + int t; /* temporary */ + int c; /* random byte */ + unsigned char header[RAND_HEAD_LEN-2]; /* random header */ + static unsigned calls = 0; /* ensure different random header each time */ + + if (bufSize> 7) & 0xff; + header[n] = (unsigned char)zencode(pkeys, pcrc_32_tab, c, t); + } + /* Encrypt random header (last two bytes is high word of crc) */ + init_keys(passwd, pkeys, pcrc_32_tab); + for (n = 0; n < RAND_HEAD_LEN-2; n++) + { + buf[n] = (unsigned char)zencode(pkeys, pcrc_32_tab, header[n], t); + } + buf[n++] = zencode(pkeys, pcrc_32_tab, (int)(crcForCrypting >> 16) & 0xff, t); + buf[n++] = zencode(pkeys, pcrc_32_tab, (int)(crcForCrypting >> 24) & 0xff, t); + return n; +} + +#endif diff --git a/supergameboy/libgambatte/src/file/unzip/ioapi.c b/supergameboy/libgambatte/src/file/unzip/ioapi.c new file mode 100644 index 00000000..05b5ef15 --- /dev/null +++ b/supergameboy/libgambatte/src/file/unzip/ioapi.c @@ -0,0 +1,177 @@ +/* ioapi.c -- IO base function header for compress/uncompress .zip + files using zlib + zip or unzip API + + Version 1.01e, February 12th, 2005 + + Copyright (C) 1998-2005 Gilles Vollant +*/ + +#include +#include +#include + +#include +#include "ioapi.h" + + + +/* I've found an old Unix (a SunOS 4.1.3_U1) without all SEEK_* defined.... */ + +#ifndef SEEK_CUR +#define SEEK_CUR 1 +#endif + +#ifndef SEEK_END +#define SEEK_END 2 +#endif + +#ifndef SEEK_SET +#define SEEK_SET 0 +#endif + +voidpf ZCALLBACK fopen_file_func OF(( + voidpf opaque, + const char* filename, + int mode)); + +uLong ZCALLBACK fread_file_func OF(( + voidpf opaque, + voidpf stream, + void* buf, + uLong size)); + +uLong ZCALLBACK fwrite_file_func OF(( + voidpf opaque, + voidpf stream, + const void* buf, + uLong size)); + +long ZCALLBACK ftell_file_func OF(( + voidpf opaque, + voidpf stream)); + +long ZCALLBACK fseek_file_func OF(( + voidpf opaque, + voidpf stream, + uLong offset, + int origin)); + +int ZCALLBACK fclose_file_func OF(( + voidpf opaque, + voidpf stream)); + +int ZCALLBACK ferror_file_func OF(( + voidpf opaque, + voidpf stream)); + + +voidpf ZCALLBACK fopen_file_func (opaque, filename, mode) + voidpf opaque; + const char* filename; + int mode; +{ + FILE* file = NULL; + const char* mode_fopen = NULL; + if ((mode & ZLIB_FILEFUNC_MODE_READWRITEFILTER)==ZLIB_FILEFUNC_MODE_READ) + mode_fopen = "rb"; + else + if (mode & ZLIB_FILEFUNC_MODE_EXISTING) + mode_fopen = "r+b"; + else + if (mode & ZLIB_FILEFUNC_MODE_CREATE) + mode_fopen = "wb"; + + if ((filename!=NULL) && (mode_fopen != NULL)) + file = fopen(filename, mode_fopen); + return file; +} + + +uLong ZCALLBACK fread_file_func (opaque, stream, buf, size) + voidpf opaque; + voidpf stream; + void* buf; + uLong size; +{ + uLong ret; + ret = (uLong)fread(buf, 1, (size_t)size, (FILE *)stream); + return ret; +} + + +uLong ZCALLBACK fwrite_file_func (opaque, stream, buf, size) + voidpf opaque; + voidpf stream; + const void* buf; + uLong size; +{ + uLong ret; + ret = (uLong)fwrite(buf, 1, (size_t)size, (FILE *)stream); + return ret; +} + +long ZCALLBACK ftell_file_func (opaque, stream) + voidpf opaque; + voidpf stream; +{ + long ret; + ret = ftell((FILE *)stream); + return ret; +} + +long ZCALLBACK fseek_file_func (opaque, stream, offset, origin) + voidpf opaque; + voidpf stream; + uLong offset; + int origin; +{ + int fseek_origin=0; + long ret; + switch (origin) + { + case ZLIB_FILEFUNC_SEEK_CUR : + fseek_origin = SEEK_CUR; + break; + case ZLIB_FILEFUNC_SEEK_END : + fseek_origin = SEEK_END; + break; + case ZLIB_FILEFUNC_SEEK_SET : + fseek_origin = SEEK_SET; + break; + default: return -1; + } + ret = 0; + fseek((FILE *)stream, offset, fseek_origin); + return ret; +} + +int ZCALLBACK fclose_file_func (opaque, stream) + voidpf opaque; + voidpf stream; +{ + int ret; + ret = fclose((FILE *)stream); + return ret; +} + +int ZCALLBACK ferror_file_func (opaque, stream) + voidpf opaque; + voidpf stream; +{ + int ret; + ret = ferror((FILE *)stream); + return ret; +} + +void fill_fopen_filefunc (pzlib_filefunc_def) + zlib_filefunc_def* pzlib_filefunc_def; +{ + pzlib_filefunc_def->zopen_file = fopen_file_func; + pzlib_filefunc_def->zread_file = fread_file_func; + pzlib_filefunc_def->zwrite_file = fwrite_file_func; + pzlib_filefunc_def->ztell_file = ftell_file_func; + pzlib_filefunc_def->zseek_file = fseek_file_func; + pzlib_filefunc_def->zclose_file = fclose_file_func; + pzlib_filefunc_def->zerror_file = ferror_file_func; + pzlib_filefunc_def->opaque = NULL; +} diff --git a/supergameboy/libgambatte/src/file/unzip/ioapi.h b/supergameboy/libgambatte/src/file/unzip/ioapi.h new file mode 100644 index 00000000..7d457baa --- /dev/null +++ b/supergameboy/libgambatte/src/file/unzip/ioapi.h @@ -0,0 +1,75 @@ +/* ioapi.h -- IO base function header for compress/uncompress .zip + files using zlib + zip or unzip API + + Version 1.01e, February 12th, 2005 + + Copyright (C) 1998-2005 Gilles Vollant +*/ + +#ifndef _ZLIBIOAPI_H +#define _ZLIBIOAPI_H + + +#define ZLIB_FILEFUNC_SEEK_CUR (1) +#define ZLIB_FILEFUNC_SEEK_END (2) +#define ZLIB_FILEFUNC_SEEK_SET (0) + +#define ZLIB_FILEFUNC_MODE_READ (1) +#define ZLIB_FILEFUNC_MODE_WRITE (2) +#define ZLIB_FILEFUNC_MODE_READWRITEFILTER (3) + +#define ZLIB_FILEFUNC_MODE_EXISTING (4) +#define ZLIB_FILEFUNC_MODE_CREATE (8) + + +#ifndef ZCALLBACK + +#if (defined(WIN32) || defined (WINDOWS) || defined (_WINDOWS)) && defined(CALLBACK) && defined (USEWINDOWS_CALLBACK) +#define ZCALLBACK CALLBACK +#else +#define ZCALLBACK +#endif +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +typedef voidpf (ZCALLBACK *open_file_func) OF((voidpf opaque, const char* filename, int mode)); +typedef uLong (ZCALLBACK *read_file_func) OF((voidpf opaque, voidpf stream, void* buf, uLong size)); +typedef uLong (ZCALLBACK *write_file_func) OF((voidpf opaque, voidpf stream, const void* buf, uLong size)); +typedef long (ZCALLBACK *tell_file_func) OF((voidpf opaque, voidpf stream)); +typedef long (ZCALLBACK *seek_file_func) OF((voidpf opaque, voidpf stream, uLong offset, int origin)); +typedef int (ZCALLBACK *close_file_func) OF((voidpf opaque, voidpf stream)); +typedef int (ZCALLBACK *testerror_file_func) OF((voidpf opaque, voidpf stream)); + +typedef struct zlib_filefunc_def_s +{ + open_file_func zopen_file; + read_file_func zread_file; + write_file_func zwrite_file; + tell_file_func ztell_file; + seek_file_func zseek_file; + close_file_func zclose_file; + testerror_file_func zerror_file; + voidpf opaque; +} zlib_filefunc_def; + + + +void fill_fopen_filefunc OF((zlib_filefunc_def* pzlib_filefunc_def)); + +#define ZREAD(filefunc,filestream,buf,size) ((*((filefunc).zread_file))((filefunc).opaque,filestream,buf,size)) +#define ZWRITE(filefunc,filestream,buf,size) ((*((filefunc).zwrite_file))((filefunc).opaque,filestream,buf,size)) +#define ZTELL(filefunc,filestream) ((*((filefunc).ztell_file))((filefunc).opaque,filestream)) +#define ZSEEK(filefunc,filestream,pos,mode) ((*((filefunc).zseek_file))((filefunc).opaque,filestream,pos,mode)) +#define ZCLOSE(filefunc,filestream) ((*((filefunc).zclose_file))((filefunc).opaque,filestream)) +#define ZERROR(filefunc,filestream) ((*((filefunc).zerror_file))((filefunc).opaque,filestream)) + + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/supergameboy/libgambatte/src/file/unzip/unzip.c b/supergameboy/libgambatte/src/file/unzip/unzip.c new file mode 100644 index 00000000..325f3d08 --- /dev/null +++ b/supergameboy/libgambatte/src/file/unzip/unzip.c @@ -0,0 +1,1605 @@ +/* unzip.c -- IO for uncompress .zip files using zlib + Version 1.01e, February 12th, 2005 + + Copyright (C) 1998-2005 Gilles Vollant + + Read unzip.h for more info +*/ + +/* Decryption code comes from crypt.c by Info-ZIP but has been greatly reduced in terms of +compatibility with older software. The following is from the original crypt.c. Code +woven in by Terry Thorsen 1/2003. +*/ +/* + Copyright (c) 1990-2000 Info-ZIP. All rights reserved. + + See the accompanying file LICENSE, version 2000-Apr-09 or later + (the contents of which are also included in zip.h) for terms of use. + If, for some reason, all these files are missing, the Info-ZIP license + also may be found at: ftp://ftp.info-zip.org/pub/infozip/license.html +*/ +/* + crypt.c (full version) by Info-ZIP. Last revised: [see crypt.h] + + The encryption/decryption parts of this source code (as opposed to the + non-echoing password parts) were originally written in Europe. The + whole source package can be freely distributed, including from the USA. + (Prior to January 2000, re-export from the US was a violation of US law.) + */ + +/* + This encryption code is a direct transcription of the algorithm from + Roger Schlafly, described by Phil Katz in the file appnote.txt. This + file (appnote.txt) is distributed with the PKZIP program (even in the + version without encryption capabilities). + */ + + +#include +#include +#include +#include +#include "unzip.h" + +#ifdef STDC +# include +# include +# include +#endif +#ifdef NO_ERRNO_H + extern int errno; +#else +# include +#endif + + +#ifndef local +# define local static +#endif +/* compile with -Dlocal if your debugger can't find static symbols */ + + +#ifndef CASESENSITIVITYDEFAULT_NO +# if !defined(unix) && !defined(CASESENSITIVITYDEFAULT_YES) +# define CASESENSITIVITYDEFAULT_NO +# endif +#endif + + +#ifndef UNZ_BUFSIZE +#define UNZ_BUFSIZE (16384) +#endif + +#ifndef UNZ_MAXFILENAMEINZIP +#define UNZ_MAXFILENAMEINZIP (256) +#endif + +#ifndef ALLOC +# define ALLOC(size) (malloc(size)) +#endif +#ifndef TRYFREE +# define TRYFREE(p) {if (p) free(p);} +#endif + +#define SIZECENTRALDIRITEM (0x2e) +#define SIZEZIPLOCALHEADER (0x1e) + + + + +const char unz_copyright[] = + " unzip 1.01 Copyright 1998-2004 Gilles Vollant - http://www.winimage.com/zLibDll"; + +/* unz_file_info_interntal contain internal info about a file in zipfile*/ +typedef struct unz_file_info_internal_s +{ + uLong offset_curfile;/* relative offset of local header 4 bytes */ +} unz_file_info_internal; + + +/* file_in_zip_read_info_s contain internal information about a file in zipfile, + when reading and decompress it */ +typedef struct +{ + char *read_buffer; /* internal buffer for compressed data */ + z_stream stream; /* zLib stream structure for inflate */ + + uLong pos_in_zipfile; /* position in byte on the zipfile, for fseek*/ + uLong stream_initialised; /* flag set if stream structure is initialised*/ + + uLong offset_local_extrafield;/* offset of the local extra field */ + uInt size_local_extrafield;/* size of the local extra field */ + uLong pos_local_extrafield; /* position in the local extra field in read*/ + + uLong crc32; /* crc32 of all data uncompressed */ + uLong crc32_wait; /* crc32 we must obtain after decompress all */ + uLong rest_read_compressed; /* number of byte to be decompressed */ + uLong rest_read_uncompressed;/*number of byte to be obtained after decomp*/ + zlib_filefunc_def z_filefunc; + voidpf filestream; /* io structore of the zipfile */ + uLong compression_method; /* compression method (0==store) */ + uLong byte_before_the_zipfile;/* byte before the zipfile, (>0 for sfx)*/ + int raw; +} file_in_zip_read_info_s; + + +/* unz_s contain internal information about the zipfile +*/ +typedef struct +{ + zlib_filefunc_def z_filefunc; + voidpf filestream; /* io structore of the zipfile */ + unz_global_info gi; /* public global information */ + uLong byte_before_the_zipfile;/* byte before the zipfile, (>0 for sfx)*/ + uLong num_file; /* number of the current file in the zipfile*/ + uLong pos_in_central_dir; /* pos of the current file in the central dir*/ + uLong current_file_ok; /* flag about the usability of the current file*/ + uLong central_pos; /* position of the beginning of the central dir*/ + + uLong size_central_dir; /* size of the central directory */ + uLong offset_central_dir; /* offset of start of central directory with + respect to the starting disk number */ + + unz_file_info cur_file_info; /* public info about the current file in zip*/ + unz_file_info_internal cur_file_info_internal; /* private info about it*/ + file_in_zip_read_info_s* pfile_in_zip_read; /* structure about the current + file if we are decompressing it */ + int encrypted; +# ifndef NOUNCRYPT + unsigned long keys[3]; /* keys defining the pseudo-random sequence */ + const unsigned long* pcrc_32_tab; +# endif +} unz_s; + + +#ifndef NOUNCRYPT +#include "crypt.h" +#endif + +/* =========================================================================== + Read a byte from a gz_stream; update next_in and avail_in. Return EOF + for end of file. + IN assertion: the stream s has been sucessfully opened for reading. +*/ + + +local int unzlocal_getByte OF(( + const zlib_filefunc_def* pzlib_filefunc_def, + voidpf filestream, + int *pi)); + +local int unzlocal_getByte(pzlib_filefunc_def,filestream,pi) + const zlib_filefunc_def* pzlib_filefunc_def; + voidpf filestream; + int *pi; +{ + unsigned char c; + int err = (int)ZREAD(*pzlib_filefunc_def,filestream,&c,1); + if (err==1) + { + *pi = (int)c; + return UNZ_OK; + } + else + { + if (ZERROR(*pzlib_filefunc_def,filestream)) + return UNZ_ERRNO; + else + return UNZ_EOF; + } +} + + +/* =========================================================================== + Reads a long in LSB order from the given gz_stream. Sets +*/ +local int unzlocal_getShort OF(( + const zlib_filefunc_def* pzlib_filefunc_def, + voidpf filestream, + uLong *pX)); + +local int unzlocal_getShort (pzlib_filefunc_def,filestream,pX) + const zlib_filefunc_def* pzlib_filefunc_def; + voidpf filestream; + uLong *pX; +{ + uLong x ; + int i = 0; + int err; + + err = unzlocal_getByte(pzlib_filefunc_def,filestream,&i); + x = (uLong)i; + + if (err==UNZ_OK) + err = unzlocal_getByte(pzlib_filefunc_def,filestream,&i); + x += ((uLong)i)<<8; + + if (err==UNZ_OK) + *pX = x; + else + *pX = 0; + return err; +} + +local int unzlocal_getLong OF(( + const zlib_filefunc_def* pzlib_filefunc_def, + voidpf filestream, + uLong *pX)); + +local int unzlocal_getLong (pzlib_filefunc_def,filestream,pX) + const zlib_filefunc_def* pzlib_filefunc_def; + voidpf filestream; + uLong *pX; +{ + uLong x ; + int i = 0; + int err; + + err = unzlocal_getByte(pzlib_filefunc_def,filestream,&i); + x = (uLong)i; + + if (err==UNZ_OK) + err = unzlocal_getByte(pzlib_filefunc_def,filestream,&i); + x += ((uLong)i)<<8; + + if (err==UNZ_OK) + err = unzlocal_getByte(pzlib_filefunc_def,filestream,&i); + x += ((uLong)i)<<16; + + if (err==UNZ_OK) + err = unzlocal_getByte(pzlib_filefunc_def,filestream,&i); + x += ((uLong)i)<<24; + + if (err==UNZ_OK) + *pX = x; + else + *pX = 0; + return err; +} + + +/* My own strcmpi / strcasecmp */ +local int strcmpcasenosensitive_internal (fileName1,fileName2) + const char* fileName1; + const char* fileName2; +{ + for (;;) + { + char c1=*(fileName1++); + char c2=*(fileName2++); + if ((c1>='a') && (c1<='z')) + c1 -= 0x20; + if ((c2>='a') && (c2<='z')) + c2 -= 0x20; + if (c1=='\0') + return ((c2=='\0') ? 0 : -1); + if (c2=='\0') + return 1; + if (c1c2) + return 1; + } +} + + +#ifdef CASESENSITIVITYDEFAULT_NO +#define CASESENSITIVITYDEFAULTVALUE 2 +#else +#define CASESENSITIVITYDEFAULTVALUE 1 +#endif + +#ifndef STRCMPCASENOSENTIVEFUNCTION +#define STRCMPCASENOSENTIVEFUNCTION strcmpcasenosensitive_internal +#endif + +/* + Compare two filename (fileName1,fileName2). + If iCaseSenisivity = 1, comparision is case sensitivity (like strcmp) + If iCaseSenisivity = 2, comparision is not case sensitivity (like strcmpi + or strcasecmp) + If iCaseSenisivity = 0, case sensitivity is defaut of your operating system + (like 1 on Unix, 2 on Windows) + +*/ +extern int ZEXPORT unzStringFileNameCompare (fileName1,fileName2,iCaseSensitivity) + const char* fileName1; + const char* fileName2; + int iCaseSensitivity; +{ + if (iCaseSensitivity==0) + iCaseSensitivity=CASESENSITIVITYDEFAULTVALUE; + + if (iCaseSensitivity==1) + return strcmp(fileName1,fileName2); + + return STRCMPCASENOSENTIVEFUNCTION(fileName1,fileName2); +} + +#ifndef BUFREADCOMMENT +#define BUFREADCOMMENT (0x400) +#endif + +/* + Locate the Central directory of a zipfile (at the end, just before + the global comment) +*/ +local uLong unzlocal_SearchCentralDir OF(( + const zlib_filefunc_def* pzlib_filefunc_def, + voidpf filestream)); + +local uLong unzlocal_SearchCentralDir(pzlib_filefunc_def,filestream) + const zlib_filefunc_def* pzlib_filefunc_def; + voidpf filestream; +{ + unsigned char* buf; + uLong uSizeFile; + uLong uBackRead; + uLong uMaxBack=0xffff; /* maximum size of global comment */ + uLong uPosFound=0; + + if (ZSEEK(*pzlib_filefunc_def,filestream,0,ZLIB_FILEFUNC_SEEK_END) != 0) + return 0; + + + uSizeFile = ZTELL(*pzlib_filefunc_def,filestream); + + if (uMaxBack>uSizeFile) + uMaxBack = uSizeFile; + + buf = (unsigned char*)ALLOC(BUFREADCOMMENT+4); + if (buf==NULL) + return 0; + + uBackRead = 4; + while (uBackReaduMaxBack) + uBackRead = uMaxBack; + else + uBackRead+=BUFREADCOMMENT; + uReadPos = uSizeFile-uBackRead ; + + uReadSize = ((BUFREADCOMMENT+4) < (uSizeFile-uReadPos)) ? + (BUFREADCOMMENT+4) : (uSizeFile-uReadPos); + if (ZSEEK(*pzlib_filefunc_def,filestream,uReadPos,ZLIB_FILEFUNC_SEEK_SET)!=0) + break; + + if (ZREAD(*pzlib_filefunc_def,filestream,buf,uReadSize)!=uReadSize) + break; + + for (i=(int)uReadSize-3; (i--)>0;) + if (((*(buf+i))==0x50) && ((*(buf+i+1))==0x4b) && + ((*(buf+i+2))==0x05) && ((*(buf+i+3))==0x06)) + { + uPosFound = uReadPos+i; + break; + } + + if (uPosFound!=0) + break; + } + TRYFREE(buf); + return uPosFound; +} + +/* + Open a Zip file. path contain the full pathname (by example, + on a Windows NT computer "c:\\test\\zlib114.zip" or on an Unix computer + "zlib/zlib114.zip". + If the zipfile cannot be opened (file doesn't exist or in not valid), the + return value is NULL. + Else, the return value is a unzFile Handle, usable with other function + of this unzip package. +*/ +extern unzFile ZEXPORT unzOpen2 (path, pzlib_filefunc_def) + const char *path; + zlib_filefunc_def* pzlib_filefunc_def; +{ + unz_s us; + unz_s *s; + uLong central_pos,uL; + + uLong number_disk; /* number of the current dist, used for + spaning ZIP, unsupported, always 0*/ + uLong number_disk_with_CD; /* number the the disk with central dir, used + for spaning ZIP, unsupported, always 0*/ + uLong number_entry_CD; /* total number of entries in + the central dir + (same than number_entry on nospan) */ + + int err=UNZ_OK; + + if (unz_copyright[0]!=' ') + return NULL; + + if (pzlib_filefunc_def==NULL) + fill_fopen_filefunc(&us.z_filefunc); + else + us.z_filefunc = *pzlib_filefunc_def; + + us.filestream= (*(us.z_filefunc.zopen_file))(us.z_filefunc.opaque, + path, + ZLIB_FILEFUNC_MODE_READ | + ZLIB_FILEFUNC_MODE_EXISTING); + if (us.filestream==NULL) + return NULL; + + central_pos = unzlocal_SearchCentralDir(&us.z_filefunc,us.filestream); + if (central_pos==0) + err=UNZ_ERRNO; + + if (ZSEEK(us.z_filefunc, us.filestream, + central_pos,ZLIB_FILEFUNC_SEEK_SET)!=0) + err=UNZ_ERRNO; + + /* the signature, already checked */ + if (unzlocal_getLong(&us.z_filefunc, us.filestream,&uL)!=UNZ_OK) + err=UNZ_ERRNO; + + /* number of this disk */ + if (unzlocal_getShort(&us.z_filefunc, us.filestream,&number_disk)!=UNZ_OK) + err=UNZ_ERRNO; + + /* number of the disk with the start of the central directory */ + if (unzlocal_getShort(&us.z_filefunc, us.filestream,&number_disk_with_CD)!=UNZ_OK) + err=UNZ_ERRNO; + + /* total number of entries in the central dir on this disk */ + if (unzlocal_getShort(&us.z_filefunc, us.filestream,&us.gi.number_entry)!=UNZ_OK) + err=UNZ_ERRNO; + + /* total number of entries in the central dir */ + if (unzlocal_getShort(&us.z_filefunc, us.filestream,&number_entry_CD)!=UNZ_OK) + err=UNZ_ERRNO; + + if ((number_entry_CD!=us.gi.number_entry) || + (number_disk_with_CD!=0) || + (number_disk!=0)) + err=UNZ_BADZIPFILE; + + /* size of the central directory */ + if (unzlocal_getLong(&us.z_filefunc, us.filestream,&us.size_central_dir)!=UNZ_OK) + err=UNZ_ERRNO; + + /* offset of start of central directory with respect to the + starting disk number */ + if (unzlocal_getLong(&us.z_filefunc, us.filestream,&us.offset_central_dir)!=UNZ_OK) + err=UNZ_ERRNO; + + /* zipfile comment length */ + if (unzlocal_getShort(&us.z_filefunc, us.filestream,&us.gi.size_comment)!=UNZ_OK) + err=UNZ_ERRNO; + + if ((central_pospfile_in_zip_read!=NULL) + unzCloseCurrentFile(file); + + ZCLOSE(s->z_filefunc, s->filestream); + TRYFREE(s); + return UNZ_OK; +} + + +/* + Write info about the ZipFile in the *pglobal_info structure. + No preparation of the structure is needed + return UNZ_OK if there is no problem. */ +extern int ZEXPORT unzGetGlobalInfo (file,pglobal_info) + unzFile file; + unz_global_info *pglobal_info; +{ + unz_s* s; + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + *pglobal_info=s->gi; + return UNZ_OK; +} + + +/* + Translate date/time from Dos format to tm_unz (readable more easilty) +*/ +local void unzlocal_DosDateToTmuDate (ulDosDate, ptm) + uLong ulDosDate; + tm_unz* ptm; +{ + uLong uDate; + uDate = (uLong)(ulDosDate>>16); + ptm->tm_mday = (uInt)(uDate&0x1f) ; + ptm->tm_mon = (uInt)((((uDate)&0x1E0)/0x20)-1) ; + ptm->tm_year = (uInt)(((uDate&0x0FE00)/0x0200)+1980) ; + + ptm->tm_hour = (uInt) ((ulDosDate &0xF800)/0x800); + ptm->tm_min = (uInt) ((ulDosDate&0x7E0)/0x20) ; + ptm->tm_sec = (uInt) (2*(ulDosDate&0x1f)) ; +} + +/* + Get Info about the current file in the zipfile, with internal only info +*/ +local int unzlocal_GetCurrentFileInfoInternal OF((unzFile file, + unz_file_info *pfile_info, + unz_file_info_internal + *pfile_info_internal, + char *szFileName, + uLong fileNameBufferSize, + void *extraField, + uLong extraFieldBufferSize, + char *szComment, + uLong commentBufferSize)); + +local int unzlocal_GetCurrentFileInfoInternal (file, + pfile_info, + pfile_info_internal, + szFileName, fileNameBufferSize, + extraField, extraFieldBufferSize, + szComment, commentBufferSize) + unzFile file; + unz_file_info *pfile_info; + unz_file_info_internal *pfile_info_internal; + char *szFileName; + uLong fileNameBufferSize; + void *extraField; + uLong extraFieldBufferSize; + char *szComment; + uLong commentBufferSize; +{ + unz_s* s; + unz_file_info file_info; + unz_file_info_internal file_info_internal; + int err=UNZ_OK; + uLong uMagic; + long lSeek=0; + + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + if (ZSEEK(s->z_filefunc, s->filestream, + s->pos_in_central_dir+s->byte_before_the_zipfile, + ZLIB_FILEFUNC_SEEK_SET)!=0) + err=UNZ_ERRNO; + + + /* we check the magic */ + if (err==UNZ_OK) + { + if (unzlocal_getLong(&s->z_filefunc, s->filestream,&uMagic) != UNZ_OK) + err=UNZ_ERRNO; + else if (uMagic!=0x02014b50) + err=UNZ_BADZIPFILE; + } + + if (unzlocal_getShort(&s->z_filefunc, s->filestream,&file_info.version) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getShort(&s->z_filefunc, s->filestream,&file_info.version_needed) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getShort(&s->z_filefunc, s->filestream,&file_info.flag) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getShort(&s->z_filefunc, s->filestream,&file_info.compression_method) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getLong(&s->z_filefunc, s->filestream,&file_info.dosDate) != UNZ_OK) + err=UNZ_ERRNO; + + unzlocal_DosDateToTmuDate(file_info.dosDate,&file_info.tmu_date); + + if (unzlocal_getLong(&s->z_filefunc, s->filestream,&file_info.crc) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getLong(&s->z_filefunc, s->filestream,&file_info.compressed_size) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getLong(&s->z_filefunc, s->filestream,&file_info.uncompressed_size) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getShort(&s->z_filefunc, s->filestream,&file_info.size_filename) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getShort(&s->z_filefunc, s->filestream,&file_info.size_file_extra) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getShort(&s->z_filefunc, s->filestream,&file_info.size_file_comment) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getShort(&s->z_filefunc, s->filestream,&file_info.disk_num_start) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getShort(&s->z_filefunc, s->filestream,&file_info.internal_fa) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getLong(&s->z_filefunc, s->filestream,&file_info.external_fa) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getLong(&s->z_filefunc, s->filestream,&file_info_internal.offset_curfile) != UNZ_OK) + err=UNZ_ERRNO; + + lSeek+=file_info.size_filename; + if ((err==UNZ_OK) && (szFileName!=NULL)) + { + uLong uSizeRead ; + if (file_info.size_filename0) && (fileNameBufferSize>0)) + if (ZREAD(s->z_filefunc, s->filestream,szFileName,uSizeRead)!=uSizeRead) + err=UNZ_ERRNO; + lSeek -= uSizeRead; + } + + + if ((err==UNZ_OK) && (extraField!=NULL)) + { + uLong uSizeRead ; + if (file_info.size_file_extraz_filefunc, s->filestream,lSeek,ZLIB_FILEFUNC_SEEK_CUR)==0) + lSeek=0; + else + err=UNZ_ERRNO; + } + if ((file_info.size_file_extra>0) && (extraFieldBufferSize>0)) + if (ZREAD(s->z_filefunc, s->filestream,extraField,uSizeRead)!=uSizeRead) + err=UNZ_ERRNO; + lSeek += file_info.size_file_extra - uSizeRead; + } + else + lSeek+=file_info.size_file_extra; + + + if ((err==UNZ_OK) && (szComment!=NULL)) + { + uLong uSizeRead ; + if (file_info.size_file_commentz_filefunc, s->filestream,lSeek,ZLIB_FILEFUNC_SEEK_CUR)==0) + lSeek=0; + else + err=UNZ_ERRNO; + } + if ((file_info.size_file_comment>0) && (commentBufferSize>0)) + if (ZREAD(s->z_filefunc, s->filestream,szComment,uSizeRead)!=uSizeRead) + err=UNZ_ERRNO; + lSeek+=file_info.size_file_comment - uSizeRead; + } + else + lSeek+=file_info.size_file_comment; + + if ((err==UNZ_OK) && (pfile_info!=NULL)) + *pfile_info=file_info; + + if ((err==UNZ_OK) && (pfile_info_internal!=NULL)) + *pfile_info_internal=file_info_internal; + + return err; +} + + + +/* + Write info about the ZipFile in the *pglobal_info structure. + No preparation of the structure is needed + return UNZ_OK if there is no problem. +*/ +extern int ZEXPORT unzGetCurrentFileInfo (file, + pfile_info, + szFileName, fileNameBufferSize, + extraField, extraFieldBufferSize, + szComment, commentBufferSize) + unzFile file; + unz_file_info *pfile_info; + char *szFileName; + uLong fileNameBufferSize; + void *extraField; + uLong extraFieldBufferSize; + char *szComment; + uLong commentBufferSize; +{ + return unzlocal_GetCurrentFileInfoInternal(file,pfile_info,NULL, + szFileName,fileNameBufferSize, + extraField,extraFieldBufferSize, + szComment,commentBufferSize); +} + +/* + Set the current file of the zipfile to the first file. + return UNZ_OK if there is no problem +*/ +extern int ZEXPORT unzGoToFirstFile (file) + unzFile file; +{ + int err=UNZ_OK; + unz_s* s; + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + s->pos_in_central_dir=s->offset_central_dir; + s->num_file=0; + err=unzlocal_GetCurrentFileInfoInternal(file,&s->cur_file_info, + &s->cur_file_info_internal, + NULL,0,NULL,0,NULL,0); + s->current_file_ok = (err == UNZ_OK); + return err; +} + +/* + Set the current file of the zipfile to the next file. + return UNZ_OK if there is no problem + return UNZ_END_OF_LIST_OF_FILE if the actual file was the latest. +*/ +extern int ZEXPORT unzGoToNextFile (file) + unzFile file; +{ + unz_s* s; + int err; + + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + if (!s->current_file_ok) + return UNZ_END_OF_LIST_OF_FILE; + if (s->gi.number_entry != 0xffff) /* 2^16 files overflow hack */ + if (s->num_file+1==s->gi.number_entry) + return UNZ_END_OF_LIST_OF_FILE; + + s->pos_in_central_dir += SIZECENTRALDIRITEM + s->cur_file_info.size_filename + + s->cur_file_info.size_file_extra + s->cur_file_info.size_file_comment ; + s->num_file++; + err = unzlocal_GetCurrentFileInfoInternal(file,&s->cur_file_info, + &s->cur_file_info_internal, + NULL,0,NULL,0,NULL,0); + s->current_file_ok = (err == UNZ_OK); + return err; +} + + +/* + Try locate the file szFileName in the zipfile. + For the iCaseSensitivity signification, see unzipStringFileNameCompare + + return value : + UNZ_OK if the file is found. It becomes the current file. + UNZ_END_OF_LIST_OF_FILE if the file is not found +*/ +extern int ZEXPORT unzLocateFile (file, szFileName, iCaseSensitivity) + unzFile file; + const char *szFileName; + int iCaseSensitivity; +{ + unz_s* s; + int err; + + /* We remember the 'current' position in the file so that we can jump + * back there if we fail. + */ + unz_file_info cur_file_infoSaved; + unz_file_info_internal cur_file_info_internalSaved; + uLong num_fileSaved; + uLong pos_in_central_dirSaved; + + + if (file==NULL) + return UNZ_PARAMERROR; + + if (strlen(szFileName)>=UNZ_MAXFILENAMEINZIP) + return UNZ_PARAMERROR; + + s=(unz_s*)file; + if (!s->current_file_ok) + return UNZ_END_OF_LIST_OF_FILE; + + /* Save the current state */ + num_fileSaved = s->num_file; + pos_in_central_dirSaved = s->pos_in_central_dir; + cur_file_infoSaved = s->cur_file_info; + cur_file_info_internalSaved = s->cur_file_info_internal; + + err = unzGoToFirstFile(file); + + while (err == UNZ_OK) + { + char szCurrentFileName[UNZ_MAXFILENAMEINZIP+1]; + err = unzGetCurrentFileInfo(file,NULL, + szCurrentFileName,sizeof(szCurrentFileName)-1, + NULL,0,NULL,0); + if (err == UNZ_OK) + { + if (unzStringFileNameCompare(szCurrentFileName, + szFileName,iCaseSensitivity)==0) + return UNZ_OK; + err = unzGoToNextFile(file); + } + } + + /* We failed, so restore the state of the 'current file' to where we + * were. + */ + s->num_file = num_fileSaved ; + s->pos_in_central_dir = pos_in_central_dirSaved ; + s->cur_file_info = cur_file_infoSaved; + s->cur_file_info_internal = cur_file_info_internalSaved; + return err; +} + + +/* +/////////////////////////////////////////// +// Contributed by Ryan Haksi (mailto://cryogen@infoserve.net) +// I need random access +// +// Further optimization could be realized by adding an ability +// to cache the directory in memory. The goal being a single +// comprehensive file read to put the file I need in a memory. +*/ + +/* +typedef struct unz_file_pos_s +{ + uLong pos_in_zip_directory; // offset in file + uLong num_of_file; // # of file +} unz_file_pos; +*/ + +extern int ZEXPORT unzGetFilePos(file, file_pos) + unzFile file; + unz_file_pos* file_pos; +{ + unz_s* s; + + if (file==NULL || file_pos==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + if (!s->current_file_ok) + return UNZ_END_OF_LIST_OF_FILE; + + file_pos->pos_in_zip_directory = s->pos_in_central_dir; + file_pos->num_of_file = s->num_file; + + return UNZ_OK; +} + +extern int ZEXPORT unzGoToFilePos(file, file_pos) + unzFile file; + unz_file_pos* file_pos; +{ + unz_s* s; + int err; + + if (file==NULL || file_pos==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + + /* jump to the right spot */ + s->pos_in_central_dir = file_pos->pos_in_zip_directory; + s->num_file = file_pos->num_of_file; + + /* set the current file */ + err = unzlocal_GetCurrentFileInfoInternal(file,&s->cur_file_info, + &s->cur_file_info_internal, + NULL,0,NULL,0,NULL,0); + /* return results */ + s->current_file_ok = (err == UNZ_OK); + return err; +} + +/* +// Unzip Helper Functions - should be here? +/////////////////////////////////////////// +*/ + +/* + Read the local header of the current zipfile + Check the coherency of the local header and info in the end of central + directory about this file + store in *piSizeVar the size of extra info in local header + (filename and size of extra field data) +*/ +local int unzlocal_CheckCurrentFileCoherencyHeader (s,piSizeVar, + poffset_local_extrafield, + psize_local_extrafield) + unz_s* s; + uInt* piSizeVar; + uLong *poffset_local_extrafield; + uInt *psize_local_extrafield; +{ + uLong uMagic,uData,uFlags; + uLong size_filename; + uLong size_extra_field; + int err=UNZ_OK; + + *piSizeVar = 0; + *poffset_local_extrafield = 0; + *psize_local_extrafield = 0; + + if (ZSEEK(s->z_filefunc, s->filestream,s->cur_file_info_internal.offset_curfile + + s->byte_before_the_zipfile,ZLIB_FILEFUNC_SEEK_SET)!=0) + return UNZ_ERRNO; + + + if (err==UNZ_OK) + { + if (unzlocal_getLong(&s->z_filefunc, s->filestream,&uMagic) != UNZ_OK) + err=UNZ_ERRNO; + else if (uMagic!=0x04034b50) + err=UNZ_BADZIPFILE; + } + + if (unzlocal_getShort(&s->z_filefunc, s->filestream,&uData) != UNZ_OK) + err=UNZ_ERRNO; +/* + else if ((err==UNZ_OK) && (uData!=s->cur_file_info.wVersion)) + err=UNZ_BADZIPFILE; +*/ + if (unzlocal_getShort(&s->z_filefunc, s->filestream,&uFlags) != UNZ_OK) + err=UNZ_ERRNO; + + if (unzlocal_getShort(&s->z_filefunc, s->filestream,&uData) != UNZ_OK) + err=UNZ_ERRNO; + else if ((err==UNZ_OK) && (uData!=s->cur_file_info.compression_method)) + err=UNZ_BADZIPFILE; + + if ((err==UNZ_OK) && (s->cur_file_info.compression_method!=0) && + (s->cur_file_info.compression_method!=Z_DEFLATED)) + err=UNZ_BADZIPFILE; + + if (unzlocal_getLong(&s->z_filefunc, s->filestream,&uData) != UNZ_OK) /* date/time */ + err=UNZ_ERRNO; + + if (unzlocal_getLong(&s->z_filefunc, s->filestream,&uData) != UNZ_OK) /* crc */ + err=UNZ_ERRNO; + else if ((err==UNZ_OK) && (uData!=s->cur_file_info.crc) && + ((uFlags & 8)==0)) + err=UNZ_BADZIPFILE; + + if (unzlocal_getLong(&s->z_filefunc, s->filestream,&uData) != UNZ_OK) /* size compr */ + err=UNZ_ERRNO; + else if ((err==UNZ_OK) && (uData!=s->cur_file_info.compressed_size) && + ((uFlags & 8)==0)) + err=UNZ_BADZIPFILE; + + if (unzlocal_getLong(&s->z_filefunc, s->filestream,&uData) != UNZ_OK) /* size uncompr */ + err=UNZ_ERRNO; + else if ((err==UNZ_OK) && (uData!=s->cur_file_info.uncompressed_size) && + ((uFlags & 8)==0)) + err=UNZ_BADZIPFILE; + + + if (unzlocal_getShort(&s->z_filefunc, s->filestream,&size_filename) != UNZ_OK) + err=UNZ_ERRNO; + else if ((err==UNZ_OK) && (size_filename!=s->cur_file_info.size_filename)) + err=UNZ_BADZIPFILE; + + *piSizeVar += (uInt)size_filename; + + if (unzlocal_getShort(&s->z_filefunc, s->filestream,&size_extra_field) != UNZ_OK) + err=UNZ_ERRNO; + *poffset_local_extrafield= s->cur_file_info_internal.offset_curfile + + SIZEZIPLOCALHEADER + size_filename; + *psize_local_extrafield = (uInt)size_extra_field; + + *piSizeVar += (uInt)size_extra_field; + + return err; +} + +/* + Open for reading data the current file in the zipfile. + If there is no error and the file is opened, the return value is UNZ_OK. +*/ +extern int ZEXPORT unzOpenCurrentFile3 (file, method, level, raw, password) + unzFile file; + int* method; + int* level; + int raw; + const char* password; +{ + int err=UNZ_OK; + uInt iSizeVar; + unz_s* s; + file_in_zip_read_info_s* pfile_in_zip_read_info; + uLong offset_local_extrafield; /* offset of the local extra field */ + uInt size_local_extrafield; /* size of the local extra field */ +# ifndef NOUNCRYPT + char source[12]; +# else + if (password != NULL) + return UNZ_PARAMERROR; +# endif + + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + if (!s->current_file_ok) + return UNZ_PARAMERROR; + + if (s->pfile_in_zip_read != NULL) + unzCloseCurrentFile(file); + + if (unzlocal_CheckCurrentFileCoherencyHeader(s,&iSizeVar, + &offset_local_extrafield,&size_local_extrafield)!=UNZ_OK) + return UNZ_BADZIPFILE; + + pfile_in_zip_read_info = (file_in_zip_read_info_s*) + ALLOC(sizeof(file_in_zip_read_info_s)); + if (pfile_in_zip_read_info==NULL) + return UNZ_INTERNALERROR; + + pfile_in_zip_read_info->read_buffer=(char*)ALLOC(UNZ_BUFSIZE); + pfile_in_zip_read_info->offset_local_extrafield = offset_local_extrafield; + pfile_in_zip_read_info->size_local_extrafield = size_local_extrafield; + pfile_in_zip_read_info->pos_local_extrafield=0; + pfile_in_zip_read_info->raw=raw; + + if (pfile_in_zip_read_info->read_buffer==NULL) + { + TRYFREE(pfile_in_zip_read_info); + return UNZ_INTERNALERROR; + } + + pfile_in_zip_read_info->stream_initialised=0; + + if (method!=NULL) + *method = (int)s->cur_file_info.compression_method; + + if (level!=NULL) + { + *level = 6; + switch (s->cur_file_info.flag & 0x06) + { + case 6 : *level = 1; break; + case 4 : *level = 2; break; + case 2 : *level = 9; break; + } + } + + if ((s->cur_file_info.compression_method!=0) && + (s->cur_file_info.compression_method!=Z_DEFLATED)) + err=UNZ_BADZIPFILE; + + pfile_in_zip_read_info->crc32_wait=s->cur_file_info.crc; + pfile_in_zip_read_info->crc32=0; + pfile_in_zip_read_info->compression_method = + s->cur_file_info.compression_method; + pfile_in_zip_read_info->filestream=s->filestream; + pfile_in_zip_read_info->z_filefunc=s->z_filefunc; + pfile_in_zip_read_info->byte_before_the_zipfile=s->byte_before_the_zipfile; + + pfile_in_zip_read_info->stream.total_out = 0; + + if ((s->cur_file_info.compression_method==Z_DEFLATED) && + (!raw)) + { + pfile_in_zip_read_info->stream.zalloc = (alloc_func)0; + pfile_in_zip_read_info->stream.zfree = (free_func)0; + pfile_in_zip_read_info->stream.opaque = (voidpf)0; + pfile_in_zip_read_info->stream.next_in = (voidpf)0; + pfile_in_zip_read_info->stream.avail_in = 0; + + err=inflateInit2(&pfile_in_zip_read_info->stream, -MAX_WBITS); + if (err == Z_OK) + pfile_in_zip_read_info->stream_initialised=1; + else + { + TRYFREE(pfile_in_zip_read_info); + return err; + } + /* windowBits is passed < 0 to tell that there is no zlib header. + * Note that in this case inflate *requires* an extra "dummy" byte + * after the compressed stream in order to complete decompression and + * return Z_STREAM_END. + * In unzip, i don't wait absolutely Z_STREAM_END because I known the + * size of both compressed and uncompressed data + */ + } + pfile_in_zip_read_info->rest_read_compressed = + s->cur_file_info.compressed_size ; + pfile_in_zip_read_info->rest_read_uncompressed = + s->cur_file_info.uncompressed_size ; + + + pfile_in_zip_read_info->pos_in_zipfile = + s->cur_file_info_internal.offset_curfile + SIZEZIPLOCALHEADER + + iSizeVar; + + pfile_in_zip_read_info->stream.avail_in = (uInt)0; + + s->pfile_in_zip_read = pfile_in_zip_read_info; + +# ifndef NOUNCRYPT + if (password != NULL) + { + int i; + s->pcrc_32_tab = get_crc_table(); + init_keys(password,s->keys,s->pcrc_32_tab); + if (ZSEEK(s->z_filefunc, s->filestream, + s->pfile_in_zip_read->pos_in_zipfile + + s->pfile_in_zip_read->byte_before_the_zipfile, + SEEK_SET)!=0) + return UNZ_INTERNALERROR; + if(ZREAD(s->z_filefunc, s->filestream,source, 12)<12) + return UNZ_INTERNALERROR; + + for (i = 0; i<12; i++) + zdecode(s->keys,s->pcrc_32_tab,source[i]); + + s->pfile_in_zip_read->pos_in_zipfile+=12; + s->encrypted=1; + } +# endif + + + return UNZ_OK; +} + +extern int ZEXPORT unzOpenCurrentFile (file) + unzFile file; +{ + return unzOpenCurrentFile3(file, NULL, NULL, 0, NULL); +} + +extern int ZEXPORT unzOpenCurrentFilePassword (file, password) + unzFile file; + const char* password; +{ + return unzOpenCurrentFile3(file, NULL, NULL, 0, password); +} + +extern int ZEXPORT unzOpenCurrentFile2 (file,method,level,raw) + unzFile file; + int* method; + int* level; + int raw; +{ + return unzOpenCurrentFile3(file, method, level, raw, NULL); +} + +/* + Read bytes from the current file. + buf contain buffer where data must be copied + len the size of buf. + + return the number of byte copied if somes bytes are copied + return 0 if the end of file was reached + return <0 with error code if there is an error + (UNZ_ERRNO for IO error, or zLib error for uncompress error) +*/ +extern int ZEXPORT unzReadCurrentFile (file, buf, len) + unzFile file; + voidp buf; + unsigned len; +{ + int err=UNZ_OK; + uInt iRead = 0; + unz_s* s; + file_in_zip_read_info_s* pfile_in_zip_read_info; + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + pfile_in_zip_read_info=s->pfile_in_zip_read; + + if (pfile_in_zip_read_info==NULL) + return UNZ_PARAMERROR; + + + if ((pfile_in_zip_read_info->read_buffer == NULL)) + return UNZ_END_OF_LIST_OF_FILE; + if (len==0) + return 0; + + pfile_in_zip_read_info->stream.next_out = (Bytef*)buf; + + pfile_in_zip_read_info->stream.avail_out = (uInt)len; + + if ((len>pfile_in_zip_read_info->rest_read_uncompressed) && + (!(pfile_in_zip_read_info->raw))) + pfile_in_zip_read_info->stream.avail_out = + (uInt)pfile_in_zip_read_info->rest_read_uncompressed; + + if ((len>pfile_in_zip_read_info->rest_read_compressed+ + pfile_in_zip_read_info->stream.avail_in) && + (pfile_in_zip_read_info->raw)) + pfile_in_zip_read_info->stream.avail_out = + (uInt)pfile_in_zip_read_info->rest_read_compressed+ + pfile_in_zip_read_info->stream.avail_in; + + while (pfile_in_zip_read_info->stream.avail_out>0) + { + if ((pfile_in_zip_read_info->stream.avail_in==0) && + (pfile_in_zip_read_info->rest_read_compressed>0)) + { + uInt uReadThis = UNZ_BUFSIZE; + if (pfile_in_zip_read_info->rest_read_compressedrest_read_compressed; + if (uReadThis == 0) + return UNZ_EOF; + if (ZSEEK(pfile_in_zip_read_info->z_filefunc, + pfile_in_zip_read_info->filestream, + pfile_in_zip_read_info->pos_in_zipfile + + pfile_in_zip_read_info->byte_before_the_zipfile, + ZLIB_FILEFUNC_SEEK_SET)!=0) + return UNZ_ERRNO; + if (ZREAD(pfile_in_zip_read_info->z_filefunc, + pfile_in_zip_read_info->filestream, + pfile_in_zip_read_info->read_buffer, + uReadThis)!=uReadThis) + return UNZ_ERRNO; + + +# ifndef NOUNCRYPT + if(s->encrypted) + { + uInt i; + for(i=0;iread_buffer[i] = + zdecode(s->keys,s->pcrc_32_tab, + pfile_in_zip_read_info->read_buffer[i]); + } +# endif + + + pfile_in_zip_read_info->pos_in_zipfile += uReadThis; + + pfile_in_zip_read_info->rest_read_compressed-=uReadThis; + + pfile_in_zip_read_info->stream.next_in = + (Bytef*)pfile_in_zip_read_info->read_buffer; + pfile_in_zip_read_info->stream.avail_in = (uInt)uReadThis; + } + + if ((pfile_in_zip_read_info->compression_method==0) || (pfile_in_zip_read_info->raw)) + { + uInt uDoCopy,i ; + + if ((pfile_in_zip_read_info->stream.avail_in == 0) && + (pfile_in_zip_read_info->rest_read_compressed == 0)) + return (iRead==0) ? UNZ_EOF : iRead; + + if (pfile_in_zip_read_info->stream.avail_out < + pfile_in_zip_read_info->stream.avail_in) + uDoCopy = pfile_in_zip_read_info->stream.avail_out ; + else + uDoCopy = pfile_in_zip_read_info->stream.avail_in ; + + for (i=0;istream.next_out+i) = + *(pfile_in_zip_read_info->stream.next_in+i); + + pfile_in_zip_read_info->crc32 = crc32(pfile_in_zip_read_info->crc32, + pfile_in_zip_read_info->stream.next_out, + uDoCopy); + pfile_in_zip_read_info->rest_read_uncompressed-=uDoCopy; + pfile_in_zip_read_info->stream.avail_in -= uDoCopy; + pfile_in_zip_read_info->stream.avail_out -= uDoCopy; + pfile_in_zip_read_info->stream.next_out += uDoCopy; + pfile_in_zip_read_info->stream.next_in += uDoCopy; + pfile_in_zip_read_info->stream.total_out += uDoCopy; + iRead += uDoCopy; + } + else + { + uLong uTotalOutBefore,uTotalOutAfter; + const Bytef *bufBefore; + uLong uOutThis; + int flush=Z_SYNC_FLUSH; + + uTotalOutBefore = pfile_in_zip_read_info->stream.total_out; + bufBefore = pfile_in_zip_read_info->stream.next_out; + + /* + if ((pfile_in_zip_read_info->rest_read_uncompressed == + pfile_in_zip_read_info->stream.avail_out) && + (pfile_in_zip_read_info->rest_read_compressed == 0)) + flush = Z_FINISH; + */ + err=inflate(&pfile_in_zip_read_info->stream,flush); + + if ((err>=0) && (pfile_in_zip_read_info->stream.msg!=NULL)) + err = Z_DATA_ERROR; + + uTotalOutAfter = pfile_in_zip_read_info->stream.total_out; + uOutThis = uTotalOutAfter-uTotalOutBefore; + + pfile_in_zip_read_info->crc32 = + crc32(pfile_in_zip_read_info->crc32,bufBefore, + (uInt)(uOutThis)); + + pfile_in_zip_read_info->rest_read_uncompressed -= + uOutThis; + + iRead += (uInt)(uTotalOutAfter - uTotalOutBefore); + + if (err==Z_STREAM_END) + return (iRead==0) ? UNZ_EOF : iRead; + if (err!=Z_OK) + break; + } + } + + if (err==Z_OK) + return iRead; + return err; +} + + +/* + Give the current position in uncompressed data +*/ +extern z_off_t ZEXPORT unztell (file) + unzFile file; +{ + unz_s* s; + file_in_zip_read_info_s* pfile_in_zip_read_info; + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + pfile_in_zip_read_info=s->pfile_in_zip_read; + + if (pfile_in_zip_read_info==NULL) + return UNZ_PARAMERROR; + + return (z_off_t)pfile_in_zip_read_info->stream.total_out; +} + + +/* + return 1 if the end of file was reached, 0 elsewhere +*/ +extern int ZEXPORT unzeof (file) + unzFile file; +{ + unz_s* s; + file_in_zip_read_info_s* pfile_in_zip_read_info; + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + pfile_in_zip_read_info=s->pfile_in_zip_read; + + if (pfile_in_zip_read_info==NULL) + return UNZ_PARAMERROR; + + if (pfile_in_zip_read_info->rest_read_uncompressed == 0) + return 1; + else + return 0; +} + + + +/* + Read extra field from the current file (opened by unzOpenCurrentFile) + This is the local-header version of the extra field (sometimes, there is + more info in the local-header version than in the central-header) + + if buf==NULL, it return the size of the local extra field that can be read + + if buf!=NULL, len is the size of the buffer, the extra header is copied in + buf. + the return value is the number of bytes copied in buf, or (if <0) + the error code +*/ +extern int ZEXPORT unzGetLocalExtrafield (file,buf,len) + unzFile file; + voidp buf; + unsigned len; +{ + unz_s* s; + file_in_zip_read_info_s* pfile_in_zip_read_info; + uInt read_now; + uLong size_to_read; + + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + pfile_in_zip_read_info=s->pfile_in_zip_read; + + if (pfile_in_zip_read_info==NULL) + return UNZ_PARAMERROR; + + size_to_read = (pfile_in_zip_read_info->size_local_extrafield - + pfile_in_zip_read_info->pos_local_extrafield); + + if (buf==NULL) + return (int)size_to_read; + + if (len>size_to_read) + read_now = (uInt)size_to_read; + else + read_now = (uInt)len ; + + if (read_now==0) + return 0; + + if (ZSEEK(pfile_in_zip_read_info->z_filefunc, + pfile_in_zip_read_info->filestream, + pfile_in_zip_read_info->offset_local_extrafield + + pfile_in_zip_read_info->pos_local_extrafield, + ZLIB_FILEFUNC_SEEK_SET)!=0) + return UNZ_ERRNO; + + if (ZREAD(pfile_in_zip_read_info->z_filefunc, + pfile_in_zip_read_info->filestream, + buf,read_now)!=read_now) + return UNZ_ERRNO; + + return (int)read_now; +} + +/* + Close the file in zip opened with unzipOpenCurrentFile + Return UNZ_CRCERROR if all the file was read but the CRC is not good +*/ +extern int ZEXPORT unzCloseCurrentFile (file) + unzFile file; +{ + int err=UNZ_OK; + + unz_s* s; + file_in_zip_read_info_s* pfile_in_zip_read_info; + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + pfile_in_zip_read_info=s->pfile_in_zip_read; + + if (pfile_in_zip_read_info==NULL) + return UNZ_PARAMERROR; + + + if ((pfile_in_zip_read_info->rest_read_uncompressed == 0) && + (!pfile_in_zip_read_info->raw)) + { + if (pfile_in_zip_read_info->crc32 != pfile_in_zip_read_info->crc32_wait) + err=UNZ_CRCERROR; + } + + + TRYFREE(pfile_in_zip_read_info->read_buffer); + pfile_in_zip_read_info->read_buffer = NULL; + if (pfile_in_zip_read_info->stream_initialised) + inflateEnd(&pfile_in_zip_read_info->stream); + + pfile_in_zip_read_info->stream_initialised = 0; + TRYFREE(pfile_in_zip_read_info); + + s->pfile_in_zip_read=NULL; + + return err; +} + + +/* + Get the global comment string of the ZipFile, in the szComment buffer. + uSizeBuf is the size of the szComment buffer. + return the number of byte copied or an error code <0 +*/ +extern int ZEXPORT unzGetGlobalComment (file, szComment, uSizeBuf) + unzFile file; + char *szComment; + uLong uSizeBuf; +{ + unz_s* s; + uLong uReadThis ; + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + + uReadThis = uSizeBuf; + if (uReadThis>s->gi.size_comment) + uReadThis = s->gi.size_comment; + + if (ZSEEK(s->z_filefunc,s->filestream,s->central_pos+22,ZLIB_FILEFUNC_SEEK_SET)!=0) + return UNZ_ERRNO; + + if (uReadThis>0) + { + *szComment='\0'; + if (ZREAD(s->z_filefunc,s->filestream,szComment,uReadThis)!=uReadThis) + return UNZ_ERRNO; + } + + if ((szComment != NULL) && (uSizeBuf > s->gi.size_comment)) + *(szComment+s->gi.size_comment)='\0'; + return (int)uReadThis; +} + +/* Additions by RX '2004 */ +extern uLong ZEXPORT unzGetOffset (file) + unzFile file; +{ + unz_s* s; + + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + if (!s->current_file_ok) + return 0; + if (s->gi.number_entry != 0 && s->gi.number_entry != 0xffff) + if (s->num_file==s->gi.number_entry) + return 0; + return s->pos_in_central_dir; +} + +extern int ZEXPORT unzSetOffset (file, pos) + unzFile file; + uLong pos; +{ + unz_s* s; + int err; + + if (file==NULL) + return UNZ_PARAMERROR; + s=(unz_s*)file; + + s->pos_in_central_dir = pos; + s->num_file = s->gi.number_entry; /* hack */ + err = unzlocal_GetCurrentFileInfoInternal(file,&s->cur_file_info, + &s->cur_file_info_internal, + NULL,0,NULL,0,NULL,0); + s->current_file_ok = (err == UNZ_OK); + return err; +} diff --git a/supergameboy/libgambatte/src/file/unzip/unzip.h b/supergameboy/libgambatte/src/file/unzip/unzip.h new file mode 100644 index 00000000..5bb6a696 --- /dev/null +++ b/supergameboy/libgambatte/src/file/unzip/unzip.h @@ -0,0 +1,354 @@ +/* unzip.h -- IO for uncompress .zip files using zlib + Version 1.01e, February 12th, 2005 + + Copyright (C) 1998-2005 Gilles Vollant + + This unzip package allow extract file from .ZIP file, compatible with PKZip 2.04g + WinZip, InfoZip tools and compatible. + + Multi volume ZipFile (span) are not supported. + Encryption compatible with pkzip 2.04g only supported + Old compressions used by old PKZip 1.x are not supported + + + I WAIT FEEDBACK at mail info@winimage.com + Visit also http://www.winimage.com/zLibDll/unzip.htm for evolution + + Condition of use and distribution are the same than zlib : + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + +*/ + +/* for more info about .ZIP format, see + http://www.info-zip.org/pub/infozip/doc/appnote-981119-iz.zip + http://www.info-zip.org/pub/infozip/doc/ + PkWare has also a specification at : + ftp://ftp.pkware.com/probdesc.zip +*/ + +#ifndef _unz_H +#define _unz_H + +#ifdef __cplusplus +extern "C" { +#endif + +#ifndef _ZLIB_H +#include +#endif + +#ifndef _ZLIBIOAPI_H +#include "ioapi.h" +#endif + +#if defined(STRICTUNZIP) || defined(STRICTZIPUNZIP) +/* like the STRICT of WIN32, we define a pointer that cannot be converted + from (void*) without cast */ +typedef struct TagunzFile__ { int unused; } unzFile__; +typedef unzFile__ *unzFile; +#else +typedef voidp unzFile; +#endif + + +#define UNZ_OK (0) +#define UNZ_END_OF_LIST_OF_FILE (-100) +#define UNZ_ERRNO (Z_ERRNO) +#define UNZ_EOF (0) +#define UNZ_PARAMERROR (-102) +#define UNZ_BADZIPFILE (-103) +#define UNZ_INTERNALERROR (-104) +#define UNZ_CRCERROR (-105) + +/* tm_unz contain date/time info */ +typedef struct tm_unz_s +{ + uInt tm_sec; /* seconds after the minute - [0,59] */ + uInt tm_min; /* minutes after the hour - [0,59] */ + uInt tm_hour; /* hours since midnight - [0,23] */ + uInt tm_mday; /* day of the month - [1,31] */ + uInt tm_mon; /* months since January - [0,11] */ + uInt tm_year; /* years - [1980..2044] */ +} tm_unz; + +/* unz_global_info structure contain global data about the ZIPfile + These data comes from the end of central dir */ +typedef struct unz_global_info_s +{ + uLong number_entry; /* total number of entries in + the central dir on this disk */ + uLong size_comment; /* size of the global comment of the zipfile */ +} unz_global_info; + + +/* unz_file_info contain information about a file in the zipfile */ +typedef struct unz_file_info_s +{ + uLong version; /* version made by 2 bytes */ + uLong version_needed; /* version needed to extract 2 bytes */ + uLong flag; /* general purpose bit flag 2 bytes */ + uLong compression_method; /* compression method 2 bytes */ + uLong dosDate; /* last mod file date in Dos fmt 4 bytes */ + uLong crc; /* crc-32 4 bytes */ + uLong compressed_size; /* compressed size 4 bytes */ + uLong uncompressed_size; /* uncompressed size 4 bytes */ + uLong size_filename; /* filename length 2 bytes */ + uLong size_file_extra; /* extra field length 2 bytes */ + uLong size_file_comment; /* file comment length 2 bytes */ + + uLong disk_num_start; /* disk number start 2 bytes */ + uLong internal_fa; /* internal file attributes 2 bytes */ + uLong external_fa; /* external file attributes 4 bytes */ + + tm_unz tmu_date; +} unz_file_info; + +extern int ZEXPORT unzStringFileNameCompare OF ((const char* fileName1, + const char* fileName2, + int iCaseSensitivity)); +/* + Compare two filename (fileName1,fileName2). + If iCaseSenisivity = 1, comparision is case sensitivity (like strcmp) + If iCaseSenisivity = 2, comparision is not case sensitivity (like strcmpi + or strcasecmp) + If iCaseSenisivity = 0, case sensitivity is defaut of your operating system + (like 1 on Unix, 2 on Windows) +*/ + + +extern unzFile ZEXPORT unzOpen OF((const char *path)); +/* + Open a Zip file. path contain the full pathname (by example, + on a Windows XP computer "c:\\zlib\\zlib113.zip" or on an Unix computer + "zlib/zlib113.zip". + If the zipfile cannot be opened (file don't exist or in not valid), the + return value is NULL. + Else, the return value is a unzFile Handle, usable with other function + of this unzip package. +*/ + +extern unzFile ZEXPORT unzOpen2 OF((const char *path, + zlib_filefunc_def* pzlib_filefunc_def)); +/* + Open a Zip file, like unzOpen, but provide a set of file low level API + for read/write the zip file (see ioapi.h) +*/ + +extern int ZEXPORT unzClose OF((unzFile file)); +/* + Close a ZipFile opened with unzipOpen. + If there is files inside the .Zip opened with unzOpenCurrentFile (see later), + these files MUST be closed with unzipCloseCurrentFile before call unzipClose. + return UNZ_OK if there is no problem. */ + +extern int ZEXPORT unzGetGlobalInfo OF((unzFile file, + unz_global_info *pglobal_info)); +/* + Write info about the ZipFile in the *pglobal_info structure. + No preparation of the structure is needed + return UNZ_OK if there is no problem. */ + + +extern int ZEXPORT unzGetGlobalComment OF((unzFile file, + char *szComment, + uLong uSizeBuf)); +/* + Get the global comment string of the ZipFile, in the szComment buffer. + uSizeBuf is the size of the szComment buffer. + return the number of byte copied or an error code <0 +*/ + + +/***************************************************************************/ +/* Unzip package allow you browse the directory of the zipfile */ + +extern int ZEXPORT unzGoToFirstFile OF((unzFile file)); +/* + Set the current file of the zipfile to the first file. + return UNZ_OK if there is no problem +*/ + +extern int ZEXPORT unzGoToNextFile OF((unzFile file)); +/* + Set the current file of the zipfile to the next file. + return UNZ_OK if there is no problem + return UNZ_END_OF_LIST_OF_FILE if the actual file was the latest. +*/ + +extern int ZEXPORT unzLocateFile OF((unzFile file, + const char *szFileName, + int iCaseSensitivity)); +/* + Try locate the file szFileName in the zipfile. + For the iCaseSensitivity signification, see unzStringFileNameCompare + + return value : + UNZ_OK if the file is found. It becomes the current file. + UNZ_END_OF_LIST_OF_FILE if the file is not found +*/ + + +/* ****************************************** */ +/* Ryan supplied functions */ +/* unz_file_info contain information about a file in the zipfile */ +typedef struct unz_file_pos_s +{ + uLong pos_in_zip_directory; /* offset in zip file directory */ + uLong num_of_file; /* # of file */ +} unz_file_pos; + +extern int ZEXPORT unzGetFilePos( + unzFile file, + unz_file_pos* file_pos); + +extern int ZEXPORT unzGoToFilePos( + unzFile file, + unz_file_pos* file_pos); + +/* ****************************************** */ + +extern int ZEXPORT unzGetCurrentFileInfo OF((unzFile file, + unz_file_info *pfile_info, + char *szFileName, + uLong fileNameBufferSize, + void *extraField, + uLong extraFieldBufferSize, + char *szComment, + uLong commentBufferSize)); +/* + Get Info about the current file + if pfile_info!=NULL, the *pfile_info structure will contain somes info about + the current file + if szFileName!=NULL, the filemane string will be copied in szFileName + (fileNameBufferSize is the size of the buffer) + if extraField!=NULL, the extra field information will be copied in extraField + (extraFieldBufferSize is the size of the buffer). + This is the Central-header version of the extra field + if szComment!=NULL, the comment string of the file will be copied in szComment + (commentBufferSize is the size of the buffer) +*/ + +/***************************************************************************/ +/* for reading the content of the current zipfile, you can open it, read data + from it, and close it (you can close it before reading all the file) + */ + +extern int ZEXPORT unzOpenCurrentFile OF((unzFile file)); +/* + Open for reading data the current file in the zipfile. + If there is no error, the return value is UNZ_OK. +*/ + +extern int ZEXPORT unzOpenCurrentFilePassword OF((unzFile file, + const char* password)); +/* + Open for reading data the current file in the zipfile. + password is a crypting password + If there is no error, the return value is UNZ_OK. +*/ + +extern int ZEXPORT unzOpenCurrentFile2 OF((unzFile file, + int* method, + int* level, + int raw)); +/* + Same than unzOpenCurrentFile, but open for read raw the file (not uncompress) + if raw==1 + *method will receive method of compression, *level will receive level of + compression + note : you can set level parameter as NULL (if you did not want known level, + but you CANNOT set method parameter as NULL +*/ + +extern int ZEXPORT unzOpenCurrentFile3 OF((unzFile file, + int* method, + int* level, + int raw, + const char* password)); +/* + Same than unzOpenCurrentFile, but open for read raw the file (not uncompress) + if raw==1 + *method will receive method of compression, *level will receive level of + compression + note : you can set level parameter as NULL (if you did not want known level, + but you CANNOT set method parameter as NULL +*/ + + +extern int ZEXPORT unzCloseCurrentFile OF((unzFile file)); +/* + Close the file in zip opened with unzOpenCurrentFile + Return UNZ_CRCERROR if all the file was read but the CRC is not good +*/ + +extern int ZEXPORT unzReadCurrentFile OF((unzFile file, + voidp buf, + unsigned len)); +/* + Read bytes from the current file (opened by unzOpenCurrentFile) + buf contain buffer where data must be copied + len the size of buf. + + return the number of byte copied if somes bytes are copied + return 0 if the end of file was reached + return <0 with error code if there is an error + (UNZ_ERRNO for IO error, or zLib error for uncompress error) +*/ + +extern z_off_t ZEXPORT unztell OF((unzFile file)); +/* + Give the current position in uncompressed data +*/ + +extern int ZEXPORT unzeof OF((unzFile file)); +/* + return 1 if the end of file was reached, 0 elsewhere +*/ + +extern int ZEXPORT unzGetLocalExtrafield OF((unzFile file, + voidp buf, + unsigned len)); +/* + Read extra field from the current file (opened by unzOpenCurrentFile) + This is the local-header version of the extra field (sometimes, there is + more info in the local-header version than in the central-header) + + if buf==NULL, it return the size of the local extra field + + if buf!=NULL, len is the size of the buffer, the extra header is copied in + buf. + the return value is the number of bytes copied in buf, or (if <0) + the error code +*/ + +/***************************************************************************/ + +/* Get the current file offset */ +extern uLong ZEXPORT unzGetOffset (unzFile file); + +/* Set the current file offset */ +extern int ZEXPORT unzSetOffset (unzFile file, uLong pos); + + + +#ifdef __cplusplus +} +#endif + +#endif /* _unz_H */ diff --git a/supergameboy/libgambatte/src/gambatte.cpp b/supergameboy/libgambatte/src/gambatte.cpp new file mode 100644 index 00000000..27354c91 --- /dev/null +++ b/supergameboy/libgambatte/src/gambatte.cpp @@ -0,0 +1,184 @@ +/*************************************************************************** + * Copyright (C) 2007 by Sindre Aam�s * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#include "gambatte.h" +#include "cpu.h" +#include "savestate.h" +#include "statesaver.h" +#include "initstate.h" +#include "state_osd_elements.h" +#include +#include + +#include + +static const std::string itos(int i) { + std::stringstream ss; + + ss << i; + + std::string out; + + ss >> out; + + return out; +} + +static const std::string statePath(const std::string &basePath, int stateNo) { + return basePath + "_" + itos(stateNo) + ".gqs"; +} + +namespace Gambatte { +GB::GB() : z80(new CPU), stateNo(1) {} + +GB::~GB() { + delete z80; +} + +unsigned GB::runFor(Gambatte::uint_least32_t *const soundBuf, const unsigned samples) { + z80->setSoundBuffer(soundBuf); + z80->runFor(samples * 2); + + return z80->fillSoundBuffer(); +} + +void GB::updateVideo() { + z80->updateVideo(); +} + +unsigned GB::lyCounter() { + return z80->lyCounter(); +} + +void GB::reset() { + z80->saveSavedata(); + + SaveState state; + z80->setStatePtrs(state); + setInitState(state, z80->isCgb()); + z80->loadState(state); + z80->loadSavedata(); + + z80->setAccumulator(supergameboy.version == 0 ? 0x01 : 0xff); + +// z80->reset(); +} + +void GB::setVideoBlitter(VideoBlitter *vb) { + z80->setVideoBlitter(vb); +} + +void GB::videoBufferChange() { + z80->videoBufferChange(); +} + +unsigned GB::videoWidth() const { + return z80->videoWidth(); +} + +unsigned GB::videoHeight() const { + return z80->videoHeight(); +} + +void GB::setVideoFilter(const unsigned n) { + z80->setVideoFilter(n); +} + +std::vector GB::filterInfo() const { + return z80->filterInfo(); +} + +void GB::setInputStateGetter(InputStateGetter *getInput) { + z80->setInputStateGetter(getInput); +} + +void GB::set_savedir(const char *sdir) { + z80->set_savedir(sdir); +} + +bool GB::load(const bool forceDmg) { + const bool failed = z80->load(forceDmg); + + if (!failed) { + SaveState state; + z80->setStatePtrs(state); + setInitState(state, z80->isCgb()); + z80->loadState(state); + z80->loadSavedata(); + + z80->setAccumulator(supergameboy.version == 0 ? 0x01 : 0xff); + + stateNo = 1; + z80->setOsdElement(std::auto_ptr()); + } + + return failed; +} + +bool GB::isCgb() const { + return z80->isCgb(); +} + +void GB::setDmgPaletteColor(unsigned palNum, unsigned colorNum, unsigned rgb32) { + z80->setDmgPaletteColor(palNum, colorNum, rgb32); +} + +void GB::saveSavedata() { + z80->saveSavedata(); +} + +void GB::loadState(const char *const filepath, const bool osdMessage) { + z80->saveSavedata(); + + SaveState state; + z80->setStatePtrs(state); + + if (StateSaver::loadState(state, filepath)) { + z80->loadState(state); + + if (osdMessage) + z80->setOsdElement(newStateLoadedOsdElement(stateNo)); + } +} + +void GB::saveState() { + saveState(statePath(z80->saveBasePath(), stateNo).c_str()); + z80->setOsdElement(newStateSavedOsdElement(stateNo)); +} + +void GB::loadState() { + loadState(statePath(z80->saveBasePath(), stateNo).c_str(), true); +} + +void GB::saveState(const char *filepath) { + SaveState state; + z80->setStatePtrs(state); + z80->saveState(state); + StateSaver::saveState(state, filepath); +} + +void GB::loadState(const char *const filepath) { + loadState(filepath, false); +} + +void GB::selectState(int n) { + n -= (n / 10) * 10; + stateNo = n < 0 ? n + 10 : n; + z80->setOsdElement(newSaveStateOsdElement(statePath(z80->saveBasePath(), stateNo).c_str(), stateNo)); +} +} diff --git a/supergameboy/libgambatte/src/initstate.cpp b/supergameboy/libgambatte/src/initstate.cpp new file mode 100644 index 00000000..c16d48b4 --- /dev/null +++ b/supergameboy/libgambatte/src/initstate.cpp @@ -0,0 +1,281 @@ +/*************************************************************************** + * Copyright (C) 2008 by Sindre Aamås * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#include "initstate.h" +#include "savestate.h" +#include +#include +#include "sound/sound_unit.h" +#include "memory.h" + +void setInitState(SaveState &state, const bool cgb) { + static const unsigned char feaxDump[0x60] = { + 0x18, 0x01, 0xEF, 0xDE, 0x06, 0x4A, 0xCD, 0xBD, + 0x18, 0x01, 0xEF, 0xDE, 0x06, 0x4A, 0xCD, 0xBD, + 0x18, 0x01, 0xEF, 0xDE, 0x06, 0x4A, 0xCD, 0xBD, + 0x18, 0x01, 0xEF, 0xDE, 0x06, 0x4A, 0xCD, 0xBD, + 0x00, 0x90, 0xF7, 0x7F, 0xC0, 0xB1, 0xB4, 0xFB, + 0x00, 0x90, 0xF7, 0x7F, 0xC0, 0xB1, 0xB4, 0xFB, + 0x00, 0x90, 0xF7, 0x7F, 0xC0, 0xB1, 0xB4, 0xFB, + 0x00, 0x90, 0xF7, 0x7F, 0xC0, 0xB1, 0xB4, 0xFB, + 0x24, 0x1B, 0xFD, 0x3A, 0x10, 0x12, 0xAD, 0x45, + 0x24, 0x1B, 0xFD, 0x3A, 0x10, 0x12, 0xAD, 0x45, + 0x24, 0x1B, 0xFD, 0x3A, 0x10, 0x12, 0xAD, 0x45, + 0x24, 0x1B, 0xFD, 0x3A, 0x10, 0x12, 0xAD, 0x45 + }; + + static const unsigned char ffxxDump[0x100] = { + 0xCF, 0x00, 0x7C, 0xFF, 0x43, 0x00, 0x00, 0xF8, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE1, + 0x80, 0xBF, 0xF3, 0xFF, 0xBF, 0xFF, 0x3F, 0x00, + 0xFF, 0xBF, 0x7F, 0xFF, 0x9F, 0xFF, 0xBF, 0xFF, + 0xFF, 0x00, 0x00, 0xBF, 0x77, 0xF3, 0xF1, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, + 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, + 0x00, 0x00, 0x00, 0x00, 0xFF, 0x7E, 0xFF, 0xFE, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x3E, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xC0, 0xFF, 0xC1, 0x00, 0xFE, 0xFF, 0xFF, 0xFF, + 0xF8, 0xFF, 0x00, 0x00, 0x00, 0x8F, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xCE, 0xED, 0x66, 0x66, 0xCC, 0x0D, 0x00, 0x0B, + 0x03, 0x73, 0x00, 0x83, 0x00, 0x0C, 0x00, 0x0D, + 0x00, 0x08, 0x11, 0x1F, 0x88, 0x89, 0x00, 0x0E, + 0xDC, 0xCC, 0x6E, 0xE6, 0xDD, 0xDD, 0xD9, 0x99, + 0xBB, 0xBB, 0x67, 0x63, 0x6E, 0x0E, 0xEC, 0xCC, + 0xDD, 0xDC, 0x99, 0x9F, 0xBB, 0xB9, 0x33, 0x3E, + 0x45, 0xEC, 0x52, 0xFA, 0x08, 0xB7, 0x07, 0x5D, + 0x01, 0xFD, 0xC0, 0xFF, 0x08, 0xFC, 0x00, 0xE5, + 0x0B, 0xF8, 0xC2, 0xCE, 0xF4, 0xF9, 0x0F, 0x7F, + 0x45, 0x6D, 0x3D, 0xFE, 0x46, 0x97, 0x33, 0x5E, + 0x08, 0xEF, 0xF1, 0xFF, 0x86, 0x83, 0x24, 0x74, + 0x12, 0xFC, 0x00, 0x9F, 0xB4, 0xB7, 0x06, 0xD5, + 0xD0, 0x7A, 0x00, 0x9E, 0x04, 0x5F, 0x41, 0x2F, + 0x1D, 0x77, 0x36, 0x75, 0x81, 0xAA, 0x70, 0x3A, + 0x98, 0xD1, 0x71, 0x02, 0x4D, 0x01, 0xC1, 0xFF, + 0x0D, 0x00, 0xD3, 0x05, 0xF9, 0x00, 0x0B, 0x00 + }; + + static const unsigned char cgbObjpDump[0x40] = { + 0x00, 0x00, 0xF2, 0xAB, + 0x61, 0xC2, 0xD9, 0xBA, + 0x88, 0x6E, 0xDD, 0x63, + 0x28, 0x27, 0xFB, 0x9F, + 0x35, 0x42, 0xD6, 0xD4, + 0x50, 0x48, 0x57, 0x5E, + 0x23, 0x3E, 0x3D, 0xCA, + 0x71, 0x21, 0x37, 0xC0, + 0xC6, 0xB3, 0xFB, 0xF9, + 0x08, 0x00, 0x8D, 0x29, + 0xA3, 0x20, 0xDB, 0x87, + 0x62, 0x05, 0x5D, 0xD4, + 0x0E, 0x08, 0xFE, 0xAF, + 0x20, 0x02, 0xD7, 0xFF, + 0x07, 0x6A, 0x55, 0xEC, + 0x83, 0x40, 0x0B, 0x77 + }; + + state.cpu.cycleCounter = 0x102A0; + state.cpu.PC = 0x100; + state.cpu.SP = 0xFFFE; + state.cpu.A = (cgb * 0x10) | 0x01; + state.cpu.B = 0x00; + state.cpu.C = 0x13; + state.cpu.D = 0x00; + state.cpu.E = 0xD8; + state.cpu.F = 0xB0; + state.cpu.H = 0x01; + state.cpu.L = 0x4D; + state.cpu.skip = false; + state.cpu.halted = false; + + + std::memset(state.mem.vram.ptr, 0, state.mem.vram.getSz()); + std::memset(state.mem.sram.ptr, 0xFF, state.mem.sram.getSz()); + + for (unsigned addr = 0x0000; addr < 0x0800; addr += 0x10) { + std::memset(state.mem.wram.ptr + addr + 0x00, 0xFF, 0x08); + std::memset(state.mem.wram.ptr + addr + 0x08, 0x00, 0x08); + } + + for (unsigned addr = 0x0800; addr < 0x1000; addr += 0x10) { + std::memset(state.mem.wram.ptr + addr + 0x00, 0x00, 0x08); + std::memset(state.mem.wram.ptr + addr + 0x08, 0xFF, 0x08); + } + + for (unsigned addr = 0x0E00; addr < 0x1000; addr += 0x10) { + state.mem.wram.ptr[addr + 0x02] = 0xFF; + state.mem.wram.ptr[addr + 0x0A] = 0x00; + } + + for (unsigned addr = 0x1000; addr < state.mem.wram.getSz(); addr += 0x1000) + std::memcpy(state.mem.wram.ptr + addr, state.mem.wram.ptr, 0x1000); + + std::memset(state.mem.ioamhram.ptr, 0x00, state.mem.ioamhram.getSz()); + std::memcpy(state.mem.ioamhram.ptr + 0xA0, feaxDump, sizeof(feaxDump)); + std::memcpy(state.mem.ioamhram.ptr + 0x100, ffxxDump, sizeof(ffxxDump)); + + state.mem.ioamhram.ptr[0x104] = 0x1C; + state.mem.ioamhram.ptr[0x140] = 0x91; + state.mem.ioamhram.ptr[0x144] = 0x00; + + if (!cgb) { + state.mem.ioamhram.ptr[0x130] = 0xAC; + state.mem.ioamhram.ptr[0x131] = 0xDD; + state.mem.ioamhram.ptr[0x132] = 0xDA; + state.mem.ioamhram.ptr[0x133] = 0x48; + state.mem.ioamhram.ptr[0x134] = 0x36; + state.mem.ioamhram.ptr[0x135] = 0x02; + state.mem.ioamhram.ptr[0x136] = 0xCF; + state.mem.ioamhram.ptr[0x137] = 0x16; + state.mem.ioamhram.ptr[0x138] = 0x2C; + state.mem.ioamhram.ptr[0x139] = 0x04; + state.mem.ioamhram.ptr[0x13A] = 0xE5; + state.mem.ioamhram.ptr[0x13B] = 0x2C; + state.mem.ioamhram.ptr[0x13C] = 0xAC; + state.mem.ioamhram.ptr[0x13D] = 0xDD; + state.mem.ioamhram.ptr[0x13E] = 0xDA; + state.mem.ioamhram.ptr[0x13F] = 0x48; + + state.mem.ioamhram.ptr[0x14D] = 0xFF; + state.mem.ioamhram.ptr[0x14F] = 0xFF; + state.mem.ioamhram.ptr[0x156] = 0xFF; + state.mem.ioamhram.ptr[0x168] = 0xFF; + state.mem.ioamhram.ptr[0x16A] = 0xFF; + state.mem.ioamhram.ptr[0x16B] = 0xFF; + state.mem.ioamhram.ptr[0x16C] = 0xFF; + state.mem.ioamhram.ptr[0x170] = 0xFF; + state.mem.ioamhram.ptr[0x172] = 0xFF; + state.mem.ioamhram.ptr[0x173] = 0xFF; + state.mem.ioamhram.ptr[0x174] = 0xFF; + state.mem.ioamhram.ptr[0x175] = 0xFF; + state.mem.ioamhram.ptr[0x176] = 0xFF; + state.mem.ioamhram.ptr[0x177] = 0xFF; + } + + state.mem.div_lastUpdate = 0; + state.mem.tima_lastUpdate = 0; + state.mem.tmatime = Memory::COUNTER_DISABLED; + state.mem.next_serialtime = Memory::COUNTER_DISABLED; + state.mem.lastOamDmaUpdate = Memory::COUNTER_DISABLED; + state.mem.minIntTime = 0; + state.mem.rombank = 1; + state.mem.dmaSource = 0; + state.mem.dmaDestination = 0; + state.mem.rambank = 0; + state.mem.oamDmaPos = 0xFE; + state.mem.IME = false; + state.mem.enable_ram = false; + state.mem.rambank_mode = false; + state.mem.hdma_transfer = false; + + + for (unsigned i = 0x00; i < 0x40; i += 0x02) { + state.ppu.bgpData.ptr[i] = 0xFF; + state.ppu.bgpData.ptr[i + 1] = 0x7F; + } + + std::memcpy(state.ppu.objpData.ptr, cgbObjpDump, sizeof(cgbObjpDump)); + + if (!cgb) { + state.ppu.bgpData.ptr[0] = state.mem.ioamhram.get()[0x147]; + state.ppu.objpData.ptr[0] = state.mem.ioamhram.get()[0x148]; + state.ppu.objpData.ptr[1] = state.mem.ioamhram.get()[0x149]; + } + + for (unsigned pos = 0; pos < 80; ++pos) + state.ppu.oamReaderBuf.ptr[pos] = state.mem.ioamhram.ptr[((pos * 2) & ~3) | (pos & 1)]; + + std::fill_n(state.ppu.oamReaderSzbuf.ptr, 40, false); + + state.ppu.videoCycles = 144*456ul + 164; + state.ppu.enableDisplayM0Time = state.cpu.cycleCounter - state.ppu.videoCycles + 159; + state.ppu.winYPos = 0xFF; + state.ppu.drawStartCycle = 90; + state.ppu.scReadOffset = 90; + state.ppu.lcdc = state.mem.ioamhram.get()[0x140]; + state.ppu.scx[1] = state.ppu.scx[0] = 0; + state.ppu.scy[1] = state.ppu.scy[0] = 0; + state.ppu.scxAnd7 = 0; + state.ppu.weMaster = false; + state.ppu.wx = 0; + state.ppu.wy = 0; + state.ppu.lycIrqSkip = false; + + + state.spu.cycleCounter = 0x1000 | ((state.cpu.cycleCounter >> 1) & 0xFFF); // spu.cycleCounter >> 12 & 7 represents the frame sequencer position. + + state.spu.ch1.sweep.counter = SoundUnit::COUNTER_DISABLED; + state.spu.ch1.sweep.shadow = 0; + state.spu.ch1.sweep.nr0 = 0; + state.spu.ch1.sweep.negging = false; + state.spu.ch1.duty.nextPosUpdate = (state.spu.cycleCounter & ~1) + 2048 * 2; + state.spu.ch1.duty.nr3 = 0; + state.spu.ch1.duty.pos = 0; + state.spu.ch1.env.counter = SoundUnit::COUNTER_DISABLED; + state.spu.ch1.env.volume = 0; + state.spu.ch1.lcounter.counter = SoundUnit::COUNTER_DISABLED; + state.spu.ch1.lcounter.lengthCounter = 0x40; + state.spu.ch1.nr4 = 0; + state.spu.ch1.master = true; + + state.spu.ch2.duty.nextPosUpdate = (state.spu.cycleCounter & ~1) + 2048 * 2; + state.spu.ch2.duty.nr3 = 0; + state.spu.ch2.duty.pos = 0; + state.spu.ch2.env.counter = state.spu.cycleCounter - ((state.spu.cycleCounter - 0x1000) & 0x7FFF) + 8ul * 0x8000; + state.spu.ch2.env.volume = 0; + state.spu.ch2.lcounter.counter = SoundUnit::COUNTER_DISABLED; + state.spu.ch2.lcounter.lengthCounter = 0x40; + state.spu.ch2.nr4 = 0; + state.spu.ch2.master = false; + + for (unsigned i = 0; i < 0x10; ++i) + state.spu.ch3.waveRam.ptr[i] = state.mem.ioamhram.get()[0x130 + i]; + + state.spu.ch3.lcounter.counter = SoundUnit::COUNTER_DISABLED; + state.spu.ch3.lcounter.lengthCounter = 0x100; + state.spu.ch3.waveCounter = SoundUnit::COUNTER_DISABLED; + state.spu.ch3.lastReadTime = SoundUnit::COUNTER_DISABLED; + state.spu.ch3.nr3 = 0; + state.spu.ch3.nr4 = 0; + state.spu.ch3.wavePos = 0; + state.spu.ch3.sampleBuf = 0; + state.spu.ch3.master = false; + + state.spu.ch4.lfsr.counter = state.spu.cycleCounter + 4; + state.spu.ch4.lfsr.reg = 0xFF; + state.spu.ch4.env.counter = state.spu.cycleCounter - ((state.spu.cycleCounter - 0x1000) & 0x7FFF) + 8ul * 0x8000; + state.spu.ch4.env.volume = 0; + state.spu.ch4.lcounter.counter = SoundUnit::COUNTER_DISABLED; + state.spu.ch4.lcounter.lengthCounter = 0x40; + state.spu.ch4.nr4 = 0; + state.spu.ch4.master = false; + + state.rtc.baseTime = std::time(0); + state.rtc.haltTime = state.rtc.baseTime; + state.rtc.index = 5; + state.rtc.dataDh = 0; + state.rtc.dataDl = 0; + state.rtc.dataH = 0; + state.rtc.dataM = 0; + state.rtc.dataS = 0; + state.rtc.lastLatchData = false; +} diff --git a/supergameboy/libgambatte/src/initstate.h b/supergameboy/libgambatte/src/initstate.h new file mode 100644 index 00000000..d550eed5 --- /dev/null +++ b/supergameboy/libgambatte/src/initstate.h @@ -0,0 +1,26 @@ +/*************************************************************************** + * Copyright (C) 2008 by Sindre Aamås * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#ifndef INITSTATE_H +#define INITSTATE_H + +class SaveState; + +void setInitState(SaveState &state, bool cgb); + +#endif diff --git a/supergameboy/libgambatte/src/insertion_sort.h b/supergameboy/libgambatte/src/insertion_sort.h new file mode 100644 index 00000000..939ba074 --- /dev/null +++ b/supergameboy/libgambatte/src/insertion_sort.h @@ -0,0 +1,51 @@ +/*************************************************************************** + * Copyright (C) 2007 by Sindre Aamås * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#ifndef INSERTION_SORT_H +#define INSERTION_SORT_H + +#include + +template +void insertionSort(T *const start, T *const end, Less less) { + if (start >= end) + return; + + T *a = start; + + while (++a < end) { + const T e = *a; + + T *b = a; + + while (b != start && less(e, *(b - 1))) { + *b = *(b - 1); + b = b - 1; + } + + *b = e; + } +} + +template +inline void insertionSort(T *const start, T *const end) { + insertionSort(start, end, std::less()); +} + +#endif /*INSERTION_SORT_H*/ diff --git a/supergameboy/libgambatte/src/interrupter.cpp b/supergameboy/libgambatte/src/interrupter.cpp new file mode 100644 index 00000000..aea9df41 --- /dev/null +++ b/supergameboy/libgambatte/src/interrupter.cpp @@ -0,0 +1,44 @@ +/*************************************************************************** + * Copyright (C) 2007 by Sindre Aam�s * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#include "interrupter.h" + +#include "memory.h" + +Interrupter::Interrupter(unsigned short &SP_in, unsigned short &PC_in, bool &halted_in) : + SP(SP_in), + PC(PC_in), + halted(halted_in) +{} + +unsigned long Interrupter::interrupt(const unsigned address, unsigned long cycleCounter, Memory &memory) { + if (halted && memory.isCgb()) + cycleCounter += 4; + + halted = false; + cycleCounter += 8; + SP = (SP - 1) & 0xFFFF; + memory.write(SP, PC >> 8, cycleCounter); + cycleCounter += 4; + SP = (SP - 1) & 0xFFFF; + memory.write(SP, PC & 0xFF, cycleCounter); + PC = address; + cycleCounter += 8; + + return cycleCounter; +} diff --git a/supergameboy/libgambatte/src/interrupter.h b/supergameboy/libgambatte/src/interrupter.h new file mode 100644 index 00000000..18e0d9e1 --- /dev/null +++ b/supergameboy/libgambatte/src/interrupter.h @@ -0,0 +1,38 @@ +/*************************************************************************** + * Copyright (C) 2007 by Sindre Aam�s * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#ifndef INTERRUPTER_H +#define INTERRUPTER_H + +class Memory; + +class Interrupter { + unsigned short &SP; + unsigned short &PC; + bool &halted; + +public: + Interrupter(unsigned short &SP, unsigned short &PC, bool &halted); + unsigned long interrupt(const unsigned address, unsigned long cycleCounter, Memory &memory); + + void unhalt() { + halted = false; + } +}; + +#endif diff --git a/supergameboy/libgambatte/src/memory.cpp b/supergameboy/libgambatte/src/memory.cpp new file mode 100644 index 00000000..2211733d --- /dev/null +++ b/supergameboy/libgambatte/src/memory.cpp @@ -0,0 +1,1867 @@ +/*************************************************************************** + * Copyright (C) 2007 by Sindre Aam�s * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#include "memory.h" +#include "video.h" +#include "sound.h" +#include "inputstate.h" +#include "inputstategetter.h" +#include "savestate.h" +#include "file/file.h" +#include +#include + +// static const uint32_t timaClock[4]={ 1024, 16, 64, 256 }; +static const unsigned char timaClock[4] = { 10, 4, 6, 8 }; + +Memory::Memory(const Interrupter &interrupter_in) : +memchunk(NULL), +rambankdata(NULL), +rdisabled_ram(NULL), +wdisabled_ram(NULL), +oamDmaSrc(NULL), +vrambank(vram), +rsrambankptr(NULL), +wsrambankptr(NULL), +getInput(NULL), +div_lastUpdate(0), +tima_lastUpdate(0), +next_timatime(COUNTER_DISABLED), +next_blittime(144*456ul), +nextIntTime(COUNTER_DISABLED), +minIntTime(0), +next_dmatime(COUNTER_DISABLED), +next_hdmaReschedule(COUNTER_DISABLED), +next_unhalttime(COUNTER_DISABLED), +next_endtime(0), +tmatime(COUNTER_DISABLED), +next_serialtime(COUNTER_DISABLED), +lastOamDmaUpdate(COUNTER_DISABLED), +nextOamEventTime(COUNTER_DISABLED), +display(ioamhram, vram), +interrupter(interrupter_in), +romtype(plain), +rombanks(1), +rombank(1), +dmaSource(0), +dmaDestination(0), +rambank(0), +rambanks(1), +oamDmaArea1Lower(0), +oamDmaArea1Width(0), +oamDmaArea2Upper(0), +oamDmaPos(0xFE), +cgb(false), +doubleSpeed(false), +IME(false), +enable_ram(false), +rambank_mode(false), +battery(false), +rtcRom(false), +hdma_transfer(false), +active(false) +{ + romdata[1] = romdata[0] = NULL; + wramdata[1] = wramdata[0] = NULL; + std::fill_n(rmem, 0x10, static_cast(NULL)); + std::fill_n(wmem, 0x10, static_cast(NULL)); + set_irqEvent(); + set_event(); +} + +void Memory::setStatePtrs(SaveState &state) { + state.mem.vram.set(vram, sizeof vram); + state.mem.sram.set(rambankdata, rambanks * 0x2000ul); + state.mem.wram.set(wramdata[0], isCgb() ? 0x8000 : 0x2000); + state.mem.ioamhram.set(ioamhram, sizeof ioamhram); + + display.setStatePtrs(state); + sound.setStatePtrs(state); +} + +unsigned long Memory::saveState(SaveState &state, unsigned long cycleCounter) { + cycleCounter = resetCounters(cycleCounter); + nontrivial_ff_read(0xFF0F, cycleCounter); + nontrivial_ff_read(0xFF26, cycleCounter); + + state.mem.div_lastUpdate = div_lastUpdate; + state.mem.tima_lastUpdate = tima_lastUpdate; + state.mem.tmatime = tmatime; + state.mem.next_serialtime = next_serialtime; + state.mem.lastOamDmaUpdate = lastOamDmaUpdate; + state.mem.minIntTime = minIntTime; + state.mem.rombank = rombank; + state.mem.dmaSource = dmaSource; + state.mem.dmaDestination = dmaDestination; + state.mem.rambank = rambank; + state.mem.oamDmaPos = oamDmaPos; + state.mem.IME = IME; + state.mem.enable_ram = enable_ram; + state.mem.rambank_mode = rambank_mode; + state.mem.hdma_transfer = hdma_transfer; + + rtc.saveState(state); + display.saveState(state); + sound.saveState(state); + + return cycleCounter; +} + +void Memory::loadState(const SaveState &state, const unsigned long oldCc) { + sound.loadState(state); + display.loadState(state, state.mem.oamDmaPos < 0xA0 ? rdisabled_ram : ioamhram); + rtc.loadState(state, rtcRom ? state.mem.enable_ram : false); + + div_lastUpdate = state.mem.div_lastUpdate; + tima_lastUpdate = state.mem.tima_lastUpdate; + tmatime = state.mem.tmatime; + next_serialtime = state.mem.next_serialtime; + lastOamDmaUpdate = state.mem.lastOamDmaUpdate; + minIntTime = state.mem.minIntTime; + rombank = state.mem.rombank & (rombanks - 1); + dmaSource = state.mem.dmaSource; + dmaDestination = state.mem.dmaDestination; + rambank = state.mem.rambank & (rambanks - 1); + oamDmaPos = state.mem.oamDmaPos; + IME = state.mem.IME; + enable_ram = state.mem.enable_ram; + rambank_mode = state.mem.rambank_mode; + hdma_transfer = state.mem.hdma_transfer; + + const bool oldDs = doubleSpeed; + doubleSpeed = isCgb() & ioamhram[0x14D] >> 7; + oamDmaArea2Upper = oamDmaArea1Width = oamDmaArea1Lower = 0; + vrambank = vram + (ioamhram[0x14F] & 0x01 & isCgb()) * 0x2000; + wramdata[1] = wramdata[0] + ((isCgb() && (ioamhram[0x170] & 0x07)) ? (ioamhram[0x170] & 0x07) : 1) * 0x1000; + std::fill_n(rmem, 0x10, static_cast(NULL)); + std::fill_n(wmem, 0x10, static_cast(NULL)); + setBanks(); + + if (lastOamDmaUpdate != COUNTER_DISABLED) { + oamDmaInitSetup(); + + unsigned oamEventPos = 0x100; + + if (oamDmaPos < 0xA0) { + setOamDmaArea(); + oamEventPos = 0xA0; + } + + nextOamEventTime = lastOamDmaUpdate + (oamEventPos - oamDmaPos) * 4; + setOamDmaSrc(); + } + + if (!IME && state.cpu.halted) + schedule_unhalt(); + + next_blittime = (ioamhram[0x140] & 0x80) ? display.nextMode1IrqTime() : static_cast(COUNTER_DISABLED); + + const unsigned long cycleCounter = state.cpu.cycleCounter; + + if (hdma_transfer) { + next_dmatime = display.nextHdmaTime(cycleCounter); + next_hdmaReschedule = display.nextHdmaTimeInvalid(); + } else { + next_hdmaReschedule = next_dmatime = COUNTER_DISABLED; + } + + next_timatime = (ioamhram[0x107] & 4) ? tima_lastUpdate + ((256u - ioamhram[0x105]) << timaClock[ioamhram[0x107] & 3]) + 1 : static_cast(COUNTER_DISABLED); + set_irqEvent(); + rescheduleIrq(cycleCounter); + + if (oldDs != isDoubleSpeed()) + next_endtime = cycleCounter - (isDoubleSpeed() ?( oldCc - next_endtime) << 1 :( oldCc - next_endtime) >> 1); + else + next_endtime = cycleCounter - (oldCc - next_endtime); + +// set_event(); +} + +void Memory::schedule_unhalt() { + next_unhalttime = std::min(next_irqEventTime, display.nextIrqEvent()); + + if (next_unhalttime != COUNTER_DISABLED) + next_unhalttime += isCgb() * 4; + + set_event(); +} + +void Memory::rescheduleIrq(const unsigned long cycleCounter) { + if (IME) { + ioamhram[0x10F] |= display.getIfReg(cycleCounter) & 3; + + nextIntTime = (ioamhram[0x10F] & ioamhram[0x1FF] & 0x1F) ? cycleCounter : std::min(next_irqEventTime, display.nextIrqEvent()); + + if (nextIntTime < minIntTime) + nextIntTime = minIntTime; + + set_event(); + } +} + +void Memory::rescheduleHdmaReschedule() { + if (hdma_transfer && (ioamhram[0x140] & 0x80)) { + const unsigned long newTime = display.nextHdmaTimeInvalid(); + + if (newTime < next_hdmaReschedule) { + next_hdmaReschedule = newTime; + + if (newTime < next_eventtime) { + next_eventtime = newTime; + next_event = HDMA_RESCHEDULE; + } + } + } +} + +void Memory::ei(const unsigned long cycleCounter) { + IME = 1; + minIntTime = cycleCounter + 1; + rescheduleIrq(cycleCounter); +} + +void Memory::incEndtime(const unsigned long inc) { + active = true; + next_endtime += inc << isDoubleSpeed(); + set_event(); +} + +void Memory::setEndtime(const unsigned long cycleCounter, const unsigned long inc) { + next_endtime = cycleCounter; + incEndtime(inc); +} + +void Memory::set_irqEvent() { + next_irqEventTime = next_timatime; + next_irqEvent = TIMA; + + if (next_serialtime < next_irqEventTime) { + next_irqEvent = SERIAL; + next_irqEventTime = next_serialtime; + } +} + +void Memory::update_irqEvents(const unsigned long cc) { + while (next_irqEventTime <= cc) { + switch (next_irqEvent) { + case TIMA: + ioamhram[0x10F] |= 4; + next_timatime += (256u - ioamhram[0x106]) << timaClock[ioamhram[0x107] & 3]; + break; + case SERIAL: + next_serialtime = COUNTER_DISABLED; + ioamhram[0x101] = 0xFF; + ioamhram[0x102] &= 0x7F; + ioamhram[0x10F] |= 8; + break; + } + + set_irqEvent(); + } +} + +void Memory::set_event() { + next_event = INTERRUPTS; + next_eventtime = nextIntTime; + if (next_hdmaReschedule < next_eventtime) { + next_eventtime = next_hdmaReschedule; + next_event = HDMA_RESCHEDULE; + } + if (next_dmatime < next_eventtime) { + next_eventtime = next_dmatime; + next_event = DMA; + } + if (next_unhalttime < next_eventtime) { + next_eventtime = next_unhalttime; + next_event = UNHALT; + } + if (nextOamEventTime < next_eventtime) { + next_eventtime = nextOamEventTime; + next_event = OAM; + } + if (next_blittime < next_eventtime) { + next_event = BLIT; + next_eventtime = next_blittime; + } + if (next_endtime < next_eventtime) { + next_eventtime = next_endtime; + next_event = END; + } +} + +unsigned long Memory::event(unsigned long cycleCounter) { + if (lastOamDmaUpdate != COUNTER_DISABLED) + updateOamDma(cycleCounter); + + switch (next_event) { + case HDMA_RESCHEDULE: +// printf("hdma_reschedule\n"); + next_dmatime = display.nextHdmaTime(cycleCounter); + next_hdmaReschedule = display.nextHdmaTimeInvalid(); + break; + case DMA: +// printf("dma\n"); + { + const bool doubleSpeed = isDoubleSpeed(); + unsigned dmaSrc = dmaSource; + unsigned dmaDest = dmaDestination; + unsigned dmaLength = ((ioamhram[0x155] & 0x7F) + 0x1) * 0x10; + + unsigned length = hdma_transfer ? 0x10 : dmaLength; + + if ((static_cast(dmaDest) + length) & 0x10000) { + length = 0x10000 - dmaDest; + ioamhram[0x155] |= 0x80; + } + + dmaLength -= length; + + if (!(ioamhram[0x140] & 0x80)) + dmaLength = 0; + + { + unsigned long lOamDmaUpdate = lastOamDmaUpdate; + lastOamDmaUpdate = COUNTER_DISABLED; + + while (length--) { + const unsigned src = dmaSrc++ & 0xFFFF; + const unsigned data = ((src & 0xE000) == 0x8000 || src > 0xFDFF) ? 0xFF : read(src, cycleCounter); + + cycleCounter += 2 << doubleSpeed; + + if (cycleCounter - 3 > lOamDmaUpdate) { + oamDmaPos = (oamDmaPos + 1) & 0xFF; + lOamDmaUpdate += 4; + + if (oamDmaPos < 0xA0) { + if (oamDmaPos == 0) + startOamDma(lOamDmaUpdate - 2); + + ioamhram[src & 0xFF] = data; + } else if (oamDmaPos == 0xA0) { + endOamDma(lOamDmaUpdate - 2); + lOamDmaUpdate = COUNTER_DISABLED; + } + } + + nontrivial_write(0x8000 | (dmaDest++ & 0x1FFF), data, cycleCounter); + } + + lastOamDmaUpdate = lOamDmaUpdate; + } + + cycleCounter += 4; + + dmaSource = dmaSrc; + dmaDestination = dmaDest; + ioamhram[0x155] = ((dmaLength / 0x10 - 0x1) & 0xFF) | (ioamhram[0x155] & 0x80); + + if (ioamhram[0x155] & 0x80) { + next_hdmaReschedule = next_dmatime = COUNTER_DISABLED; + hdma_transfer = 0; + } + + if (hdma_transfer) { + if (lastOamDmaUpdate != COUNTER_DISABLED) + updateOamDma(cycleCounter); + + next_dmatime = display.nextHdmaTime(cycleCounter); + } + } + + break; + case INTERRUPTS: +// printf("interrupts\n"); + update_irqEvents(cycleCounter); + ioamhram[0x10F] |= display.getIfReg(cycleCounter) & 3; + + { + /*unsigned interrupt = ioamhram[0x10F] & ioamhram[0x1FF]; + interrupt |= interrupt << 1; + interrupt |= interrupt << 2; + interrupt |= interrupt << 1; + interrupt = ~interrupt; + ++interrupt; + interrupt &= 0x1F; + + if (interrupt) { + ioamhram[0x10F] &= ~interrupt; + display.setIfReg(ioamhram[0x10F], CycleCounter); + IME = false; + + unsigned address = interrupt; + interrupt >>= 1; + address -= interrupt & 0x0C; + interrupt >>= 1; + address -= interrupt & 5; + address += interrupt >> 2; + + address <<= 3; + address += 0x38; + + z80.interrupt(address); + }*/ + + const unsigned interrupt = ioamhram[0x10F] & ioamhram[0x1FF] & 0x1F; + + if (interrupt) { + unsigned n; + unsigned address; + + if ((n = interrupt & 0x01)) + address = 0x40; + else if ((n = interrupt & 0x02)) + address = 0x48; + else if ((n = interrupt & 0x04)) + address = 0x50; + else if ((n = interrupt & 0x08)) + address = 0x58; + else { + n = 0x10; + address = 0x60; + } + + ioamhram[0x10F] &= ~n; + display.setIfReg(ioamhram[0x10F], cycleCounter); + IME = false; + cycleCounter = interrupter.interrupt(address, cycleCounter, *this); + } + } + + nextIntTime = IME ? std::min(next_irqEventTime, display.nextIrqEvent()) : static_cast(COUNTER_DISABLED); + break; + case BLIT: +// printf("blit\n"); + display.updateScreen(next_blittime); + + if (ioamhram[0x140] & 0x80) + next_blittime += 70224 << isDoubleSpeed(); + else + next_blittime = COUNTER_DISABLED; + + break; + case UNHALT: +// printf("unhalt\n"); + update_irqEvents(cycleCounter); + ioamhram[0x10F] |= display.getIfReg(cycleCounter) & 3; + + if (ioamhram[0x10F] & ioamhram[0x1FF] & 0x1F) { + next_unhalttime = COUNTER_DISABLED; + interrupter.unhalt(); + } else + next_unhalttime = std::min(next_irqEventTime, display.nextIrqEvent()) + isCgb() * 4; + + break; + case OAM: + nextOamEventTime = lastOamDmaUpdate == COUNTER_DISABLED ? static_cast(COUNTER_DISABLED) : nextOamEventTime + 0xA0 * 4; + break; + case END: + { + const unsigned long endtime = next_endtime; + next_endtime = COUNTER_DISABLED; + set_event(); + + while (cycleCounter >= next_eventtime) + cycleCounter = event(cycleCounter); + + next_endtime = endtime; + active = false; + } + + break; + } + + set_event(); + + return cycleCounter; +} + +void Memory::speedChange(const unsigned long cycleCounter) { + if (isCgb() && (ioamhram[0x14D] & 0x1)) { + std::printf("speedChange\n"); + + update_irqEvents(cycleCounter); + sound.generate_samples(cycleCounter, isDoubleSpeed()); + display.preSpeedChange(cycleCounter); + + ioamhram[0x14D] = ~ioamhram[0x14D] & 0x80; + doubleSpeed = ioamhram[0x14D] >> 7; + + display.postSpeedChange(cycleCounter); + + if (hdma_transfer) { + next_dmatime = display.nextHdmaTime(cycleCounter); + next_hdmaReschedule = display.nextHdmaTimeInvalid(); + } + + next_blittime = (ioamhram[0x140] & 0x80) ? display.nextMode1IrqTime() : static_cast(COUNTER_DISABLED); + next_endtime = cycleCounter + (isDoubleSpeed() ?( next_endtime - cycleCounter) << 1 : ((next_endtime - cycleCounter) >> 1)); + set_irqEvent(); + rescheduleIrq(cycleCounter); + set_event(); + } +} + +static void decCycles(unsigned long &counter, const unsigned long dec) { + if (counter != Memory::COUNTER_DISABLED) + counter -= dec; +} + +unsigned long Memory::resetCounters(unsigned long cycleCounter) { + std::printf("resetting counters\n"); + + if (lastOamDmaUpdate != COUNTER_DISABLED) + updateOamDma(cycleCounter); + + update_irqEvents(cycleCounter); + rescheduleIrq(cycleCounter); + display.preResetCounter(cycleCounter); + + const unsigned long oldCC = cycleCounter; + + { + const unsigned long divinc = (cycleCounter - div_lastUpdate) >> 8; + ioamhram[0x104] = (ioamhram[0x104] + divinc) & 0xFF; + div_lastUpdate += divinc << 8; + } + + if (ioamhram[0x107] & 0x04) { + update_tima(cycleCounter); + } + + const unsigned long dec = cycleCounter < 0x10000 ? 0 : (cycleCounter & ~0x7FFFul) - 0x8000; + + minIntTime = minIntTime < cycleCounter ? 0 : minIntTime - dec; + + if (ioamhram[0x107] & 0x04) + decCycles(tima_lastUpdate, dec); + + decCycles(div_lastUpdate, dec); + decCycles(lastOamDmaUpdate, dec); + decCycles(next_eventtime, dec); + decCycles(next_irqEventTime, dec); + decCycles(next_timatime, dec); + decCycles(next_blittime, dec); + decCycles(nextOamEventTime, dec); + decCycles(next_endtime, dec); + decCycles(next_dmatime, dec); + decCycles(next_hdmaReschedule, dec); + decCycles(nextIntTime, dec); + decCycles(next_serialtime, dec); + decCycles(tmatime, dec); + decCycles(next_unhalttime, dec); + + cycleCounter -= dec; + + display.postResetCounter(oldCC, cycleCounter); + sound.resetCounter(cycleCounter, oldCC, isDoubleSpeed()); + + return cycleCounter; +} + +void Memory::updateInput() { + unsigned joypadId = 0x0F; + unsigned button = 0xFF; + unsigned dpad = 0xFF; + + if (getInput) { + const Gambatte::InputState &is = (*getInput)(); + + joypadId = is.joypadId; + + button ^= is.startButton << 3; + button ^= is.selectButton << 2; + button ^= is.bButton << 1; + button ^= is.aButton; + + dpad ^= is.dpadDown << 3; + dpad ^= is.dpadUp << 2; + dpad ^= is.dpadLeft << 1; + dpad ^= is.dpadRight; + } + + ioamhram[0x100] |= 0xF; + + if ((ioamhram[0x100] & 0x30) == 0x30) { + ioamhram[0x100] &= 0xf0; + ioamhram[0x100] |= joypadId; + } else { + if (!(ioamhram[0x100] & 0x10)) + ioamhram[0x100] &= dpad; + + if (!(ioamhram[0x100] & 0x20)) + ioamhram[0x100] &= button; + } +} + +void Memory::setRombank() { + unsigned bank = rombank; + + if ((romtype == mbc1 && !(bank & 0x1F)) || (romtype == mbc5 && !bank)) + ++bank; + + romdata[1] = romdata[0] + bank * 0x4000ul - 0x4000; + + if (oamDmaArea1Lower != 0xA0) { + rmem[0x7] = rmem[0x6] = rmem[0x5] = rmem[0x4] = romdata[1]; + } else + setOamDmaSrc(); +} + +void Memory::setRambank() { + rmem[0xB] = rmem[0xA] = rsrambankptr = rdisabled_ram - 0xA000; + wmem[0xB] = wmem[0xA] = wsrambankptr = wdisabled_ram - 0xA000; + + if (enable_ram) { + if (rtc.getActive()) { + wmem[0xB] = wmem[0xA] = rmem[0xB] = rmem[0xA] = wsrambankptr = rsrambankptr = NULL; + } else if (rambanks) { + wmem[0xB] = rmem[0xB] = wmem[0xA] = rmem[0xA] = wsrambankptr = rsrambankptr = rambankdata + rambank * 0x2000ul - 0xA000; + } + } + + if (oamDmaArea1Lower == 0xA0) { + wmem[0xB] = wmem[0xA] = rmem[0xB] = rmem[0xA] = NULL; + setOamDmaSrc(); + } +} + +void Memory::setBanks() { + rmem[0x3] = rmem[0x2] = rmem[0x1] = rmem[0x0] = romdata[0]; + + setRombank(); + setRambank(); + + rmem[0xC] = wmem[0xC] = wramdata[0] - 0xC000; + rmem[0xD] = wmem[0xD] = wramdata[1] - 0xD000; + rmem[0xE] = wmem[0xE] = wramdata[0] - 0xE000; +} + +void Memory::updateOamDma(const unsigned long cycleCounter) { + unsigned cycles = (cycleCounter - lastOamDmaUpdate) >> 2; + + while (cycles--) { + oamDmaPos = (oamDmaPos + 1) & 0xFF; + lastOamDmaUpdate += 4; + + //TODO: reads from vram while the ppu is reading vram should return whatever the ppu is reading. + if (oamDmaPos < 0xA0) { + if (oamDmaPos == 0) + startOamDma(lastOamDmaUpdate - 2); + + ioamhram[oamDmaPos] = oamDmaSrc ? oamDmaSrc[oamDmaPos] : *rtc.getActive(); + } else if (oamDmaPos == 0xA0) { + endOamDma(lastOamDmaUpdate - 2); + lastOamDmaUpdate = COUNTER_DISABLED; + break; + } + } +} + +void Memory::setOamDmaArea() { + if (ioamhram[0x146] < 0xC0) { + if ((ioamhram[0x146] & 0xE0) != 0x80) + oamDmaArea2Upper = 0x80; + + oamDmaArea1Width = 0x20; + } else if (ioamhram[0x146] < 0xE0) + oamDmaArea1Width = 0x3E; +} + +void Memory::oamDmaInitSetup() { + if (ioamhram[0x146] < 0xC0) { + if ((ioamhram[0x146] & 0xE0) == 0x80) { + oamDmaArea1Lower = 0x80; + } else { + oamDmaArea1Lower = 0xA0; + std::fill_n(rmem, 0x8, static_cast(NULL)); + rmem[0xB] = rmem[0xA] = NULL; + wmem[0xB] = wmem[0xA] = NULL; + } + } else if (ioamhram[0x146] < 0xE0) { + oamDmaArea1Lower = 0xC0; + rmem[0xE] = rmem[0xD] = rmem[0xC] = NULL; + wmem[0xE] = wmem[0xD] = wmem[0xC] = NULL; + } +} + +void Memory::setOamDmaSrc() { + oamDmaSrc = NULL; + + if (ioamhram[0x146] < 0xC0) { + if ((ioamhram[0x146] & 0xE0) == 0x80) { + oamDmaSrc = vrambank + (ioamhram[0x146] << 8 & 0x1FFF); + } else { + if (ioamhram[0x146] < 0x80) + oamDmaSrc = romdata[ioamhram[0x146] >> 6] + (ioamhram[0x146] << 8); + else if (rsrambankptr) + oamDmaSrc = rsrambankptr + (ioamhram[0x146] << 8); + } + } else if (ioamhram[0x146] < 0xE0) { + oamDmaSrc = wramdata[ioamhram[0x146] >> 4 & 1] + (ioamhram[0x146] << 8 & 0xFFF); + } else + oamDmaSrc = rdisabled_ram; +} + +void Memory::startOamDma(const unsigned long cycleCounter) { + setOamDmaArea(); + display.oamChange(rdisabled_ram, cycleCounter); + + if (next_unhalttime != COUNTER_DISABLED) + schedule_unhalt(); + else + rescheduleIrq(cycleCounter); + + rescheduleHdmaReschedule(); +} + +void Memory::endOamDma(const unsigned long cycleCounter) { + oamDmaArea2Upper = oamDmaArea1Width = oamDmaArea1Lower = 0; + oamDmaPos = 0xFE; + setBanks(); + display.oamChange(ioamhram, cycleCounter); + + if (next_unhalttime != COUNTER_DISABLED) + schedule_unhalt(); + else + rescheduleIrq(cycleCounter); + + rescheduleHdmaReschedule(); +} + +void Memory::update_tima(const unsigned long cycleCounter) { + const unsigned long ticks = (cycleCounter - tima_lastUpdate) >> timaClock[ioamhram[0x107] & 3]; + + tima_lastUpdate += ticks << timaClock[ioamhram[0x107] & 3]; + + if (cycleCounter >= tmatime) { + if (cycleCounter >= tmatime + 4) + tmatime = COUNTER_DISABLED; + + ioamhram[0x105] = ioamhram[0x106]; + } + + unsigned long tmp = ioamhram[0x105] + ticks; + + while (tmp > 0x100) + tmp -= 0x100 - ioamhram[0x106]; + + if (tmp == 0x100) { + tmp = 0; + tmatime = tima_lastUpdate + 3; + + if (cycleCounter >= tmatime) { + if (cycleCounter >= tmatime + 4) + tmatime = COUNTER_DISABLED; + + tmp = ioamhram[0x106]; + } + } + + ioamhram[0x105] = tmp; +} + +unsigned Memory::nontrivial_ff_read(const unsigned P, const unsigned long cycleCounter) { + if (lastOamDmaUpdate != COUNTER_DISABLED) + updateOamDma(cycleCounter); + + switch (P & 0x7F) { + case 0x00: + updateInput(); + break; + case 0x04: +// printf("div read\n"); + { + const unsigned long divcycles = (cycleCounter - div_lastUpdate) >> 8; + ioamhram[0x104] = (ioamhram[0x104] + divcycles) & 0xFF; + div_lastUpdate += divcycles << 8; + } + + break; + case 0x05: +// printf("tima read\n"); + if (ioamhram[0x107] & 0x04) + update_tima(cycleCounter); + + break; + case 0x0F: + update_irqEvents(cycleCounter); + ioamhram[0x10F] |= display.getIfReg(cycleCounter) & 3; +// rescheduleIrq(cycleCounter); + break; + case 0x26: +// printf("sound status read\n"); + if (ioamhram[0x126] & 0x80) { + sound.generate_samples(cycleCounter, isDoubleSpeed()); + ioamhram[0x126] = 0xF0 | sound.getStatus(); + } else + ioamhram[0x126] = 0x70; + + break; + case 0x30: + case 0x31: + case 0x32: + case 0x33: + case 0x34: + case 0x35: + case 0x36: + case 0x37: + case 0x38: + case 0x39: + case 0x3A: + case 0x3B: + case 0x3C: + case 0x3D: + case 0x3E: + case 0x3F: + sound.generate_samples(cycleCounter, isDoubleSpeed()); + return sound.waveRamRead(P & 0xF); + case 0x41: + return ioamhram[0x141] | display.get_stat(ioamhram[0x145], cycleCounter); + case 0x44: + return display.getLyReg(cycleCounter/*+4*/); + case 0x69: + return display.cgbBgColorRead(ioamhram[0x168] & 0x3F, cycleCounter); + case 0x6B: + return display.cgbSpColorRead(ioamhram[0x16A] & 0x3F, cycleCounter); + default: break; + } + + return ioamhram[P - 0xFE00]; +} + +unsigned Memory::nontrivial_read(const unsigned P, const unsigned long cycleCounter) { + if (P < 0xFF80) { + if (lastOamDmaUpdate != COUNTER_DISABLED) { + updateOamDma(cycleCounter); + + if ((P >> 8) - oamDmaArea1Lower < oamDmaArea1Width || P >> 8 < oamDmaArea2Upper) + return ioamhram[oamDmaPos]; + } + + if (P < 0xC000) { + if (P < 0x8000) + return romdata[P >> 14][P]; + + if (P < 0xA000) { + if (!display.vramAccessible(cycleCounter)) + return 0xFF; + + return vrambank[P & 0x1FFF]; + } + + if (rsrambankptr) + return rsrambankptr[P]; + + return *rtc.getActive(); + } + + if (P < 0xFE00) + return wramdata[P >> 12 & 1][P & 0xFFF]; + + if (P & 0x100) + return nontrivial_ff_read(P, cycleCounter); + + if (!display.oamAccessible(cycleCounter) || oamDmaPos < 0xA0) + return 0xFF; + } + + return ioamhram[P - 0xFE00]; +} + +void Memory::nontrivial_ff_write(const unsigned P, unsigned data, const unsigned long cycleCounter) { +// printf("mem[0x%X] = 0x%X\n", P, data); + + if (lastOamDmaUpdate != COUNTER_DISABLED) + updateOamDma(cycleCounter); + + switch (P & 0xFF) { + case 0x00: + data = (ioamhram[0x100] & 0xCF) | (data & 0xF0); + supergameboy.joyp_write(data & 0x20, data & 0x10); + break; + case 0x01: + update_irqEvents(cycleCounter); + break; + case 0x02: + update_irqEvents(cycleCounter); + + if ((data & 0x81) == 0x81) { + next_serialtime = cycleCounter; + next_serialtime += (isCgb() && (data & 0x2)) ? 128 : 4096; + set_irqEvent(); + } + + rescheduleIrq(cycleCounter); + data |= 0x7C; + break; + //If rom is trying to write to DIV register, reset it to 0. + case 0x04: +// printf("DIV write\n"); + ioamhram[0x104] = 0; + div_lastUpdate = cycleCounter; + return; + case 0x05: + // printf("tima write\n"); + if (ioamhram[0x107] & 0x04) { + update_irqEvents(cycleCounter); + update_tima(cycleCounter); + + if (tmatime - cycleCounter < 4) + tmatime = COUNTER_DISABLED; + + next_timatime = tima_lastUpdate + ((256u - data) << timaClock[ioamhram[0x107] & 3]) + 1; + set_irqEvent(); + rescheduleIrq(cycleCounter); + } + + break; + case 0x06: + if (ioamhram[0x107] & 0x04) { + update_irqEvents(cycleCounter); + update_tima(cycleCounter); + } + + break; + case 0x07: + // printf("tac write: %i\n", data); + data |= 0xF8; + + if (ioamhram[0x107] ^ data) { + if (ioamhram[0x107] & 0x04) { + update_irqEvents(cycleCounter); + update_tima(cycleCounter); + + tima_lastUpdate -= (1u << (timaClock[ioamhram[0x107] & 3] - 1)) + 3; + tmatime -= (1u << (timaClock[ioamhram[0x107] & 3] - 1)) + 3; + next_timatime -= (1u << (timaClock[ioamhram[0x107] & 3] - 1)) + 3; + set_irqEvent(); + update_tima(cycleCounter); + update_irqEvents(cycleCounter); + + tmatime = COUNTER_DISABLED; + next_timatime = COUNTER_DISABLED; + } + + if (data & 4) { + tima_lastUpdate = (cycleCounter >> timaClock[data & 3]) << timaClock[data & 3]; + next_timatime = tima_lastUpdate + ((256u - ioamhram[0x105]) << timaClock[data & 3]) + 1; + } + + set_irqEvent(); + rescheduleIrq(cycleCounter); + } + + break; + case 0x0F: + update_irqEvents(cycleCounter); + display.setIfReg(data, cycleCounter); + ioamhram[0x10F] = 0xE0 | data; + rescheduleIrq(cycleCounter); + return; + case 0x10: + if(!sound.isEnabled()) return; + sound.generate_samples(cycleCounter, isDoubleSpeed()); + sound.set_nr10(data); + data |= 0x80; + break; + case 0x11: + if(!sound.isEnabled()) { + if (isCgb()) + return; + + data &= 0x3F; + } + + sound.generate_samples(cycleCounter, isDoubleSpeed()); + sound.set_nr11(data); + data |= 0x3F; + break; + case 0x12: + if(!sound.isEnabled()) return; + sound.generate_samples(cycleCounter, isDoubleSpeed()); + sound.set_nr12(data); + break; + case 0x13: + if(!sound.isEnabled()) return; + sound.generate_samples(cycleCounter, isDoubleSpeed()); + sound.set_nr13(data); + return; + case 0x14: + if(!sound.isEnabled()) return; + sound.generate_samples(cycleCounter, isDoubleSpeed()); + sound.set_nr14(data); + data |= 0xBF; + break; + case 0x16: + if(!sound.isEnabled()) { + if (isCgb()) + return; + + data &= 0x3F; + } + + sound.generate_samples(cycleCounter, isDoubleSpeed()); + sound.set_nr21(data); + data |= 0x3F; + break; + case 0x17: + if(!sound.isEnabled()) return; + sound.generate_samples(cycleCounter, isDoubleSpeed()); + sound.set_nr22(data); + break; + case 0x18: + if(!sound.isEnabled()) return; + sound.generate_samples(cycleCounter, isDoubleSpeed()); + sound.set_nr23(data); + return; + case 0x19: + if(!sound.isEnabled()) return; + sound.generate_samples(cycleCounter, isDoubleSpeed()); + sound.set_nr24(data); + data |= 0xBF; + break; + case 0x1A: + if(!sound.isEnabled()) return; + sound.generate_samples(cycleCounter, isDoubleSpeed()); + sound.set_nr30(data); + data |= 0x7F; + break; + case 0x1B: + if(!sound.isEnabled() && isCgb()) return; + sound.generate_samples(cycleCounter, isDoubleSpeed()); + sound.set_nr31(data); + return; + case 0x1C: + if(!sound.isEnabled()) return; + sound.generate_samples(cycleCounter, isDoubleSpeed()); + sound.set_nr32(data); + data |= 0x9F; + break; + case 0x1D: + if(!sound.isEnabled()) return; + sound.generate_samples(cycleCounter, isDoubleSpeed()); + sound.set_nr33(data); + return; + case 0x1E: + if(!sound.isEnabled()) return; + sound.generate_samples(cycleCounter, isDoubleSpeed()); + sound.set_nr34(data); + data |= 0xBF; + break; + case 0x20: + if(!sound.isEnabled() && isCgb()) return; + sound.generate_samples(cycleCounter, isDoubleSpeed()); + sound.set_nr41(data); + return; + case 0x21: + if(!sound.isEnabled()) return; + sound.generate_samples(cycleCounter, isDoubleSpeed()); + sound.set_nr42(data); + break; + case 0x22: + if(!sound.isEnabled()) return; + sound.generate_samples(cycleCounter, isDoubleSpeed()); + sound.set_nr43(data); + break; + case 0x23: + if(!sound.isEnabled()) return; + sound.generate_samples(cycleCounter, isDoubleSpeed()); + sound.set_nr44(data); + data |= 0xBF; + break; + case 0x24: + if(!sound.isEnabled()) return; + sound.generate_samples(cycleCounter, isDoubleSpeed()); + sound.set_so_volume(data); + break; + case 0x25: + if(!sound.isEnabled()) return; + sound.generate_samples(cycleCounter, isDoubleSpeed()); + sound.map_so(data); + break; + case 0x26: + if ((ioamhram[0x126] ^ data) & 0x80) { + sound.generate_samples(cycleCounter, isDoubleSpeed()); + + if (!(data & 0x80)) { + for (unsigned i = 0xFF10; i < 0xFF26; ++i) + ff_write(i, 0, cycleCounter); + +// std::memcpy(memory + 0xFF10, soundRegInitValues, sizeof(soundRegInitValues)); + sound.setEnabled(false); + } else { + sound.reset(/*memory + 0xFF00, isDoubleSpeed()*/); + sound.setEnabled(true); + } + } + + data = (data & 0x80) | (ioamhram[0x126] & 0x7F); + break; + case 0x30: + case 0x31: + case 0x32: + case 0x33: + case 0x34: + case 0x35: + case 0x36: + case 0x37: + case 0x38: + case 0x39: + case 0x3A: + case 0x3B: + case 0x3C: + case 0x3D: + case 0x3E: + case 0x3F: + sound.generate_samples(cycleCounter, isDoubleSpeed()); + sound.waveRamWrite(P & 0xF, data); + break; + case 0x40: + if (ioamhram[0x140] != data) { + if ((ioamhram[0x140] ^ data) & 0x80) { + update_irqEvents(cycleCounter); + const unsigned lyc = display.get_stat(ioamhram[0x145], cycleCounter) & 4; + display.enableChange(cycleCounter); + ioamhram[0x144] = 0; +// enable_display = bool(data & 0x80); + ioamhram[0x141] &= 0xF8; + + if (data & 0x80) { + next_blittime = display.nextMode1IrqTime() + (70224 << isDoubleSpeed()); + } else { + ioamhram[0x141] |= lyc; //Mr. Do! needs conicidence flag preserved. + next_blittime = cycleCounter + (456 * 4 << isDoubleSpeed()); + + if (hdma_transfer) + next_dmatime = cycleCounter; + + next_hdmaReschedule = COUNTER_DISABLED; + } + + set_event(); + } + + if ((ioamhram[0x140] ^ data) & 0x4) { + display.spriteSizeChange(data & 0x4, cycleCounter); + } + + if ((ioamhram[0x140] ^ data) & 0x20) { +// printf("%u: weChange to %u\n", CycleCounter, (data & 0x20) != 0); + display.weChange(data & 0x20, cycleCounter); + } + + if ((ioamhram[0x140] ^ data) & 0x40) + display.wdTileMapSelectChange(data & 0x40, cycleCounter); + + if ((ioamhram[0x140] ^ data) & 0x08) + display.bgTileMapSelectChange(data & 0x08, cycleCounter); + + if ((ioamhram[0x140] ^ data) & 0x10) + display.bgTileDataSelectChange(data & 0x10, cycleCounter); + + if ((ioamhram[0x140] ^ data) & 0x02) + display.spriteEnableChange(data & 0x02, cycleCounter); + + if ((ioamhram[0x140] ^ data) & 0x01) + display.bgEnableChange(data & 0x01, cycleCounter); + + ioamhram[0x140] = data; + rescheduleIrq(cycleCounter); + rescheduleHdmaReschedule(); + } + + return; + case 0x41: + display.lcdstatChange(data, cycleCounter); + rescheduleIrq(cycleCounter); + data = (ioamhram[0x141] & 0x87) | (data & 0x78); + break; + case 0x42: + display.scyChange(data, cycleCounter); + break; + case 0x43: + display.scxChange(data, cycleCounter); + rescheduleIrq(cycleCounter); + rescheduleHdmaReschedule(); + break; + //If rom is trying to write to LY register, reset it to 0. + case 0x44: + if (ioamhram[0x140] & 0x80) { + std::printf("ly write\n"); + display.lyWrite(cycleCounter); + rescheduleIrq(cycleCounter); + rescheduleHdmaReschedule(); + } + + return; + case 0x45: + display.lycRegChange(data, cycleCounter); + rescheduleIrq(cycleCounter); + break; + case 0x46: + if (lastOamDmaUpdate != COUNTER_DISABLED) + endOamDma(cycleCounter); + + lastOamDmaUpdate = cycleCounter; + nextOamEventTime = cycleCounter + 8; + ioamhram[0x146] = data; + oamDmaInitSetup(); + setOamDmaSrc(); + return; + case 0x47: + if (!isCgb()) { + display.dmgBgPaletteChange(data, cycleCounter); + } + + break; + case 0x48: + if (!isCgb()) { + display.dmgSpPalette1Change(data, cycleCounter); + } + + break; + case 0x49: + if (!isCgb()) { + display.dmgSpPalette2Change(data, cycleCounter); + } + + break; + case 0x4A: +// printf("%u: wyChange to %u\n", CycleCounter, data); + display.wyChange(data, cycleCounter); + rescheduleIrq(cycleCounter); + rescheduleHdmaReschedule(); + break; + case 0x4B: +// printf("%u: wxChange to %u\n", CycleCounter, data); + display.wxChange(data, cycleCounter); + rescheduleIrq(cycleCounter); + rescheduleHdmaReschedule(); + break; + + //cgb stuff: + case 0x4D: + ioamhram[0x14D] |= data & 0x01; + return; + //Select vram bank + case 0x4F: + if (isCgb()) { + vrambank = vram + (data & 0x01) * 0x2000; + + if (oamDmaArea1Lower == 0x80) + setOamDmaSrc(); + + ioamhram[0x14F] = 0xFE | data; + } + + return; + case 0x51: + dmaSource = data << 8 | (dmaSource & 0xFF); + return; + case 0x52: + dmaSource = (dmaSource & 0xFF00) | (data & 0xF0); + return; + case 0x53: + dmaDestination = data << 8 | (dmaDestination & 0xFF); + return; + case 0x54: + dmaDestination = (dmaDestination & 0xFF00) | (data & 0xF0); + return; + case 0x55: + if (!isCgb()) + return; + + ioamhram[0x155] = data & 0x7F; + + if (hdma_transfer) { + if (!(data & 0x80)) { + ioamhram[0x155] |= 0x80; + + if (next_dmatime > cycleCounter) { + hdma_transfer = 0; + next_hdmaReschedule = next_dmatime = COUNTER_DISABLED; + set_event(); + } + } + + return; + } + + if (data & 0x80) { + hdma_transfer = 1; + + if (!(ioamhram[0x140] & 0x80) || display.isHdmaPeriod(cycleCounter)) { + next_dmatime = cycleCounter; + next_hdmaReschedule = COUNTER_DISABLED; + } else { + next_dmatime = display.nextHdmaTime(cycleCounter); + next_hdmaReschedule = display.nextHdmaTimeInvalid(); + } + } else + next_dmatime = cycleCounter; + + set_event(); + return; + case 0x56: + if (isCgb()) { + ioamhram[0x156] = data | 0x3E; + } + + return; + //Set bg palette index + case 0x68: + if (isCgb()) + ioamhram[0x168] = data | 0x40; + + return; + //Write to bg palette data + case 0x69: + if (isCgb()) { + const unsigned index = ioamhram[0x168] & 0x3F; + + display.cgbBgColorChange(index, data, cycleCounter); + + ioamhram[0x168] = (ioamhram[0x168] & ~0x3F) | ((index + (ioamhram[0x168] >> 7)) & 0x3F); + } + + return; + case 0x6A: + if (isCgb()) + ioamhram[0x16A] = data | 0x40; + + return; + //Write to obj palette data. + case 0x6B: + if (isCgb()) { + const unsigned index = ioamhram[0x16A] & 0x3F; + + display.cgbSpColorChange(index, data, cycleCounter); + + ioamhram[0x16A] = (ioamhram[0x16A] & ~0x3F) | ((index + (ioamhram[0x16A] >> 7)) & 0x3F); + } + + return; + case 0x6C: + if (isCgb()) + ioamhram[0x16C] = data | 0xFE; + + return; + case 0x70: + if (isCgb()) { + wramdata[1] = wramdata[0] + ((data & 0x07) ? (data & 0x07) : 1) * 0x1000; + + if (oamDmaArea1Lower == 0xC0) + setOamDmaSrc(); + else + wmem[0xD] = rmem[0xD] = wramdata[1] - 0xD000; + + ioamhram[0x170] = data | 0xF8; + } + + return; + case 0x72: + case 0x73: + case 0x74: + if (isCgb()) + break; + + return; + case 0x75: + if (isCgb()) + ioamhram[0x175] = data | 0x8F; + + return; + case 0xFF: + ioamhram[0x1FF] = data; + rescheduleIrq(cycleCounter); + return; + default: +// if (P < 0xFF80) + return; + } + + ioamhram[P - 0xFE00] = data; +} + +void Memory::mbc_write(const unsigned P, const unsigned data) { +// printf("mem[0x%X] = 0x%X\n", P, data); + + switch (P >> 12 & 0x7) { + case 0x0: + case 0x1: //Most MBCs write 0x?A to addresses lower than 0x2000 to enable ram. + if (romtype == mbc2 && (P & 0x0100)) break; + + enable_ram = (data & 0x0F) == 0xA; + + if (rtcRom) + rtc.setEnabled(enable_ram); + + setRambank(); + break; + //MBC1 writes ???n nnnn to address area 0x2000-0x3FFF, ???n nnnn makes up the lower digits to determine which rombank to load. + //MBC3 writes ?nnn nnnn to address area 0x2000-0x3FFF, ?nnn nnnn makes up the lower digits to determine which rombank to load. + //MBC5 writes nnnn nnnn to address area 0x2000-0x2FFF, nnnn nnnn makes up the lower digits to determine which rombank to load. + //MBC5 writes bit8 of the number that determines which rombank to load to address 0x3000-0x3FFF. + case 0x2: + switch (romtype) { + case plain: + return; + case mbc5: + rombank = (rombank & 0x100) | data; + rombank = rombank & (rombanks - 1); + setRombank(); + return; + default: + break; //Only supposed to break one level. + } + case 0x3: + switch (romtype) { + case mbc1: + rombank = rambank_mode ? data & 0x1F : ((rombank & 0x60) | (data & 0x1F)); + break; + case mbc2: + if (P & 0x0100) { + rombank = data & 0x0F; + break; + } + + return; + case mbc3: + rombank = data & 0x7F; + break; + case mbc5: + rombank = (data & 0x1) << 8 | (rombank & 0xFF); + break; + default: + return; + } + + rombank = rombank & (rombanks - 1); + setRombank(); + break; + //MBC1 writes ???? ??nn to area 0x4000-0x5FFF either to determine rambank to load, or upper 2 bits of the rombank number to load, depending on rom-mode. + //MBC3 writes ???? ??nn to area 0x4000-0x5FFF to determine rambank to load + //MBC5 writes ???? nnnn to area 0x4000-0x5FFF to determine rambank to load + case 0x4: + case 0x5: + switch (romtype) { + case mbc1: + if (rambank_mode) { + rambank = data & 0x03; + break; + } + + rombank = (data & 0x03) << 5 | (rombank & 0x1F); + rombank = rombank & (rombanks - 1); + setRombank(); + return; + case mbc3: + if (rtcRom) + rtc.swapActive(data); + + rambank = data & 0x03; + break; + case mbc5: + rambank = data & 0x0F; + break; + default: + return; + } + + rambank &= rambanks - 1; + setRambank(); + break; + //MBC1: If ???? ???1 is written to area 0x6000-0x7FFFF rom will be set to rambank mode. + case 0x6: + case 0x7: + switch (romtype) { + case mbc1: + rambank_mode = data & 0x01; + break; + case mbc3: + rtc.latch(data); + break; + default: + break; + } + + break; +// default: break; + } +} + +void Memory::nontrivial_write(const unsigned P, const unsigned data, const unsigned long cycleCounter) { + if (lastOamDmaUpdate != COUNTER_DISABLED) { + updateOamDma(cycleCounter); + + if ((P >> 8) - oamDmaArea1Lower < oamDmaArea1Width || P >> 8 < oamDmaArea2Upper) { + ioamhram[oamDmaPos] = data; + return; + } + } + + if (P < 0xFE00) { + if (P < 0xA000) { + if (P < 0x8000) { + mbc_write(P, data); + } else if (display.vramAccessible(cycleCounter)) { + display.vramChange(cycleCounter); + vrambank[P & 0x1FFF] = data; + } + } else if (P < 0xC000) { + if (wsrambankptr) + wsrambankptr[P] = data; + else + rtc.write(data); + } else + wramdata[P >> 12 & 1][P & 0xFFF] = data; + } else if (((P + 1) & 0xFFFF) < 0xFF81) { + if (P < 0xFF00) { + if (display.oamAccessible(cycleCounter) && oamDmaPos >= 0xA0) { + display.oamChange(cycleCounter); + rescheduleIrq(cycleCounter); + rescheduleHdmaReschedule(); + ioamhram[P - 0xFE00] = data; + } + } else + nontrivial_ff_write(P, data, cycleCounter); + } else + ioamhram[P - 0xFE00] = data; +} + +static const std::string stripExtension(const std::string &str) { + const std::string::size_type lastDot = str.find_last_of('.'); + const std::string::size_type lastSlash = str.find_last_of('/'); + + if (lastDot != std::string::npos && (lastSlash == std::string::npos || lastSlash < lastDot)) + return str.substr(0, lastDot); + + return str; +} + +static const std::string stripDir(const std::string &str) { + const std::string::size_type lastSlash = str.find_last_of('/'); + + if (lastSlash != std::string::npos) + return str.substr(lastSlash + 1); + + return str; +} + +const std::string Memory::saveBasePath() const { + return saveDir.empty() ? defaultSaveBasePath : saveDir + stripDir(defaultSaveBasePath); +} + +void Memory::set_savedir(const char *dir) { + saveDir = dir ? dir : ""; + + if (!saveDir.empty() && saveDir[saveDir.length() - 1] != '/') { + saveDir += '/'; + } +} + +static void enforce8bit(unsigned char *data, unsigned long sz) { + if (static_cast(0x100)) + while (sz--) + *data++ &= 0xFF; +} + +static unsigned pow2ceil(unsigned n) { + --n; + n |= n >> 1; + n |= n >> 2; + n |= n >> 4; + n |= n >> 8; + ++n; + + return n; +} + +bool Memory::loadROM(const bool forceDmg) { + defaultSaveBasePath = ""; + + { + unsigned char *header = (unsigned char*)supergameboy.romdata; + + cgb = header[0x0143] >> 7 & 1; + + if (cgb & forceDmg) { + cgb = false; + defaultSaveBasePath += "_dmg"; + } + + switch (header[0x0147]) { + case 0x00: std::printf("Plain ROM loaded.\n"); + romtype = plain; + break; + case 0x01: std::printf("MBC1 ROM loaded.\n"); + romtype = mbc1; + break; + case 0x02: std::printf("MBC1 ROM+RAM loaded.\n"); + romtype = mbc1; + break; + case 0x03: std::printf("MBC1 ROM+RAM+BATTERY loaded.\n"); + romtype = mbc1; + battery = 1; + break; + case 0x05: std::printf("MBC2 ROM loaded.\n"); + romtype = mbc2; + break; + case 0x06: std::printf("MBC2 ROM+BATTERY loaded.\n"); + romtype = mbc2; + battery = 1; + break; + case 0x08: std::printf("Plain ROM with additional RAM loaded.\n"); + break; + case 0x09: std::printf("Plain ROM with additional RAM and Battery loaded.\n"); + battery = 1; + break; + case 0x0B: /*cout << "MM01 ROM not supported.\n";*/ + return 1; + break; + case 0x0C: /*cout << "MM01 ROM not supported.\n";*/ + return 1; + break; + case 0x0D: /*cout << "MM01 ROM not supported.\n";*/ + return 1; + break; + case 0x0F: std::printf("MBC3 ROM+TIMER+BATTERY loaded.\n"); + romtype = mbc3; + battery = true; + rtcRom = true; + break; + case 0x10: std::printf("MBC3 ROM+TIMER+RAM+BATTERY loaded.\n"); + romtype = mbc3; + battery = true; + rtcRom = true; + break; + case 0x11: std::printf("MBC3 ROM loaded.\n"); + romtype = mbc3; + break; + case 0x12: std::printf("MBC3 ROM+RAM loaded.\n"); + romtype = mbc3; + break; + case 0x13: std::printf("MBC3 ROM+RAM+BATTERY loaded.\n"); + romtype = mbc3; + battery = 1; + break; + case 0x15: /*cout << "MBC4 ROM not supported.\n";*/ + return 1; + break; + case 0x16: /*cout << "MBC4 ROM not supported.\n";*/ + return 1; + break; + case 0x17: /*cout << "MBC4 ROM not supported.\n";*/ + return 1; + break; + case 0x19: std::printf("MBC5 ROM loaded.\n"); + romtype = mbc5; + break; + case 0x1A: std::printf("MBC5 ROM+RAM loaded.\n"); + romtype = mbc5; + break; + case 0x1B: std::printf("MBC5 ROM+RAM+BATTERY loaded.\n"); + romtype = mbc5; + battery = 1; + break; + case 0x1C: std::printf("MBC5+RUMLE ROM not supported.\n"); + romtype = mbc5; + break; + case 0x1D: std::printf("MBC5+RUMLE+RAM ROM not suported.\n"); + romtype = mbc5; + break; + case 0x1E: std::printf("MBC5+RUMLE+RAM+BATTERY ROM not supported.\n"); + romtype = mbc5; + battery = 1; + break; + case 0xFC: /*cout << "Pocket Camera ROM not supported.\n";*/ + return 1; + break; + case 0xFD: /*cout << "Bandai TAMA5 ROM not supported.\n";*/ + return 1; + break; + case 0xFE: /*cout << "HuC3 ROM not supported.\n";*/ + return 1; + break; + case 0xFF: /*cout << "HuC1 ROM not supported.\n";*/ + return 1; + break; + default: /*cout << "Wrong data-format, corrupt or unsupported ROM loaded.\n";*/ + return 1; + } + + /*switch (header[0x0148]) { + case 0x00: + rombanks = 2; + break; + case 0x01: + rombanks = 4; + break; + case 0x02: + rombanks = 8; + break; + case 0x03: + rombanks = 16; + break; + case 0x04: + rombanks = 32; + break; + case 0x05: + rombanks = 64; + break; + case 0x06: + rombanks = 128; + break; + case 0x07: + rombanks = 256; + break; + case 0x08: + rombanks = 512; + break; + case 0x52: + rombanks = 72; + break; + case 0x53: + rombanks = 80; + break; + case 0x54: + rombanks = 96; + break; + default: + return 1; + } + + printf("rombanks: %u\n", rombanks);*/ + + switch (header[0x0149]) { + case 0x00: /*cout << "No RAM\n";*/ rambanks = romtype == mbc2; break; + case 0x01: /*cout << "2kB RAM\n";*/ /*rambankrom=1; break;*/ + case 0x02: /*cout << "8kB RAM\n";*/ + rambanks = 1; + break; + case 0x03: /*cout << "32kB RAM\n";*/ + rambanks = 4; + break; + case 0x04: /*cout << "128kB RAM\n";*/ + rambanks = 16; + break; + case 0x05: /*cout << "undocumented kB RAM\n";*/ + rambanks = 16; + break; + default: /*cout << "Wrong data-format, corrupt or unsupported ROM loaded.\n";*/ + rambanks = 16; + break; + } + } + + std::printf("rambanks: %u\n", rambanks); + + rombanks = pow2ceil(supergameboy.romsize / 0x4000); + std::printf("rombanks: %u\n", supergameboy.romsize / 0x4000); + + delete []memchunk; + memchunk = new unsigned char[0x4000 + rombanks * 0x4000ul + rambanks * 0x2000ul + (isCgb() ? 0x8000 : 0x2000) + 0x4000]; + + romdata[0] = memchunk + 0x4000; + rambankdata = romdata[0] + rombanks * 0x4000ul; + wramdata[0] = rambankdata + rambanks * 0x2000; + rdisabled_ram = wramdata[0] + (isCgb() ? 0x8000 : 0x2000); + wdisabled_ram = rdisabled_ram + 0x2000; + + wramdata[1] = wramdata[0] + 0x1000; + std::memset(rdisabled_ram, 0xFF, 0x2000); + + memcpy((char*)romdata[0], supergameboy.romdata, (supergameboy.romsize / 0x4000) * 0x4000ul); + // In case rombanks isn't a power of 2, allocate a disabled area for invalid rombank addresses. This is only based on speculation. + std::memset(romdata[0] + (supergameboy.romsize / 0x4000) * 0x4000ul, 0xFF, (rombanks - supergameboy.romsize / 0x4000) * 0x4000ul); + enforce8bit(romdata[0], rombanks * 0x4000ul); + + sound.init(isCgb()); + display.reset(ioamhram, isCgb()); + + return 0; +} + +void Memory::loadSavedata() { + if (battery) { + if (supergameboy.ramdata) { + memcpy((char*)rambankdata, supergameboy.ramdata, std::min(supergameboy.ramsize, (unsigned int)(rambanks * 0x2000ul))); + enforce8bit(rambankdata, rambanks * 0x2000ul); + } + } + + if (rtcRom) { + if (supergameboy.rtcdata && supergameboy.rtcsize >= 4) { + unsigned long basetime = 0; + + basetime = basetime << 8 | (supergameboy.rtcdata[0]); + basetime = basetime << 8 | (supergameboy.rtcdata[1]); + basetime = basetime << 8 | (supergameboy.rtcdata[2]); + basetime = basetime << 8 | (supergameboy.rtcdata[3]); + + rtc.setBaseTime(basetime); + } + } +} + +void Memory::saveSavedata() { + if (battery) { + if (supergameboy.ramdata) { + memcpy(supergameboy.ramdata, (char*)rambankdata, std::min(supergameboy.ramsize, (unsigned int)(rambanks * 0x2000ul))); + } + } + + if (rtcRom) { + if (supergameboy.rtcdata && supergameboy.rtcsize >= 4) { + const unsigned long basetime = rtc.getBaseTime(); + + supergameboy.rtcdata[0] = basetime >> 24; + supergameboy.rtcdata[1] = basetime >> 16; + supergameboy.rtcdata[2] = basetime >> 8; + supergameboy.rtcdata[3] = basetime >> 0; + } + } +} + +unsigned Memory::fillSoundBuffer(const unsigned long cycleCounter) { + sound.generate_samples(cycleCounter, isDoubleSpeed()); + return sound.fillBuffer(); +} + +void Memory::setVideoBlitter(Gambatte::VideoBlitter *const vb) { + display.setVideoBlitter(vb); +} + +void Memory::videoBufferChange() { + display.videoBufferChange(); +} + +void Memory::setVideoFilter(const unsigned int n) { + display.setVideoFilter(n); +} + +void Memory::setDmgPaletteColor(unsigned palNum, unsigned colorNum, unsigned long rgb32) { + display.setDmgPaletteColor(palNum, colorNum, rgb32); +} + +Memory::~Memory() { + saveSavedata(); + + delete []memchunk; +} diff --git a/supergameboy/libgambatte/src/memory.h b/supergameboy/libgambatte/src/memory.h new file mode 100644 index 00000000..eb9f3197 --- /dev/null +++ b/supergameboy/libgambatte/src/memory.h @@ -0,0 +1,238 @@ +/*************************************************************************** + * Copyright (C) 2007 by Sindre Aam�s * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#ifndef MEMORY_H +#define MEMORY_H + +class SaveState; + +#include "int.h" +#include "video.h" +#include "sound.h" + +#include "interrupter.h" +#include "rtc.h" +#include + +namespace Gambatte { +class InputStateGetter; +class FilterInfo; +} + +class Memory { +public: + enum { COUNTER_DISABLED = 0xFFFFFFFFu }; + +private: + enum cartridgetype { plain, mbc1, mbc2, mbc3, mbc5 }; + enum events { HDMA_RESCHEDULE, DMA, INTERRUPTS, BLIT, UNHALT, OAM, END }; + enum irqEvents { /*MODE0, MODE1, MODE2, LYC,*/ TIMA, /*M0RESC,*/ SERIAL }; + + unsigned char ioamhram[0x200]; + unsigned char vram[0x2000 * 2]; + unsigned char *rmem[0x10]; + unsigned char *wmem[0x10]; + + unsigned char *memchunk; + unsigned char *romdata[2]; + unsigned char *wramdata[2]; + unsigned char *rambankdata; + unsigned char *rdisabled_ram; + unsigned char *wdisabled_ram; + unsigned char *oamDmaSrc; + unsigned char *vrambank; + unsigned char *rsrambankptr; + unsigned char *wsrambankptr; + + Gambatte::InputStateGetter *getInput; + + unsigned long div_lastUpdate; + unsigned long tima_lastUpdate; + unsigned long next_timatime; + unsigned long next_blittime; + unsigned long nextIntTime; + unsigned long minIntTime; + unsigned long next_dmatime; + unsigned long next_hdmaReschedule; + unsigned long next_unhalttime; + unsigned long next_endtime; + unsigned long next_irqEventTime; + unsigned long tmatime; + unsigned long next_serialtime; + unsigned long next_eventtime; + unsigned long lastOamDmaUpdate; + unsigned long nextOamEventTime; + + LCD display; + PSG sound; + Interrupter interrupter; + Rtc rtc; + + events next_event; + irqEvents next_irqEvent; + cartridgetype romtype; + + std::string defaultSaveBasePath; + std::string saveDir; + + unsigned short rombanks; + unsigned short rombank; + unsigned short dmaSource; + unsigned short dmaDestination; + + unsigned char rambank; + unsigned char rambanks; + unsigned char oamDmaArea1Lower; + unsigned char oamDmaArea1Width; + unsigned char oamDmaArea2Upper; + unsigned char oamDmaPos; + + bool cgb; + bool doubleSpeed; + bool IME; + bool enable_ram; + bool rambank_mode; + bool battery, rtcRom; + bool hdma_transfer; + bool active; + + void updateInput(); + + void setRombank(); + void setRambank(); + void setBanks(); + void oamDmaInitSetup(); + void setOamDmaArea(); + void updateOamDma(unsigned long cycleCounter); + void startOamDma(unsigned long cycleCounter); + void endOamDma(unsigned long cycleCounter); + void setOamDmaSrc(); + + unsigned nontrivial_ff_read(unsigned P, unsigned long cycleCounter); + unsigned nontrivial_read(unsigned P, unsigned long cycleCounter); + void nontrivial_ff_write(unsigned P, unsigned data, unsigned long cycleCounter); + void mbc_write(unsigned P, unsigned data); + void nontrivial_write(unsigned P, unsigned data, unsigned long cycleCounter); + + void set_event(); + void set_irqEvent(); + void update_irqEvents(unsigned long cc); + void update_tima(unsigned long cycleCounter); + + void rescheduleIrq(unsigned long cycleCounter); + void rescheduleHdmaReschedule(); + + bool isDoubleSpeed() const { return doubleSpeed; } + +public: + Memory(const Interrupter &interrupter); + ~Memory(); + + void updateVideo(unsigned cycleCounter) { display.update(cycleCounter); } + unsigned lyCounter(unsigned cycleCounter) { return display.getLyReg(cycleCounter); } + + void setStatePtrs(SaveState &state); + unsigned long saveState(SaveState &state, unsigned long cc); + void loadState(const SaveState &state, unsigned long oldCc); + void loadSavedata(); + void saveSavedata(); + const std::string saveBasePath() const; + + void setOsdElement(std::auto_ptr osdElement) { + display.setOsdElement(osdElement); + } + + void speedChange(unsigned long cycleCounter); + bool isCgb() const { return cgb; } + bool getIME() const { return IME; } + unsigned long getNextEventTime() const { return next_eventtime; } + + bool isActive() const { return active; } + + void ei(unsigned long cycleCounter); + + void di() { + IME = 0; + nextIntTime = COUNTER_DISABLED; + + if (next_event == INTERRUPTS) + set_event(); + +// next_eitime=0; +// if(next_event==EI) set_event(); + } + + unsigned ff_read(const unsigned P, const unsigned long cycleCounter) { + return P < 0xFF80 ? nontrivial_ff_read(P, cycleCounter) : ioamhram[P - 0xFE00]; + } + + unsigned read(const unsigned P, const unsigned long cycleCounter) { + return rmem[P >> 12] ? rmem[P >> 12][P] : nontrivial_read(P, cycleCounter); + } + + void write(const unsigned P, const unsigned data, const unsigned long cycleCounter) { + if (wmem[P >> 12]) + wmem[P >> 12][P] = data; + else + nontrivial_write(P, data, cycleCounter); + } + + void ff_write(const unsigned P, const unsigned data, const unsigned long cycleCounter) { + if (((P + 1) & 0xFF) < 0x81) + nontrivial_ff_write(P, data, cycleCounter); + else + ioamhram[P - 0xFE00] = data; + } + + unsigned long event(unsigned long cycleCounter); + unsigned long resetCounters(unsigned long cycleCounter); + + bool loadROM(bool forceDmg); + void set_savedir(const char *dir); + + void setInputStateGetter(Gambatte::InputStateGetter *getInput) { + this->getInput = getInput; + } + + void schedule_unhalt(); + void incEndtime(unsigned long inc); + void setEndtime(unsigned long cc, unsigned long inc); + + void setSoundBuffer(Gambatte::uint_least32_t *const buf) { sound.setBuffer(buf); } + unsigned fillSoundBuffer(unsigned long cc); + void setVideoBlitter(Gambatte::VideoBlitter * vb); + void setVideoFilter(unsigned int n); + + void videoBufferChange(); + + unsigned videoWidth() const { + return display.videoWidth(); + } + + unsigned videoHeight() const { + return display.videoHeight(); + } + + std::vector filterInfo() const { + return display.filterInfo(); + } + + void setDmgPaletteColor(unsigned palNum, unsigned colorNum, unsigned long rgb32); +}; + +#endif diff --git a/supergameboy/libgambatte/src/osd_element.h b/supergameboy/libgambatte/src/osd_element.h new file mode 100644 index 00000000..2517d34f --- /dev/null +++ b/supergameboy/libgambatte/src/osd_element.h @@ -0,0 +1,65 @@ +/*************************************************************************** + * Copyright (C) 2008 by Sindre Aam�s * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#ifndef OSD_ELEMENT_H +#define OSD_ELEMENT_H + +#include "int.h" + +class OsdElement { +public: + enum Opacity { SEVEN_EIGHTHS, THREE_FOURTHS }; + +private: + Opacity opacity_; + unsigned x_; + unsigned y_; + unsigned w_; + unsigned h_; + +protected: + OsdElement(unsigned x = 0, unsigned y = 0, unsigned w = 0, unsigned h = 0, Opacity opacity = SEVEN_EIGHTHS) { + setPos(x, y); + setSize(w, h); + setOpacity(opacity); + } + + void setPos(unsigned x, unsigned y) { + x_ = x; + y_ = y; + } + + void setSize(unsigned w, unsigned h) { + w_ = w; + h_ = h; + } + + void setOpacity(Opacity opacity) { opacity_ = opacity; } + +public: + virtual ~OsdElement() {} + unsigned x() const { return x_; } + unsigned y() const { return y_; } + unsigned w() const { return w_; } + unsigned h() const { return h_; } + Opacity opacity() const { return opacity_; } + + virtual const Gambatte::uint_least32_t* update() = 0; +}; + +#endif diff --git a/supergameboy/libgambatte/src/rtc.cpp b/supergameboy/libgambatte/src/rtc.cpp new file mode 100644 index 00000000..75164919 --- /dev/null +++ b/supergameboy/libgambatte/src/rtc.cpp @@ -0,0 +1,157 @@ +/*************************************************************************** + * Copyright (C) 2007 by Sindre Aamås * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#include "rtc.h" +#include "savestate.h" + +using namespace std; + +Rtc::Rtc() : +activeData(NULL), +activeSet(NULL), +baseTime(0), +haltTime(0), +index(5), +dataDh(0), +dataDl(0), +dataH(0), +dataM(0), +dataS(0), +enabled(false), +lastLatchData(false) { +} + +void Rtc::doLatch() { + time_t tmp = ((dataDh & 0x40) ? haltTime : time(NULL)) - baseTime; + + while (tmp > 0x1FF * 86400) { + baseTime += 0x1FF * 86400; + tmp -= 0x1FF * 86400; + dataDh |= 0x80; + } + + dataDl = (tmp / 86400) & 0xFF; + dataDh &= 0xFE; + dataDh |= ((tmp / 86400) & 0x100) >> 8; + tmp %= 86400; + + dataH = tmp / 3600; + tmp %= 3600; + + dataM = tmp / 60; + tmp %= 60; + + dataS = tmp; +} + +void Rtc::doSwapActive() { + if (!enabled || index > 4) { + activeData = NULL; + activeSet = NULL; + } else switch (index) { + case 0x00: + activeData = &dataS; + activeSet = &Rtc::setS; + break; + case 0x01: + activeData = &dataM; + activeSet = &Rtc::setM; + break; + case 0x02: + activeData = &dataH; + activeSet = &Rtc::setH; + break; + case 0x03: + activeData = &dataDl; + activeSet = &Rtc::setDl; + break; + case 0x04: + activeData = &dataDh; + activeSet = &Rtc::setDh; + break; + } +} + +void Rtc::saveState(SaveState &state) const { + state.rtc.baseTime = baseTime; + state.rtc.haltTime = haltTime; + state.rtc.index = index; + state.rtc.dataDh = dataDh; + state.rtc.dataDl = dataDl; + state.rtc.dataH = dataH; + state.rtc.dataM = dataM; + state.rtc.dataS = dataS; + state.rtc.lastLatchData = lastLatchData; +} + +void Rtc::loadState(const SaveState &state, const bool enabled) { + this->enabled = enabled; + + baseTime = state.rtc.baseTime; + haltTime = state.rtc.haltTime; + index = state.rtc.index; + dataDh = state.rtc.dataDh; + dataDl = state.rtc.dataDl; + dataH = state.rtc.dataH; + dataM = state.rtc.dataM; + dataS = state.rtc.dataS; + lastLatchData = state.rtc.lastLatchData; + + doSwapActive(); +} + +void Rtc::setDh(const unsigned new_dh) { + const time_t unixtime = (dataDh & 0x40) ? haltTime : time(NULL); + const time_t old_highdays = ((unixtime - baseTime) / 86400) & 0x100; + baseTime += old_highdays * 86400; + baseTime -= ((new_dh & 0x1) << 8) * 86400; + + if ((dataDh ^ new_dh) & 0x40) { + if (new_dh & 0x40) + haltTime = time(NULL); + else + baseTime += time(NULL) - haltTime; + } +} + +void Rtc::setDl(const unsigned new_lowdays) { + const time_t unixtime = (dataDh & 0x40) ? haltTime : time(NULL); + const time_t old_lowdays = ((unixtime - baseTime) / 86400) & 0xFF; + baseTime += old_lowdays * 86400; + baseTime -= new_lowdays * 86400; +} + +void Rtc::setH(const unsigned new_hours) { + const time_t unixtime = (dataDh & 0x40) ? haltTime : time(NULL); + const time_t old_hours = ((unixtime - baseTime) / 3600) % 24; + baseTime += old_hours * 3600; + baseTime -= new_hours * 3600; +} + +void Rtc::setM(const unsigned new_minutes) { + const time_t unixtime = (dataDh & 0x40) ? haltTime : time(NULL); + const time_t old_minutes = ((unixtime - baseTime) / 60) % 60; + baseTime += old_minutes * 60; + baseTime -= new_minutes * 60; +} + +void Rtc::setS(const unsigned new_seconds) { + const time_t unixtime = (dataDh & 0x40) ? haltTime : time(NULL); + baseTime += (unixtime - baseTime) % 60; + baseTime -= new_seconds; +} diff --git a/supergameboy/libgambatte/src/rtc.h b/supergameboy/libgambatte/src/rtc.h new file mode 100644 index 00000000..40905c18 --- /dev/null +++ b/supergameboy/libgambatte/src/rtc.h @@ -0,0 +1,97 @@ +/*************************************************************************** + * Copyright (C) 2007 by Sindre Aamås * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#ifndef RTC_H +#define RTC_H + +class SaveState; + +#include + +class Rtc { +private: + unsigned char *activeData; + void (Rtc::*activeSet)(unsigned); + std::time_t baseTime; + std::time_t haltTime; + unsigned char index; + unsigned char dataDh; + unsigned char dataDl; + unsigned char dataH; + unsigned char dataM; + unsigned char dataS; + bool enabled; + bool lastLatchData; + + void doLatch(); + void doSwapActive(); + void setDh(unsigned new_dh); + void setDl(unsigned new_lowdays); + void setH(unsigned new_hours); + void setM(unsigned new_minutes); + void setS(unsigned new_seconds); + +public: + Rtc(); + + const unsigned char* getActive() const { + return activeData; + } + + std::time_t getBaseTime() const { + return baseTime; + } + + void setBaseTime(const std::time_t baseTime) { + this->baseTime = baseTime; +// doLatch(); + } + + void latch(const unsigned data) { + if (!lastLatchData && data == 1) + doLatch(); + + lastLatchData = data; + } + + void saveState(SaveState &state) const; + void loadState(const SaveState &state, bool enabled); + + void setEnabled(const bool enabled) { + this->enabled = enabled; + + doSwapActive(); + } + + void swapActive(unsigned index) { + index &= 0xF; + index -= 8; + + this->index = index; + + doSwapActive(); + } + + void write(const unsigned data) { +// if (activeSet) + (this->*activeSet)(data); + *activeData = data; + } +}; + +#endif diff --git a/supergameboy/libgambatte/src/savestate.h b/supergameboy/libgambatte/src/savestate.h new file mode 100644 index 00000000..c4b245fd --- /dev/null +++ b/supergameboy/libgambatte/src/savestate.h @@ -0,0 +1,184 @@ +/*************************************************************************** + * Copyright (C) 2008 by Sindre Aamås * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#ifndef SAVESTATE_H +#define SAVESTATE_H + +#include "int.h" + +struct SaveState { + template + class Ptr { + T *ptr; + unsigned long sz; + + public: + Ptr() : ptr(0), sz(0) {} + const T* get() const { return ptr; } + unsigned long getSz() const { return sz; } + void set(T *ptr, const unsigned long sz) { this->ptr = ptr; this->sz = sz; } + + friend class SaverList; + friend void setInitState(SaveState &, bool); + }; + + struct CPU { + unsigned long cycleCounter; + unsigned short PC; + unsigned short SP; + unsigned char A; + unsigned char B; + unsigned char C; + unsigned char D; + unsigned char E; + unsigned char F; + unsigned char H; + unsigned char L; + bool skip; + bool halted; + } cpu; + + struct Mem { + Ptr vram; + Ptr sram; + Ptr wram; + Ptr ioamhram; + unsigned long div_lastUpdate; + unsigned long tima_lastUpdate; + unsigned long tmatime; + unsigned long next_serialtime; + unsigned long lastOamDmaUpdate; + unsigned long minIntTime; + unsigned short rombank; + unsigned short dmaSource; + unsigned short dmaDestination; + unsigned char rambank; + unsigned char oamDmaPos; + bool IME; + bool enable_ram; + bool rambank_mode; + bool hdma_transfer; + } mem; + + struct PPU { + Ptr drawBuffer; + Ptr bgpData; + Ptr objpData; + //SpriteMapper::OamReader + Ptr oamReaderBuf; + Ptr oamReaderSzbuf; + + unsigned long videoCycles; + unsigned long enableDisplayM0Time; + unsigned char winYPos; + unsigned char drawStartCycle; + unsigned char scReadOffset; + unsigned char lcdc; + //ScReader + unsigned char scx[2]; + unsigned char scy[2]; + //ScxReader + unsigned char scxAnd7; + //WeMasterChecker + bool weMaster; + //WxReader + unsigned char wx; + //Wy + unsigned char wy; + bool lycIrqSkip; + } ppu; + + struct SPU { + struct Duty { + unsigned long nextPosUpdate; + unsigned char nr3; + unsigned char pos; + }; + + struct Env { + unsigned long counter; + unsigned char volume; + }; + + struct LCounter { + unsigned long counter; + unsigned short lengthCounter; + }; + + struct { + struct { + unsigned long counter; + unsigned short shadow; + unsigned char nr0; + bool negging; + } sweep; + Duty duty; + Env env; + LCounter lcounter; + unsigned char nr4; + bool master; + } ch1; + + struct { + Duty duty; + Env env; + LCounter lcounter; + unsigned char nr4; + bool master; + } ch2; + + struct { + Ptr waveRam; + LCounter lcounter; + unsigned long waveCounter; + unsigned long lastReadTime; + unsigned char nr3; + unsigned char nr4; + unsigned char wavePos; + unsigned char sampleBuf; + bool master; + } ch3; + + struct { + struct { + unsigned long counter; + unsigned short reg; + } lfsr; + Env env; + LCounter lcounter; + unsigned char nr4; + bool master; + } ch4; + + unsigned long cycleCounter; + } spu; + + struct RTC { + unsigned long baseTime; + unsigned long haltTime; + unsigned char index; + unsigned char dataDh; + unsigned char dataDl; + unsigned char dataH; + unsigned char dataM; + unsigned char dataS; + bool lastLatchData; + } rtc; +}; + +#endif diff --git a/supergameboy/libgambatte/src/sound.cpp b/supergameboy/libgambatte/src/sound.cpp new file mode 100644 index 00000000..3ff8063f --- /dev/null +++ b/supergameboy/libgambatte/src/sound.cpp @@ -0,0 +1,155 @@ +/*************************************************************************** + * Copyright (C) 2007 by Sindre Aam�s * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#include "sound.h" + +#include "savestate.h" +#include +#include + +/* + Frame Sequencer + + Step Length Ctr Vol Env Sweep + - - - - - - - - - - - - - - - - - - - - + 0 Clock - Clock +S 1 - Clock - + 2 Clock - - + 3 - - - + 4 Clock - Clock + 5 - - - + 6 Clock - - + 7 - - - + - - - - - - - - - - - - - - - - - - - - + Rate 256 Hz 64 Hz 128 Hz + +S) start step on sound power on. +*/ + +// static const unsigned bufferSize = 35112 + 16 + 2048; //FIXME: DMA can prevent process from returning for up to 4096 cycles. + +PSG::PSG() : +buffer(NULL), +lastUpdate(0), +soVol(0), +rsum(0x8000), // initialize to 0x8000 to prevent borrows from high word, xor away later +bufferPos(0), +enabled(false) +{} + +void PSG::init(const bool cgb) { + ch1.init(cgb); + ch2.init(cgb); + ch3.init(cgb); + ch4.init(cgb); +} + +void PSG::reset() { + ch1.reset(); + ch2.reset(); + ch3.reset(); + ch4.reset(); +} + +void PSG::setStatePtrs(SaveState &state) { + ch3.setStatePtrs(state); +} + +void PSG::saveState(SaveState &state) { + ch1.saveState(state); + ch2.saveState(state); + ch3.saveState(state); + ch4.saveState(state); +} + +void PSG::loadState(const SaveState &state) { + ch1.loadState(state); + ch2.loadState(state); + ch3.loadState(state); + ch4.loadState(state); + + lastUpdate = state.cpu.cycleCounter; + set_so_volume(state.mem.ioamhram.get()[0x124]); + map_so(state.mem.ioamhram.get()[0x125]); + enabled = state.mem.ioamhram.get()[0x126] >> 7 & 1; +} + +void PSG::accumulate_channels(const unsigned long cycles) { + Gambatte::uint_least32_t *const buf = buffer + bufferPos; + + std::memset(buf, 0, cycles * sizeof(Gambatte::uint_least32_t)); + ch1.update(buf, soVol, cycles); + ch2.update(buf, soVol, cycles); + ch3.update(buf, soVol, cycles); + ch4.update(buf, soVol, cycles); +} + +void PSG::generate_samples(const unsigned long cycleCounter, const unsigned doubleSpeed) { + const unsigned long cycles = (cycleCounter - lastUpdate) >> (1 + doubleSpeed); + lastUpdate += cycles << (1 + doubleSpeed); + + if (cycles) + accumulate_channels(cycles); + + bufferPos += cycles; +} + +void PSG::resetCounter(const unsigned long newCc, const unsigned long oldCc, const unsigned doubleSpeed) { + generate_samples(oldCc, doubleSpeed); + lastUpdate = newCc - (oldCc - lastUpdate); +} + +unsigned PSG::fillBuffer() { + Gambatte::uint_least32_t sum = rsum; + Gambatte::uint_least32_t *b = buffer; + unsigned n = bufferPos; + + while (n--) { + sum += *b; + *b++ = sum ^ 0x8000; // xor away the initial rsum value of 0x8000 (which prevents borrows from the high word) from the low word + } + + rsum = sum; + + return bufferPos; +} + +#ifdef WORDS_BIGENDIAN +static const unsigned long so1Mul = 0x00000001; +static const unsigned long so2Mul = 0x00010000; +#else +static const unsigned long so1Mul = 0x00010000; +static const unsigned long so2Mul = 0x00000001; +#endif + +void PSG::set_so_volume(const unsigned nr50) { + soVol = (((nr50 & 0x7) + 1) * so1Mul + ((nr50 >> 4 & 0x7) + 1) * so2Mul) * 64; +} + +void PSG::map_so(const unsigned nr51) { + const unsigned long tmp = nr51 * so1Mul + (nr51 >> 4) * so2Mul; + + ch1.setSo((tmp & 0x00010001) * 0xFFFF); + ch2.setSo((tmp >> 1 & 0x00010001) * 0xFFFF); + ch3.setSo((tmp >> 2 & 0x00010001) * 0xFFFF); + ch4.setSo((tmp >> 3 & 0x00010001) * 0xFFFF); +} + +unsigned PSG::getStatus() const { + return ch1.isActive() | (ch2.isActive() << 1) | (ch3.isActive() << 2) | (ch4.isActive() << 3); +} diff --git a/supergameboy/libgambatte/src/sound.h b/supergameboy/libgambatte/src/sound.h new file mode 100644 index 00000000..06916846 --- /dev/null +++ b/supergameboy/libgambatte/src/sound.h @@ -0,0 +1,95 @@ +/*************************************************************************** + * Copyright (C) 2007 by Sindre Aam�s * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#ifndef SOUND_H +#define SOUND_H + +class SaveState; + +#include "int.h" + +#include "sound/channel1.h" +#include "sound/channel2.h" +#include "sound/channel3.h" +#include "sound/channel4.h" + +class PSG { + Channel1 ch1; + Channel2 ch2; + Channel3 ch3; + Channel4 ch4; + + Gambatte::uint_least32_t *buffer; + + unsigned long lastUpdate; + unsigned long soVol; + + Gambatte::uint_least32_t rsum; + + unsigned bufferPos; + + bool enabled; + + void accumulate_channels(unsigned long cycles); + +public: + PSG(); + void init(bool cgb); + void reset(); + void setStatePtrs(SaveState &state); + void saveState(SaveState &state); + void loadState(const SaveState &state); + + void generate_samples(unsigned long cycleCounter, unsigned doubleSpeed); + void resetCounter(unsigned long newCc, unsigned long oldCc, unsigned doubleSpeed); + unsigned fillBuffer(); + void setBuffer(Gambatte::uint_least32_t *const buf) { buffer = buf; bufferPos = 0; } + + bool isEnabled() const { return enabled; } + void setEnabled(bool value) { enabled = value; } + + void set_nr10(unsigned data) { ch1.setNr0(data); } + void set_nr11(unsigned data) { ch1.setNr1(data); } + void set_nr12(unsigned data) { ch1.setNr2(data); } + void set_nr13(unsigned data) { ch1.setNr3(data); } + void set_nr14(unsigned data) { ch1.setNr4(data); } + + void set_nr21(unsigned data) { ch2.setNr1(data); } + void set_nr22(unsigned data) { ch2.setNr2(data); } + void set_nr23(unsigned data) { ch2.setNr3(data); } + void set_nr24(unsigned data) { ch2.setNr4(data); } + + void set_nr30(unsigned data) { ch3.setNr0(data); } + void set_nr31(unsigned data) { ch3.setNr1(data); } + void set_nr32(unsigned data) { ch3.setNr2(data); } + void set_nr33(unsigned data) { ch3.setNr3(data); } + void set_nr34(unsigned data) { ch3.setNr4(data); } + unsigned waveRamRead(unsigned index) const { return ch3.waveRamRead(index); } + void waveRamWrite(unsigned index, unsigned data) { ch3.waveRamWrite(index, data); } + + void set_nr41(unsigned data) { ch4.setNr1(data); } + void set_nr42(unsigned data) { ch4.setNr2(data); } + void set_nr43(unsigned data) { ch4.setNr3(data); } + void set_nr44(unsigned data) { ch4.setNr4(data); } + + void set_so_volume(unsigned nr50); + void map_so(unsigned nr51); + unsigned getStatus() const; +}; + +#endif diff --git a/supergameboy/libgambatte/src/sound/channel1.cpp b/supergameboy/libgambatte/src/sound/channel1.cpp new file mode 100644 index 00000000..5e112eb2 --- /dev/null +++ b/supergameboy/libgambatte/src/sound/channel1.cpp @@ -0,0 +1,257 @@ +/*************************************************************************** + * Copyright (C) 2007 by Sindre Aam�s * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#include "channel1.h" +#include "../savestate.h" +#include + +Channel1::SweepUnit::SweepUnit(MasterDisabler &disabler, DutyUnit &dutyUnit) : + disableMaster(disabler), + dutyUnit(dutyUnit), + shadow(0), + nr0(0), + negging(false) +{} + +unsigned Channel1::SweepUnit::calcFreq() { + unsigned freq = shadow >> (nr0 & 0x07); + + if (nr0 & 0x08) { + freq = shadow - freq; + negging = true; + } else + freq = shadow + freq; + + if (freq & 2048) + disableMaster(); + + return freq; +} + +void Channel1::SweepUnit::event() { + const unsigned long period = nr0 >> 4 & 0x07; + + if (period) { + const unsigned freq = calcFreq(); + + if (!(freq & 2048) && (nr0 & 0x07)) { + shadow = freq; + dutyUnit.setFreq(freq, counter); + calcFreq(); + } + + counter += period << 14; + } else + counter += 8ul << 14; +} + +void Channel1::SweepUnit::nr0Change(const unsigned newNr0) { + if (negging && !(newNr0 & 0x08)) + disableMaster(); + + nr0 = newNr0; +} + +void Channel1::SweepUnit::nr4Init(const unsigned long cc) { + negging = false; + shadow = dutyUnit.getFreq(); + + const unsigned period = nr0 >> 4 & 0x07; + const unsigned shift = nr0 & 0x07; + + if (period | shift) + counter = ((cc >> 14) + (period ? period : 8)) << 14; + else + counter = COUNTER_DISABLED; + + if (shift) + calcFreq(); +} + +void Channel1::SweepUnit::reset() { + counter = COUNTER_DISABLED; +} + +void Channel1::SweepUnit::saveState(SaveState &state) const { + state.spu.ch1.sweep.counter = counter; + state.spu.ch1.sweep.shadow = shadow; + state.spu.ch1.sweep.nr0 = nr0; + state.spu.ch1.sweep.negging = negging; +} + +void Channel1::SweepUnit::loadState(const SaveState &state) { + counter = std::max(state.spu.ch1.sweep.counter, state.spu.cycleCounter); + shadow = state.spu.ch1.sweep.shadow; + nr0 = state.spu.ch1.sweep.nr0; + negging = state.spu.ch1.sweep.negging; +} + +Channel1::Channel1() : + staticOutputTest(*this, dutyUnit), + disableMaster(master, dutyUnit), + lengthCounter(disableMaster, 0x3F), + envelopeUnit(staticOutputTest), + sweepUnit(disableMaster, dutyUnit), + cycleCounter(0), + soMask(0), + prevOut(0), + nr4(0), + master(false) +{ + setEvent(); +} + +void Channel1::setEvent() { +// nextEventUnit = &dutyUnit; +// if (sweepUnit.getCounter() < nextEventUnit->getCounter()) + nextEventUnit = &sweepUnit; + if (envelopeUnit.getCounter() < nextEventUnit->getCounter()) + nextEventUnit = &envelopeUnit; + if (lengthCounter.getCounter() < nextEventUnit->getCounter()) + nextEventUnit = &lengthCounter; +} + +void Channel1::setNr0(const unsigned data) { + sweepUnit.nr0Change(data); + setEvent(); +} + +void Channel1::setNr1(const unsigned data) { + lengthCounter.nr1Change(data, nr4, cycleCounter); + dutyUnit.nr1Change(data, cycleCounter); + + setEvent(); +} + +void Channel1::setNr2(const unsigned data) { + if (envelopeUnit.nr2Change(data)) + disableMaster(); + else + staticOutputTest(cycleCounter); + + setEvent(); +} + +void Channel1::setNr3(const unsigned data) { + dutyUnit.nr3Change(data, cycleCounter); + setEvent(); +} + +void Channel1::setNr4(const unsigned data) { + lengthCounter.nr4Change(nr4, data, cycleCounter); + + nr4 = data; + + dutyUnit.nr4Change(data, cycleCounter); + + if (data & 0x80) { //init-bit + nr4 &= 0x7F; + master = !envelopeUnit.nr4Init(cycleCounter); + sweepUnit.nr4Init(cycleCounter); + staticOutputTest(cycleCounter); + } + + setEvent(); +} + +void Channel1::setSo(const unsigned long soMask) { + this->soMask = soMask; + staticOutputTest(cycleCounter); + setEvent(); +} + +void Channel1::reset() { + cycleCounter = 0x1000 | (cycleCounter & 0xFFF); // cycleCounter >> 12 & 7 represents the frame sequencer position. + +// lengthCounter.reset(); + dutyUnit.reset(); + envelopeUnit.reset(); + sweepUnit.reset(); + + setEvent(); +} + +void Channel1::init(const bool cgb) { + lengthCounter.init(cgb); +} + +void Channel1::saveState(SaveState &state) { + sweepUnit.saveState(state); + dutyUnit.saveState(state.spu.ch1.duty, cycleCounter); + envelopeUnit.saveState(state.spu.ch1.env); + lengthCounter.saveState(state.spu.ch1.lcounter); + + state.spu.cycleCounter = cycleCounter; + state.spu.ch1.nr4 = nr4; + state.spu.ch1.master = master; +} + +void Channel1::loadState(const SaveState &state) { + sweepUnit.loadState(state); + dutyUnit.loadState(state.spu.ch1.duty, state.mem.ioamhram.get()[0x111], state.spu.ch1.nr4, state.spu.cycleCounter); + envelopeUnit.loadState(state.spu.ch1.env, state.mem.ioamhram.get()[0x112], state.spu.cycleCounter); + lengthCounter.loadState(state.spu.ch1.lcounter, state.spu.cycleCounter); + + cycleCounter = state.spu.cycleCounter; + nr4 = state.spu.ch1.nr4; + master = state.spu.ch1.master; +} + +void Channel1::update(Gambatte::uint_least32_t *buf, const unsigned long soBaseVol, unsigned long cycles) { + const unsigned long outBase = envelopeUnit.dacIsOn() ? soBaseVol & soMask : 0; + const unsigned long outLow = outBase * (0 - 15ul); + const unsigned long endCycles = cycleCounter + cycles; + + for (;;) { + const unsigned long outHigh = master ? outBase * (envelopeUnit.getVolume() * 2 - 15ul) : outLow; + const unsigned long nextMajorEvent = nextEventUnit->getCounter() < endCycles ? nextEventUnit->getCounter() : endCycles; + unsigned long out = dutyUnit.isHighState() ? outHigh : outLow; + + while (dutyUnit.getCounter() <= nextMajorEvent) { + *buf = out - prevOut; + prevOut = out; + buf += dutyUnit.getCounter() - cycleCounter; + cycleCounter = dutyUnit.getCounter(); + + dutyUnit.event(); + out = dutyUnit.isHighState() ? outHigh : outLow; + } + + if (cycleCounter < nextMajorEvent) { + *buf = out - prevOut; + prevOut = out; + buf += nextMajorEvent - cycleCounter; + cycleCounter = nextMajorEvent; + } + + if (nextEventUnit->getCounter() == nextMajorEvent) { + nextEventUnit->event(); + setEvent(); + } else + break; + } + + if (cycleCounter & SoundUnit::COUNTER_MAX) { + dutyUnit.resetCounters(cycleCounter); + lengthCounter.resetCounters(cycleCounter); + envelopeUnit.resetCounters(cycleCounter); + sweepUnit.resetCounters(cycleCounter); + + cycleCounter -= SoundUnit::COUNTER_MAX; + } +} diff --git a/supergameboy/libgambatte/src/sound/channel1.h b/supergameboy/libgambatte/src/sound/channel1.h new file mode 100644 index 00000000..d790e0ec --- /dev/null +++ b/supergameboy/libgambatte/src/sound/channel1.h @@ -0,0 +1,91 @@ +/*************************************************************************** + * Copyright (C) 2007 by Sindre Aam�s * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#ifndef SOUND_CHANNEL1_H +#define SOUND_CHANNEL1_H + +class SaveState; + +#include "int.h" + +#include "master_disabler.h" +#include "length_counter.h" +#include "duty_unit.h" +#include "envelope_unit.h" +#include "static_output_tester.h" + +class Channel1 { + class SweepUnit : public SoundUnit { + MasterDisabler &disableMaster; + DutyUnit &dutyUnit; + unsigned short shadow; + unsigned char nr0; + bool negging; + + unsigned calcFreq(); + + public: + SweepUnit(MasterDisabler &disabler, DutyUnit &dutyUnit); + void event(); + void nr0Change(unsigned newNr0); + void nr4Init(unsigned long cycleCounter); + void reset(); + void saveState(SaveState &state) const; + void loadState(const SaveState &state); + }; + + friend class StaticOutputTester; + + StaticOutputTester staticOutputTest; + DutyMasterDisabler disableMaster; + LengthCounter lengthCounter; + DutyUnit dutyUnit; + EnvelopeUnit envelopeUnit; + SweepUnit sweepUnit; + + SoundUnit *nextEventUnit; + + unsigned long cycleCounter; + unsigned long soMask; + unsigned long prevOut; + + unsigned char nr4; + bool master; + + void setEvent(); + +public: + Channel1(); + void setNr0(unsigned data); + void setNr1(unsigned data); + void setNr2(unsigned data); + void setNr3(unsigned data); + void setNr4(unsigned data); + + void setSo(unsigned long soMask); + bool isActive() const { return master; } + + void update(Gambatte::uint_least32_t *buf, unsigned long soBaseVol, unsigned long cycles); + + void reset(); + void init(bool cgb); + void saveState(SaveState &state); + void loadState(const SaveState &state); +}; + +#endif diff --git a/supergameboy/libgambatte/src/sound/channel2.cpp b/supergameboy/libgambatte/src/sound/channel2.cpp new file mode 100644 index 00000000..2db30658 --- /dev/null +++ b/supergameboy/libgambatte/src/sound/channel2.cpp @@ -0,0 +1,161 @@ +/*************************************************************************** + * Copyright (C) 2007 by Sindre Aam�s * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#include "channel2.h" +#include "../savestate.h" + +Channel2::Channel2() : + staticOutputTest(*this, dutyUnit), + disableMaster(master, dutyUnit), + lengthCounter(disableMaster, 0x3F), + envelopeUnit(staticOutputTest), + cycleCounter(0), + soMask(0), + prevOut(0), + nr4(0), + master(false) +{ + setEvent(); +} + +void Channel2::setEvent() { +// nextEventUnit = &dutyUnit; +// if (envelopeUnit.getCounter() < nextEventUnit->getCounter()) + nextEventUnit = &envelopeUnit; + if (lengthCounter.getCounter() < nextEventUnit->getCounter()) + nextEventUnit = &lengthCounter; +} + +void Channel2::setNr1(const unsigned data) { + lengthCounter.nr1Change(data, nr4, cycleCounter); + dutyUnit.nr1Change(data, cycleCounter); + + setEvent(); +} + +void Channel2::setNr2(const unsigned data) { + if (envelopeUnit.nr2Change(data)) + disableMaster(); + else + staticOutputTest(cycleCounter); + + setEvent(); +} + +void Channel2::setNr3(const unsigned data) { + dutyUnit.nr3Change(data, cycleCounter); + setEvent(); +} + +void Channel2::setNr4(const unsigned data) { + lengthCounter.nr4Change(nr4, data, cycleCounter); + + nr4 = data; + + if (data & 0x80) { //init-bit + nr4 &= 0x7F; + master = !envelopeUnit.nr4Init(cycleCounter); + staticOutputTest(cycleCounter); + } + + dutyUnit.nr4Change(data, cycleCounter); + + setEvent(); +} + +void Channel2::setSo(const unsigned long soMask) { + this->soMask = soMask; + staticOutputTest(cycleCounter); + setEvent(); +} + +void Channel2::reset() { + cycleCounter = 0x1000 | (cycleCounter & 0xFFF); // cycleCounter >> 12 & 7 represents the frame sequencer position. + +// lengthCounter.reset(); + dutyUnit.reset(); + envelopeUnit.reset(); + + setEvent(); +} + +void Channel2::init(const bool cgb) { + lengthCounter.init(cgb); +} + +void Channel2::saveState(SaveState &state) { + dutyUnit.saveState(state.spu.ch2.duty, cycleCounter); + envelopeUnit.saveState(state.spu.ch2.env); + lengthCounter.saveState(state.spu.ch2.lcounter); + + state.spu.ch2.nr4 = nr4; + state.spu.ch2.master = master; +} + +void Channel2::loadState(const SaveState &state) { + dutyUnit.loadState(state.spu.ch2.duty, state.mem.ioamhram.get()[0x116], state.spu.ch2.nr4,state.spu.cycleCounter); + envelopeUnit.loadState(state.spu.ch2.env, state.mem.ioamhram.get()[0x117], state.spu.cycleCounter); + lengthCounter.loadState(state.spu.ch2.lcounter, state.spu.cycleCounter); + + cycleCounter = state.spu.cycleCounter; + nr4 = state.spu.ch2.nr4; + master = state.spu.ch2.master; +} + +void Channel2::update(Gambatte::uint_least32_t *buf, const unsigned long soBaseVol, unsigned long cycles) { + const unsigned long outBase = envelopeUnit.dacIsOn() ? soBaseVol & soMask : 0; + const unsigned long outLow = outBase * (0 - 15ul); + const unsigned long endCycles = cycleCounter + cycles; + + for (;;) { + const unsigned long outHigh = master ? outBase * (envelopeUnit.getVolume() * 2 - 15ul) : outLow; + const unsigned long nextMajorEvent = nextEventUnit->getCounter() < endCycles ? nextEventUnit->getCounter() : endCycles; + unsigned long out = dutyUnit.isHighState() ? outHigh : outLow; + + while (dutyUnit.getCounter() <= nextMajorEvent) { + *buf += out - prevOut; + prevOut = out; + buf += dutyUnit.getCounter() - cycleCounter; + cycleCounter = dutyUnit.getCounter(); + + dutyUnit.event(); + out = dutyUnit.isHighState() ? outHigh : outLow; + } + + if (cycleCounter < nextMajorEvent) { + *buf += out - prevOut; + prevOut = out; + buf += nextMajorEvent - cycleCounter; + cycleCounter = nextMajorEvent; + } + + if (nextEventUnit->getCounter() == nextMajorEvent) { + nextEventUnit->event(); + setEvent(); + } else + break; + } + + if (cycleCounter & SoundUnit::COUNTER_MAX) { + dutyUnit.resetCounters(cycleCounter); + lengthCounter.resetCounters(cycleCounter); + envelopeUnit.resetCounters(cycleCounter); + + cycleCounter -= SoundUnit::COUNTER_MAX; + } +} diff --git a/supergameboy/libgambatte/src/sound/channel2.h b/supergameboy/libgambatte/src/sound/channel2.h new file mode 100644 index 00000000..24bc66a4 --- /dev/null +++ b/supergameboy/libgambatte/src/sound/channel2.h @@ -0,0 +1,70 @@ +/*************************************************************************** + * Copyright (C) 2007 by Sindre Aam�s * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#ifndef SOUND_CHANNEL2_H +#define SOUND_CHANNEL2_H + +class SaveState; + +#include "int.h" + +#include "length_counter.h" +#include "duty_unit.h" +#include "envelope_unit.h" +#include "static_output_tester.h" + +class Channel2 { + friend class StaticOutputTester; + + StaticOutputTester staticOutputTest; + DutyMasterDisabler disableMaster; + LengthCounter lengthCounter; + DutyUnit dutyUnit; + EnvelopeUnit envelopeUnit; + + SoundUnit *nextEventUnit; + + unsigned long cycleCounter; + unsigned long soMask; + unsigned long prevOut; + + unsigned char nr4; + bool master; + + void setEvent(); + +public: + Channel2(); + void setNr1(unsigned data); + void setNr2(unsigned data); + void setNr3(unsigned data); + void setNr4(unsigned data); + + void setSo(unsigned long soMask); + // void deactivate() { disableMaster(); setEvent(); } + bool isActive() const { return master; } + + void update(Gambatte::uint_least32_t *buf, unsigned long soBaseVol, unsigned long cycles); + + void reset(); + void init(bool cgb); + void saveState(SaveState &state); + void loadState(const SaveState &state); +}; + +#endif diff --git a/supergameboy/libgambatte/src/sound/channel3.cpp b/supergameboy/libgambatte/src/sound/channel3.cpp new file mode 100644 index 00000000..944271e3 --- /dev/null +++ b/supergameboy/libgambatte/src/sound/channel3.cpp @@ -0,0 +1,207 @@ +/*************************************************************************** + * Copyright (C) 2007 by Sindre Aam�s * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#include "channel3.h" +#include "../savestate.h" +#include +#include + +static inline unsigned toPeriod(const unsigned nr3, const unsigned nr4) { + return 0x800 - ((nr4 << 8 & 0x700) | nr3); +} + +Channel3::Channel3() : + disableMaster(master, waveCounter), + lengthCounter(disableMaster, 0xFF), + cycleCounter(0), + soMask(0), + prevOut(0), + waveCounter(SoundUnit::COUNTER_DISABLED), + lastReadTime(0), + nr0(0), + nr3(0), + nr4(0), + wavePos(0), + rShift(4), + sampleBuf(0), + master(false), + cgb(false) +{} + +void Channel3::setNr0(const unsigned data) { + nr0 = data & 0x80; + + if (!(data & 0x80)) + disableMaster(); +} + +void Channel3::setNr2(const unsigned data) { + rShift = (data >> 5 & 3U) - 1; + + if (rShift > 3) + rShift = 4; +} + +void Channel3::setNr4(const unsigned data) { + lengthCounter.nr4Change(nr4, data, cycleCounter); + + nr4 = data & 0x7F; + + if (data & nr0/* & 0x80*/) { + if (!cgb && waveCounter == cycleCounter + 1) { + const unsigned pos = ((wavePos + 1) & 0x1F) >> 1; + + if (pos < 4) + waveRam[0] = waveRam[pos]; + else + std::memcpy(waveRam, waveRam + (pos & ~3), 4); + } + + master = true; + wavePos = 0; + lastReadTime = waveCounter = cycleCounter + toPeriod(nr3, data) + 3; + } +} + +void Channel3::setSo(const unsigned long soMask) { + this->soMask = soMask; +} + +void Channel3::reset() { + cycleCounter = 0x1000 | (cycleCounter & 0xFFF); // cycleCounter >> 12 & 7 represents the frame sequencer position. + +// lengthCounter.reset(); + sampleBuf = 0; +} + +void Channel3::init(const bool cgb) { + this->cgb = cgb; + lengthCounter.init(cgb); +} + +void Channel3::setStatePtrs(SaveState &state) { + state.spu.ch3.waveRam.set(waveRam, sizeof waveRam); +} + +void Channel3::saveState(SaveState &state) const { + lengthCounter.saveState(state.spu.ch3.lcounter); + + state.spu.ch3.waveCounter = waveCounter; + state.spu.ch3.lastReadTime = lastReadTime; + state.spu.ch3.nr3 = nr3; + state.spu.ch3.nr4 = nr4; + state.spu.ch3.wavePos = wavePos; + state.spu.ch3.sampleBuf = sampleBuf; + state.spu.ch3.master = master; +} + +void Channel3::loadState(const SaveState &state) { + lengthCounter.loadState(state.spu.ch3.lcounter, state.spu.cycleCounter); + + cycleCounter = state.spu.cycleCounter; + waveCounter = std::max(state.spu.ch3.waveCounter, state.spu.cycleCounter); + lastReadTime = state.spu.ch3.lastReadTime; + nr3 = state.spu.ch3.nr3; + nr4 = state.spu.ch3.nr4; + wavePos = state.spu.ch3.wavePos & 0x1F; + sampleBuf = state.spu.ch3.sampleBuf; + master = state.spu.ch3.master; + + nr0 = state.mem.ioamhram.get()[0x11A] & 0x80; + setNr2(state.mem.ioamhram.get()[0x11C]); +} + +void Channel3::updateWaveCounter(const unsigned long cc) { + if (cc >= waveCounter) { + const unsigned period = toPeriod(nr3, nr4); + const unsigned long periods = (cc - waveCounter) / period; + + lastReadTime = waveCounter + periods * period; + waveCounter = lastReadTime + period; + + wavePos += periods + 1; + wavePos &= 0x1F; + + sampleBuf = waveRam[wavePos >> 1]; + } +} + +void Channel3::update(Gambatte::uint_least32_t *buf, const unsigned long soBaseVol, unsigned long cycles) { + const unsigned long outBase = (nr0/* & 0x80*/) ? soBaseVol & soMask : 0; + + if (outBase && rShift != 4) { + const unsigned long endCycles = cycleCounter + cycles; + + for (;;) { + const unsigned long nextMajorEvent = lengthCounter.getCounter() < endCycles ? lengthCounter.getCounter() : endCycles; + unsigned long out = outBase * (master ? ((sampleBuf >> (~wavePos << 2 & 4) & 0xF) >> rShift) * 2 - 15ul : 0 - 15ul); + + while (waveCounter <= nextMajorEvent) { + *buf += out - prevOut; + prevOut = out; + buf += waveCounter - cycleCounter; + cycleCounter = waveCounter; + + lastReadTime = waveCounter; + waveCounter += toPeriod(nr3, nr4); + ++wavePos; + wavePos &= 0x1F; + sampleBuf = waveRam[wavePos >> 1]; + out = outBase * (/*master ? */((sampleBuf >> (~wavePos << 2 & 4) & 0xF) >> rShift) * 2 - 15ul/* : 0 - 15ul*/); + } + + if (cycleCounter < nextMajorEvent) { + *buf += out - prevOut; + prevOut = out; + buf += nextMajorEvent - cycleCounter; + cycleCounter = nextMajorEvent; + } + + if (lengthCounter.getCounter() == nextMajorEvent) { + lengthCounter.event(); + } else + break; + } + } else { + if (outBase) { + const unsigned long out = outBase * (0 - 15ul); + + *buf += out - prevOut; + prevOut = out; + } + + cycleCounter += cycles; + + while (lengthCounter.getCounter() <= cycleCounter) { + updateWaveCounter(lengthCounter.getCounter()); + lengthCounter.event(); + } + + updateWaveCounter(cycleCounter); + } + + if (cycleCounter & SoundUnit::COUNTER_MAX) { + lengthCounter.resetCounters(cycleCounter); + + if (waveCounter != SoundUnit::COUNTER_DISABLED) + waveCounter -= SoundUnit::COUNTER_MAX; + + lastReadTime -= SoundUnit::COUNTER_MAX; + cycleCounter -= SoundUnit::COUNTER_MAX; + } +} diff --git a/supergameboy/libgambatte/src/sound/channel3.h b/supergameboy/libgambatte/src/sound/channel3.h new file mode 100644 index 00000000..8ec8688d --- /dev/null +++ b/supergameboy/libgambatte/src/sound/channel3.h @@ -0,0 +1,100 @@ +/*************************************************************************** + * Copyright (C) 2007 by Sindre Aam�s * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#ifndef SOUND_CHANNEL3_H +#define SOUND_CHANNEL3_H + +class SaveState; + +#include "int.h" + +#include "master_disabler.h" +#include "length_counter.h" + +class Channel3 { + class Ch3MasterDisabler : public MasterDisabler { + unsigned long &waveCounter; + + public: + Ch3MasterDisabler(bool &m, unsigned long &wC) : MasterDisabler(m), waveCounter(wC) {} + void operator()() { MasterDisabler::operator()(); waveCounter = SoundUnit::COUNTER_DISABLED; } + }; + + unsigned char waveRam[0x10]; + + Ch3MasterDisabler disableMaster; + LengthCounter lengthCounter; + + unsigned long cycleCounter; + unsigned long soMask; + unsigned long prevOut; + unsigned long waveCounter; + unsigned long lastReadTime; + + unsigned char nr0; + unsigned char nr3; + unsigned char nr4; + unsigned char wavePos; + unsigned char rShift; + unsigned char sampleBuf; + + bool master; + bool cgb; + + void updateWaveCounter(unsigned long cc); + +public: + Channel3(); + bool isActive() const { return master; } + void reset(); + void init(bool cgb); + void setStatePtrs(SaveState &state); + void saveState(SaveState &state) const; + void loadState(const SaveState &state); + void setNr0(unsigned data); + void setNr1(unsigned data) { lengthCounter.nr1Change(data, nr4, cycleCounter); } + void setNr2(unsigned data); + void setNr3(unsigned data) { nr3 = data; } + void setNr4(unsigned data); + void setSo(unsigned long soMask); + void update(Gambatte::uint_least32_t *buf, unsigned long soBaseVol, unsigned long cycles); + + unsigned waveRamRead(unsigned index) const { + if (master) { + if (!cgb && cycleCounter != lastReadTime) + return 0xFF; + + index = wavePos >> 1; + } + + return waveRam[index]; + } + + void waveRamWrite(unsigned index, unsigned data) { + if (master) { + if (!cgb && cycleCounter != lastReadTime) + return; + + index = wavePos >> 1; + } + + waveRam[index] = data; + } +}; + +#endif diff --git a/supergameboy/libgambatte/src/sound/channel4.cpp b/supergameboy/libgambatte/src/sound/channel4.cpp new file mode 100644 index 00000000..c1efcf28 --- /dev/null +++ b/supergameboy/libgambatte/src/sound/channel4.cpp @@ -0,0 +1,300 @@ +/*************************************************************************** + * Copyright (C) 2007 by Sindre Aam�s * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#include "channel4.h" +#include "../savestate.h" +#include + +static unsigned long toPeriod(const unsigned nr3) { + unsigned s = (nr3 >> 4) + 3; + unsigned r = nr3 & 7; + + if (!r) { + r = 1; + --s; + } + + return r << s; +} + +Channel4::Lfsr::Lfsr() : +backupCounter(COUNTER_DISABLED), +reg(0xFF), +nr3(0), +master(false) +{} + +void Channel4::Lfsr::updateBackupCounter(const unsigned long cc) { + /*if (backupCounter <= cc) { + const unsigned long period = toPeriod(nr3); + backupCounter = cc - (cc - backupCounter) % period + period; + }*/ + + if (backupCounter <= cc) { + const unsigned long period = toPeriod(nr3); + unsigned long periods = (cc - backupCounter) / period + 1; + + backupCounter += periods * period; + + if (master && nr3 < 0xE0) { + if (nr3 & 8) { + while (periods > 6) { + const unsigned xored = (reg << 1 ^ reg) & 0x7E; + reg = (reg >> 6 & ~0x7E) | xored | xored << 8; + periods -= 6; + } + + const unsigned xored = ((reg ^ reg >> 1) << (7 - periods)) & 0x7F; + reg = (reg >> periods & ~(0x80 - (0x80 >> periods))) | xored | xored << 8; + } else { + while (periods > 15) { + reg = reg ^ reg >> 1; + periods -= 15; + } + + reg = reg >> periods | (((reg ^ reg >> 1) << (15 - periods)) & 0x7FFF); + } + } + } +} + +void Channel4::Lfsr::reviveCounter(const unsigned long cc) { + updateBackupCounter(cc); + counter = backupCounter; +} + +/*static const unsigned char nextStateDistance[0x40] = { + 6, 1, 1, 2, 2, 1, 1, 3, + 3, 1, 1, 2, 2, 1, 1, 4, + 4, 1, 1, 2, 2, 1, 1, 3, + 3, 1, 1, 2, 2, 1, 1, 5, + 5, 1, 1, 2, 2, 1, 1, 3, + 3, 1, 1, 2, 2, 1, 1, 4, + 4, 1, 1, 2, 2, 1, 1, 3, + 3, 1, 1, 2, 2, 1, 1, 6, +};*/ + +inline void Channel4::Lfsr::event() { + if (nr3 < 0xE0) { + const unsigned shifted = reg >> 1; + const unsigned xored = (reg ^ shifted) & 1; + + reg = shifted | xored << 14; + + if (nr3 & 8) + reg = (reg & ~0x40) | xored << 6; + } + + counter += toPeriod(nr3); + backupCounter = counter; + + + /*if (nr3 < 0xE0) { + const unsigned periods = nextStateDistance[reg & 0x3F]; + const unsigned xored = ((reg ^ reg >> 1) << (7 - periods)) & 0x7F; + + reg = reg >> periods | xored << 8; + + if (nr3 & 8) + reg = reg & ~(0x80 - (0x80 >> periods)) | xored; + } + + const unsigned long period = toPeriod(nr3); + backupCounter = counter + period; + counter += period * nextStateDistance[reg & 0x3F];*/ +} + +void Channel4::Lfsr::nr3Change(const unsigned newNr3, const unsigned long cc) { + updateBackupCounter(cc); + nr3 = newNr3; + +// if (counter != COUNTER_DISABLED) +// counter = backupCounter + toPeriod(nr3) * (nextStateDistance[reg & 0x3F] - 1); +} + +void Channel4::Lfsr::nr4Init(unsigned long cc) { + disableMaster(); + updateBackupCounter(cc); + master = true; + backupCounter += 4; + counter = backupCounter; +// counter = backupCounter + toPeriod(nr3) * (nextStateDistance[reg & 0x3F] - 1); +} + +void Channel4::Lfsr::reset(const unsigned long cc) { + nr3 = 0; + disableMaster(); + backupCounter = cc + toPeriod(nr3); +} + +void Channel4::Lfsr::resetCounters(const unsigned long oldCc) { + updateBackupCounter(oldCc); + backupCounter -= COUNTER_MAX; + SoundUnit::resetCounters(oldCc); +} + +void Channel4::Lfsr::saveState(SaveState &state, const unsigned long cc) { + updateBackupCounter(cc); + state.spu.ch4.lfsr.counter = backupCounter; + state.spu.ch4.lfsr.reg = reg; +} + +void Channel4::Lfsr::loadState(const SaveState &state) { + counter = backupCounter = std::max(state.spu.ch4.lfsr.counter, state.spu.cycleCounter); + reg = state.spu.ch4.lfsr.reg; + master = state.spu.ch4.master; + nr3 = state.mem.ioamhram.get()[0x122]; +} + +Channel4::Channel4() : + staticOutputTest(*this, lfsr), + disableMaster(master, lfsr), + lengthCounter(disableMaster, 0x3F), + envelopeUnit(staticOutputTest), + cycleCounter(0), + soMask(0), + prevOut(0), + nr4(0), + master(false) +{ + setEvent(); +} + +void Channel4::setEvent() { +// nextEventUnit = &lfsr; +// if (envelopeUnit.getCounter() < nextEventUnit->getCounter()) + nextEventUnit = &envelopeUnit; + if (lengthCounter.getCounter() < nextEventUnit->getCounter()) + nextEventUnit = &lengthCounter; +} + +void Channel4::setNr1(const unsigned data) { + lengthCounter.nr1Change(data, nr4, cycleCounter); + + setEvent(); +} + +void Channel4::setNr2(const unsigned data) { + if (envelopeUnit.nr2Change(data)) + disableMaster(); + else + staticOutputTest(cycleCounter); + + setEvent(); +} + +void Channel4::setNr4(const unsigned data) { + lengthCounter.nr4Change(nr4, data, cycleCounter); + + nr4 = data; + + if (data & 0x80) { //init-bit + nr4 &= 0x7F; + + master = !envelopeUnit.nr4Init(cycleCounter); + + if (master) + lfsr.nr4Init(cycleCounter); + + staticOutputTest(cycleCounter); + } + + setEvent(); +} + +void Channel4::setSo(const unsigned long soMask) { + this->soMask = soMask; + staticOutputTest(cycleCounter); + setEvent(); +} + +void Channel4::reset() { + cycleCounter = 0x1000 | (cycleCounter & 0xFFF); // cycleCounter >> 12 & 7 represents the frame sequencer position. + +// lengthCounter.reset(); + lfsr.reset(cycleCounter); + envelopeUnit.reset(); + + setEvent(); +} + +void Channel4::init(const bool cgb) { + lengthCounter.init(cgb); +} + +void Channel4::saveState(SaveState &state) { + lfsr.saveState(state, cycleCounter); + envelopeUnit.saveState(state.spu.ch4.env); + lengthCounter.saveState(state.spu.ch4.lcounter); + + state.spu.ch4.nr4 = nr4; + state.spu.ch4.master = master; +} + +void Channel4::loadState(const SaveState &state) { + lfsr.loadState(state); + envelopeUnit.loadState(state.spu.ch4.env, state.mem.ioamhram.get()[0x121], state.spu.cycleCounter); + lengthCounter.loadState(state.spu.ch4.lcounter, state.spu.cycleCounter); + + cycleCounter = state.spu.cycleCounter; + nr4 = state.spu.ch4.nr4; + master = state.spu.ch4.master; +} + +void Channel4::update(Gambatte::uint_least32_t *buf, const unsigned long soBaseVol, unsigned long cycles) { + const unsigned long outBase = envelopeUnit.dacIsOn() ? soBaseVol & soMask : 0; + const unsigned long outLow = outBase * (0 - 15ul); + const unsigned long endCycles = cycleCounter + cycles; + + for (;;) { + const unsigned long outHigh = /*master ? */outBase * (envelopeUnit.getVolume() * 2 - 15ul)/* : outLow*/; + const unsigned long nextMajorEvent = nextEventUnit->getCounter() < endCycles ? nextEventUnit->getCounter() : endCycles; + unsigned long out = lfsr.isHighState() ? outHigh : outLow; + + while (lfsr.getCounter() <= nextMajorEvent) { + *buf += out - prevOut; + prevOut = out; + buf += lfsr.getCounter() - cycleCounter; + cycleCounter = lfsr.getCounter(); + + lfsr.event(); + out = lfsr.isHighState() ? outHigh : outLow; + } + + if (cycleCounter < nextMajorEvent) { + *buf += out - prevOut; + prevOut = out; + buf += nextMajorEvent - cycleCounter; + cycleCounter = nextMajorEvent; + } + + if (nextEventUnit->getCounter() == nextMajorEvent) { + nextEventUnit->event(); + setEvent(); + } else + break; + } + + if (cycleCounter & SoundUnit::COUNTER_MAX) { + lengthCounter.resetCounters(cycleCounter); + lfsr.resetCounters(cycleCounter); + envelopeUnit.resetCounters(cycleCounter); + + cycleCounter -= SoundUnit::COUNTER_MAX; + } +} diff --git a/supergameboy/libgambatte/src/sound/channel4.h b/supergameboy/libgambatte/src/sound/channel4.h new file mode 100644 index 00000000..7563dc2c --- /dev/null +++ b/supergameboy/libgambatte/src/sound/channel4.h @@ -0,0 +1,99 @@ +/*************************************************************************** + * Copyright (C) 2007 by Sindre Aam�s * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#ifndef SOUND_CHANNEL4_H +#define SOUND_CHANNEL4_H + +class SaveState; + +#include "int.h" + +#include "master_disabler.h" +#include "length_counter.h" +#include "envelope_unit.h" +#include "static_output_tester.h" + +class Channel4 { + class Lfsr : public SoundUnit { + unsigned long backupCounter; + unsigned short reg; + unsigned char nr3; + bool master; + + void updateBackupCounter(unsigned long cc); + + public: + Lfsr(); + void event(); + bool isHighState() const { return ~reg & 1; } + void nr3Change(unsigned newNr3, unsigned long cc); + void nr4Init(unsigned long cc); + void reset(unsigned long cc); + void saveState(SaveState &state, const unsigned long cc); + void loadState(const SaveState &state); + void resetCounters(unsigned long oldCc); + void disableMaster() { killCounter(); master = false; reg = 0xFF; } + void killCounter() { counter = COUNTER_DISABLED; } + void reviveCounter(unsigned long cc); + }; + + class Ch4MasterDisabler : public MasterDisabler { + Lfsr &lfsr; + public: + Ch4MasterDisabler(bool &m, Lfsr &lfsr) : MasterDisabler(m), lfsr(lfsr) {} + void operator()() { MasterDisabler::operator()(); lfsr.disableMaster(); } + }; + + friend class StaticOutputTester; + + StaticOutputTester staticOutputTest; + Ch4MasterDisabler disableMaster; + LengthCounter lengthCounter; + EnvelopeUnit envelopeUnit; + Lfsr lfsr; + + SoundUnit *nextEventUnit; + + unsigned long cycleCounter; + unsigned long soMask; + unsigned long prevOut; + + unsigned char nr4; + bool master; + + void setEvent(); + +public: + Channel4(); + void setNr1(unsigned data); + void setNr2(unsigned data); + void setNr3(unsigned data) { lfsr.nr3Change(data, cycleCounter); /*setEvent();*/ } + void setNr4(unsigned data); + + void setSo(unsigned long soMask); + bool isActive() const { return master; } + + void update(Gambatte::uint_least32_t *buf, unsigned long soBaseVol, unsigned long cycles); + + void reset(); + void init(bool cgb); + void saveState(SaveState &state); + void loadState(const SaveState &state); +}; + +#endif diff --git a/supergameboy/libgambatte/src/sound/duty_unit.cpp b/supergameboy/libgambatte/src/sound/duty_unit.cpp new file mode 100644 index 00000000..d3de6abd --- /dev/null +++ b/supergameboy/libgambatte/src/sound/duty_unit.cpp @@ -0,0 +1,148 @@ +/*************************************************************************** + * Copyright (C) 2007 by Sindre Aamås * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#include "duty_unit.h" +#include + +static inline bool toOutState(const unsigned duty, const unsigned pos) { + static const unsigned char duties[4] = { 0x80, 0x81, 0xE1, 0x7E }; + + return duties[duty] >> pos & 1; +} + +static inline unsigned toPeriod(const unsigned freq) { + return (2048 - freq) << 1; +} + +void DutyUnit::updatePos(const unsigned long cc) { + if (cc >= nextPosUpdate) { + const unsigned long inc = (cc - nextPosUpdate) / period + 1; + nextPosUpdate += period * inc; + pos += inc; + pos &= 7; + } +} + +void DutyUnit::setDuty(const unsigned nr1) { + duty = nr1 >> 6; + high = toOutState(duty, pos); +} + +void DutyUnit::setCounter() { + static const unsigned char nextStateDistance[4 * 8] = { + 6, 5, 4, 3, 2, 1, 0, 0, + 0, 5, 4, 3, 2, 1, 0, 1, + 0, 3, 2, 1, 0, 3, 2, 1, + 0, 5, 4, 3, 2, 1, 0, 1 + }; + + if (enableEvents && nextPosUpdate != COUNTER_DISABLED) + counter = nextPosUpdate + period * nextStateDistance[(duty * 8) | pos]; + else + counter = COUNTER_DISABLED; +} + +void DutyUnit::setFreq(const unsigned newFreq, const unsigned long cc) { + updatePos(cc); + period = toPeriod(newFreq); + setCounter(); +} + +void DutyUnit::event() { + unsigned inc = period << duty; + + if (duty == 3) + inc -= period * 2; + + if (!(high ^= true)) + inc = period * 8 - inc; + + counter += inc; +} + +void DutyUnit::nr1Change(const unsigned newNr1, const unsigned long cc) { + updatePos(cc); + setDuty(newNr1); + setCounter(); +} + +void DutyUnit::nr3Change(const unsigned newNr3, const unsigned long cc) { + setFreq((getFreq() & 0x700) | newNr3, cc); +} + +void DutyUnit::nr4Change(const unsigned newNr4, const unsigned long cc) { + setFreq((newNr4 << 8 & 0x700) | (getFreq() & 0xFF), cc); + + if (newNr4 & 0x80) { + nextPosUpdate = (cc & ~1) + period; + setCounter(); + } +} + +DutyUnit::DutyUnit() : +nextPosUpdate(COUNTER_DISABLED), +period(4096), +pos(0), +duty(0), +high(false), +enableEvents(true) +{} + +void DutyUnit::reset() { + pos = 0; + high = toOutState(duty, pos); + nextPosUpdate = COUNTER_DISABLED; + setCounter(); +} + +void DutyUnit::saveState(SaveState::SPU::Duty &dstate, const unsigned long cc) { + updatePos(cc); + dstate.nextPosUpdate = nextPosUpdate; + dstate.nr3 = getFreq() & 0xFF; + dstate.pos = pos; +} + +void DutyUnit::loadState(const SaveState::SPU::Duty &dstate, const unsigned nr1, const unsigned nr4, const unsigned long cc) { + nextPosUpdate = std::max(dstate.nextPosUpdate, cc); + pos = dstate.pos & 7; + setDuty(nr1); + period = toPeriod((nr4 << 8 & 0x700) | dstate.nr3); + enableEvents = true; + setCounter(); +} + +void DutyUnit::resetCounters(const unsigned long oldCc) { + if (nextPosUpdate == COUNTER_DISABLED) + return; + + updatePos(oldCc); + nextPosUpdate -= COUNTER_MAX; + SoundUnit::resetCounters(oldCc); +} + +void DutyUnit::killCounter() { + enableEvents = false; + setCounter(); +} + +void DutyUnit::reviveCounter(const unsigned long cc) { + updatePos(cc); + high = toOutState(duty, pos); + enableEvents = true; + setCounter(); +} diff --git a/supergameboy/libgambatte/src/sound/duty_unit.h b/supergameboy/libgambatte/src/sound/duty_unit.h new file mode 100644 index 00000000..e55cec59 --- /dev/null +++ b/supergameboy/libgambatte/src/sound/duty_unit.h @@ -0,0 +1,64 @@ +/*************************************************************************** + * Copyright (C) 2007 by Sindre Aam�s * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#ifndef DUTY_UNIT_H +#define DUTY_UNIT_H + +#include "sound_unit.h" +#include "master_disabler.h" +#include "../savestate.h" + +class DutyUnit : public SoundUnit { + unsigned long nextPosUpdate; + unsigned short period; + unsigned char pos; + unsigned char duty; + bool high; + bool enableEvents; + + void setCounter(); + void setDuty(unsigned nr1); + void updatePos(unsigned long cc); + +public: + DutyUnit(); + void event(); + bool isHighState() const { return high; } + void nr1Change(unsigned newNr1, unsigned long cc); + void nr3Change(unsigned newNr3, unsigned long cc); + void nr4Change(unsigned newNr4, unsigned long cc); + void reset(); + void saveState(SaveState::SPU::Duty &dstate, unsigned long cc); + void loadState(const SaveState::SPU::Duty &dstate, unsigned nr1, unsigned nr4, unsigned long cc); + void resetCounters(unsigned long oldCc); + void killCounter(); + void reviveCounter(unsigned long cc); + + //intended for use by SweepUnit only. + unsigned getFreq() const { return 2048 - (period >> 1); } + void setFreq(unsigned newFreq, unsigned long cc); +}; + +class DutyMasterDisabler : public MasterDisabler { + DutyUnit &dutyUnit; +public: + DutyMasterDisabler(bool &m, DutyUnit &dutyUnit) : MasterDisabler(m), dutyUnit(dutyUnit) {} + void operator()() { MasterDisabler::operator()(); dutyUnit.killCounter(); } +}; + +#endif diff --git a/supergameboy/libgambatte/src/sound/envelope_unit.cpp b/supergameboy/libgambatte/src/sound/envelope_unit.cpp new file mode 100644 index 00000000..ed526eb5 --- /dev/null +++ b/supergameboy/libgambatte/src/sound/envelope_unit.cpp @@ -0,0 +1,101 @@ +/*************************************************************************** + * Copyright (C) 2007 by Sindre Aamås * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#include "envelope_unit.h" +#include + +EnvelopeUnit::VolOnOffEvent EnvelopeUnit::nullEvent; + +void EnvelopeUnit::event() { + const unsigned long period = nr2 & 7; + + if (period) { + unsigned newVol = volume; + + if (nr2 & 8) + ++newVol; + else + --newVol; + + if (newVol < 0x10U) { + volume = newVol; + + if (volume < 2) + volOnOffEvent(counter); + + counter += period << 15; + } else + counter = COUNTER_DISABLED; + } else + counter += 8ul << 15; +} + +bool EnvelopeUnit::nr2Change(const unsigned newNr2) { + if (!(nr2 & 7) && counter != COUNTER_DISABLED) + ++volume; + else if (!(nr2 & 8)) + volume += 2; + + if ((nr2 ^ newNr2) & 8) + volume = 0x10 - volume; + + volume &= 0xF; + + nr2 = newNr2; + + return !(newNr2 & 0xF8); +} + +bool EnvelopeUnit::nr4Init(const unsigned long cc) { + { + unsigned long period = nr2 & 7; + + if (!period) + period = 8; + + if (!(cc & 0x7000)) + ++period; + + counter = cc - ((cc - 0x1000) & 0x7FFF) + period * 0x8000; + } + + volume = nr2 >> 4; + + return !(nr2 & 0xF8); +} + +EnvelopeUnit::EnvelopeUnit(VolOnOffEvent &volOnOffEvent) : +volOnOffEvent(volOnOffEvent), +nr2(0), +volume(0) +{} + +void EnvelopeUnit::reset() { + counter = COUNTER_DISABLED; +} + +void EnvelopeUnit::saveState(SaveState::SPU::Env &estate) const { + estate.counter = counter; + estate.volume = volume; +} + +void EnvelopeUnit::loadState(const SaveState::SPU::Env &estate, const unsigned nr2, const unsigned long cc) { + counter = std::max(estate.counter, cc); + volume = estate.volume; + this->nr2 = nr2; +} diff --git a/supergameboy/libgambatte/src/sound/envelope_unit.h b/supergameboy/libgambatte/src/sound/envelope_unit.h new file mode 100644 index 00000000..e9bae2f0 --- /dev/null +++ b/supergameboy/libgambatte/src/sound/envelope_unit.h @@ -0,0 +1,50 @@ +/*************************************************************************** + * Copyright (C) 2007 by Sindre Aamås * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#ifndef ENVELOPE_UNIT_H +#define ENVELOPE_UNIT_H + +#include "sound_unit.h" +#include "../savestate.h" + +class EnvelopeUnit : public SoundUnit { +public: + struct VolOnOffEvent { + virtual ~VolOnOffEvent() {} + virtual void operator()(unsigned long /*cc*/) {} + }; + +private: + static VolOnOffEvent nullEvent; + VolOnOffEvent &volOnOffEvent; + unsigned char nr2; + unsigned char volume; + +public: + EnvelopeUnit(VolOnOffEvent &volOnOffEvent = nullEvent); + void event(); + bool dacIsOn() const { return nr2 & 0xF8; } + unsigned getVolume() const { return volume; } + bool nr2Change(unsigned newNr2); + bool nr4Init(unsigned long cycleCounter); + void reset(); + void saveState(SaveState::SPU::Env &estate) const; + void loadState(const SaveState::SPU::Env &estate, unsigned nr2, unsigned long cc); +}; + +#endif diff --git a/supergameboy/libgambatte/src/sound/length_counter.cpp b/supergameboy/libgambatte/src/sound/length_counter.cpp new file mode 100644 index 00000000..8bbe85e1 --- /dev/null +++ b/supergameboy/libgambatte/src/sound/length_counter.cpp @@ -0,0 +1,87 @@ +/*************************************************************************** + * Copyright (C) 2007 by Sindre Aamås * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ +#include "length_counter.h" +#include "master_disabler.h" +#include + +LengthCounter::LengthCounter(MasterDisabler &disabler, const unsigned mask) : + disableMaster(disabler), + lengthMask(mask) +{ + init(false); + nr1Change(0, 0, 0); +} + +void LengthCounter::event() { + counter = COUNTER_DISABLED; + lengthCounter = 0; + disableMaster(); +} + +void LengthCounter::nr1Change(const unsigned newNr1, const unsigned nr4, const unsigned long cycleCounter) { + lengthCounter = (~newNr1 & lengthMask) + 1; + counter = (nr4 & 0x40) ?( (cycleCounter >> 13) + lengthCounter) << 13 : static_cast(COUNTER_DISABLED); +} + +void LengthCounter::nr4Change(const unsigned oldNr4, const unsigned newNr4, const unsigned long cycleCounter) { + if (counter != COUNTER_DISABLED) + lengthCounter = (counter >> 13) - (cycleCounter >> 13); + + { + unsigned dec = 0; + + if (newNr4 & 0x40) { + dec = ~cycleCounter >> 12 & 1; + + if (!(oldNr4 & 0x40) && lengthCounter) { + if (!(lengthCounter -= dec)) + disableMaster(); + } + } + + if ((newNr4 & 0x80) && !lengthCounter) + lengthCounter = lengthMask + 1 - dec; + } + + if ((newNr4 & 0x40) && lengthCounter) + counter = ((cycleCounter >> 13) + lengthCounter) << 13; + else + counter = COUNTER_DISABLED; +} + +/*void LengthCounter::reset() { + counter = COUNTER_DISABLED; + + if (cgb) + lengthCounter = lengthMask + 1; +}*/ + +void LengthCounter::init(const bool cgb) { + this->cgb = cgb; +} + +void LengthCounter::saveState(SaveState::SPU::LCounter &lstate) const { + lstate.counter = counter; + lstate.lengthCounter = lengthCounter; +} + +void LengthCounter::loadState(const SaveState::SPU::LCounter &lstate, const unsigned long cc) { + counter = std::max(lstate.counter, cc); + lengthCounter = lstate.lengthCounter; +} diff --git a/supergameboy/libgambatte/src/sound/length_counter.h b/supergameboy/libgambatte/src/sound/length_counter.h new file mode 100644 index 00000000..2d9451d7 --- /dev/null +++ b/supergameboy/libgambatte/src/sound/length_counter.h @@ -0,0 +1,44 @@ +/*************************************************************************** + * Copyright (C) 2007 by Sindre Aamås * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ +#ifndef LENGTH_COUNTER_H +#define LENGTH_COUNTER_H + +#include "sound_unit.h" +#include "../savestate.h" + +class MasterDisabler; + +class LengthCounter : public SoundUnit { + MasterDisabler &disableMaster; + unsigned short lengthCounter; + const unsigned char lengthMask; + bool cgb; + +public: + LengthCounter(MasterDisabler &disabler, unsigned lengthMask); + void event(); + void nr1Change(unsigned newNr1, unsigned nr4, unsigned long cc); + void nr4Change(unsigned oldNr4, unsigned newNr4, unsigned long cc); +// void reset(); + void init(bool cgb); + void saveState(SaveState::SPU::LCounter &lstate) const; + void loadState(const SaveState::SPU::LCounter &lstate, unsigned long cc); +}; + +#endif diff --git a/supergameboy/libgambatte/src/sound/master_disabler.h b/supergameboy/libgambatte/src/sound/master_disabler.h new file mode 100644 index 00000000..7dd588c5 --- /dev/null +++ b/supergameboy/libgambatte/src/sound/master_disabler.h @@ -0,0 +1,31 @@ +/*************************************************************************** + * Copyright (C) 2007 by Sindre Aamås * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ +#ifndef MASTER_DISABLER_H +#define MASTER_DISABLER_H + +class MasterDisabler { + bool &master; + +public: + MasterDisabler(bool &m) : master(m) {} + virtual ~MasterDisabler() {} + virtual void operator()() { master = false; } +}; + +#endif diff --git a/supergameboy/libgambatte/src/sound/sound_unit.h b/supergameboy/libgambatte/src/sound/sound_unit.h new file mode 100644 index 00000000..2857c0c1 --- /dev/null +++ b/supergameboy/libgambatte/src/sound/sound_unit.h @@ -0,0 +1,35 @@ +/*************************************************************************** + * Copyright (C) 2007 by Sindre Aamås * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ +#ifndef SOUND_UNIT_H +#define SOUND_UNIT_H + +class SoundUnit { +protected: + unsigned long counter; +public: + enum { COUNTER_MAX = 0x80000000u, COUNTER_DISABLED = 0xFFFFFFFFu }; + + SoundUnit() : counter(COUNTER_DISABLED) {} + virtual ~SoundUnit() {} + virtual void event() = 0; + unsigned long getCounter() const { return counter; } + virtual void resetCounters(unsigned long /*oldCc*/) { if (counter != COUNTER_DISABLED) counter -= COUNTER_MAX; } +}; + +#endif diff --git a/supergameboy/libgambatte/src/sound/static_output_tester.h b/supergameboy/libgambatte/src/sound/static_output_tester.h new file mode 100644 index 00000000..3dbe216e --- /dev/null +++ b/supergameboy/libgambatte/src/sound/static_output_tester.h @@ -0,0 +1,41 @@ +/*************************************************************************** + * Copyright (C) 2007 by Sindre Aamås * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#ifndef STATIC_OUTPUT_TESTER_H +#define STATIC_OUTPUT_TESTER_H + +#include "envelope_unit.h" + +template +class StaticOutputTester : public EnvelopeUnit::VolOnOffEvent { + const Channel &ch; + Unit &unit; +public: + StaticOutputTester(const Channel &ch, Unit &unit) : ch(ch), unit(unit) {} + void operator()(unsigned long cc); +}; + +template +void StaticOutputTester::operator()(const unsigned long cc) { + if (ch.soMask && ch.master && ch.envelopeUnit.getVolume()) + unit.reviveCounter(cc); + else + unit.killCounter(); +} + +#endif diff --git a/supergameboy/libgambatte/src/state_osd_elements.cpp b/supergameboy/libgambatte/src/state_osd_elements.cpp new file mode 100644 index 00000000..44740d16 --- /dev/null +++ b/supergameboy/libgambatte/src/state_osd_elements.cpp @@ -0,0 +1,169 @@ +/*************************************************************************** + * Copyright (C) 2008 by Sindre Aam�s * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#include "state_osd_elements.h" +#include "bitmap_font.h" +#include "statesaver.h" +#include +#include + +using namespace BitmapFont; + +static const char stateLoadedTxt[] = { S,t,a,t,e,SPC,N0,SPC,l,o,a,d,e,d,0 }; +static const char stateSavedTxt[] = { S,t,a,t,e,SPC,N0,SPC,s,a,v,e,d,0 }; +static const unsigned stateLoadedTxtWidth = getWidth(stateLoadedTxt); +static const unsigned stateSavedTxtWidth = getWidth(stateSavedTxt); + +class ShadedTextOsdElment : public OsdElement { + struct ShadeFill { + void operator()(Gambatte::uint_least32_t *dest, const unsigned pitch) { + dest[2] = dest[1] = dest[0] = 0x000000ul; + dest += pitch; + dest[2] = dest[0] = 0x000000ul; + dest += pitch; + dest[2] = dest[1] = dest[0] = 0x000000ul; + } + }; + + Gambatte::uint_least32_t *const pixels; + unsigned life; + +public: + ShadedTextOsdElment(unsigned w, const char *txt); + ~ShadedTextOsdElment(); + const Gambatte::uint_least32_t* update(); +}; + +ShadedTextOsdElment::ShadedTextOsdElment(unsigned width, const char *txt) : +OsdElement(MAX_WIDTH, 144 - HEIGHT - HEIGHT, width + 2, HEIGHT + 2, THREE_FOURTHS), +pixels(new Gambatte::uint_least32_t[w() * h()]), +life(4 * 60) { + std::memset(pixels, 0xFF, w() * h() * sizeof(Gambatte::uint_least32_t)); + + /*print(pixels + 0 * w() + 0, w(), 0x000000ul, txt); + print(pixels + 0 * w() + 1, w(), 0x000000ul, txt); + print(pixels + 0 * w() + 2, w(), 0x000000ul, txt); + print(pixels + 1 * w() + 0, w(), 0x000000ul, txt); + print(pixels + 1 * w() + 2, w(), 0x000000ul, txt); + print(pixels + 2 * w() + 0, w(), 0x000000ul, txt); + print(pixels + 2 * w() + 1, w(), 0x000000ul, txt); + print(pixels + 2 * w() + 2, w(), 0x000000ul, txt); + print(pixels + 1 * w() + 1, w(), 0xE0E0E0ul, txt);*/ + + print(pixels, w(), ShadeFill(), txt); + print(pixels + 1 * w() + 1, w(), 0xE0E0E0ul, txt); +} + +ShadedTextOsdElment::~ShadedTextOsdElment() { + delete []pixels; +} + +const Gambatte::uint_least32_t* ShadedTextOsdElment::update() { + if (life--) + return pixels; + + return 0; +} + +/*class FramedTextOsdElment : public OsdElement { + Gambatte::uint_least32_t *const pixels; + unsigned life; + +public: + FramedTextOsdElment(unsigned w, const char *txt); + ~FramedTextOsdElment(); + const Gambatte::uint_least32_t* update(); +}; + +FramedTextOsdElment::FramedTextOsdElment(unsigned width, const char *txt) : +OsdElement(NUMBER_WIDTH, 144 - HEIGHT * 2 - HEIGHT / 2, width + NUMBER_WIDTH * 2, HEIGHT * 2), +pixels(new Gambatte::uint_least32_t[w() * h()]), +life(4 * 60) { + std::memset(pixels, 0x00, w() * h() * sizeof(Gambatte::uint_least32_t)); + print(pixels + (w() - width) / 2 + ((h() - HEIGHT) / 2) * w(), w(), 0xA0A0A0ul, txt); +} + +FramedTextOsdElment::~FramedTextOsdElment() { + delete []pixels; +} + +const Gambatte::uint_least32_t* FramedTextOsdElment::update() { + if (life--) + return pixels; + + return 0; +}*/ + +std::auto_ptr newStateLoadedOsdElement(unsigned stateNo) { + char txt[sizeof(stateLoadedTxt)]; + + std::memcpy(txt, stateLoadedTxt, sizeof(stateLoadedTxt)); + utoa(stateNo, txt + 6); + + return std::auto_ptr(new ShadedTextOsdElment(stateLoadedTxtWidth, txt)); +} + +std::auto_ptr newStateSavedOsdElement(unsigned stateNo) { + char txt[sizeof(stateSavedTxt)]; + + std::memcpy(txt, stateSavedTxt, sizeof(stateSavedTxt)); + utoa(stateNo, txt + 6); + + return std::auto_ptr(new ShadedTextOsdElment(stateSavedTxtWidth, txt)); +} + +class SaveStateOsdElement : public OsdElement { + Gambatte::uint_least32_t pixels[StateSaver::SS_WIDTH * StateSaver::SS_HEIGHT]; + unsigned life; + +public: + SaveStateOsdElement(const char *fileName, unsigned stateNo); + const Gambatte::uint_least32_t* update(); +}; + +SaveStateOsdElement::SaveStateOsdElement(const char *fileName, unsigned stateNo) : +OsdElement((stateNo ? stateNo - 1 : 9) * ((160 - StateSaver::SS_WIDTH) / 10) + ((160 - StateSaver::SS_WIDTH) / 10) / 2, 4, StateSaver::SS_WIDTH, StateSaver::SS_HEIGHT), +life(4 * 60) { + std::ifstream file(fileName, std::ios_base::binary); + + if (file.is_open()) { + file.ignore(5); + file.read(reinterpret_cast(pixels), sizeof(pixels)); + } else { + std::memset(pixels, 0, sizeof(pixels)); + + { + using namespace BitmapFont; + + static const char txt[] = { E,m,p,t,BitmapFont::y,0 }; + + print(pixels + 3 + (StateSaver::SS_HEIGHT / 2 - BitmapFont::HEIGHT / 2) * StateSaver::SS_WIDTH, StateSaver::SS_WIDTH, 0x808080ul, txt); + } + } +} + +const Gambatte::uint_least32_t* SaveStateOsdElement::update() { + if (life--) + return pixels; + + return 0; +} + +std::auto_ptr newSaveStateOsdElement(const char *fileName, unsigned stateNo) { + return std::auto_ptr(new SaveStateOsdElement(fileName, stateNo)); +} diff --git a/supergameboy/libgambatte/src/state_osd_elements.h b/supergameboy/libgambatte/src/state_osd_elements.h new file mode 100644 index 00000000..c10344d2 --- /dev/null +++ b/supergameboy/libgambatte/src/state_osd_elements.h @@ -0,0 +1,29 @@ +/*************************************************************************** + * Copyright (C) 2008 by Sindre Aamås * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#ifndef STATE_OSD_ELEMENTS_H +#define STATE_OSD_ELEMENTS_H + +#include "osd_element.h" +#include + +std::auto_ptr newStateLoadedOsdElement(unsigned stateNo); +std::auto_ptr newStateSavedOsdElement(unsigned stateNo); +std::auto_ptr newSaveStateOsdElement(const char *fileName, unsigned stateNo); + +#endif diff --git a/supergameboy/libgambatte/src/statesaver.cpp b/supergameboy/libgambatte/src/statesaver.cpp new file mode 100644 index 00000000..c157129d --- /dev/null +++ b/supergameboy/libgambatte/src/statesaver.cpp @@ -0,0 +1,407 @@ +/*************************************************************************** + * Copyright (C) 2008 by Sindre Aamås * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#include "statesaver.h" +#include "savestate.h" +#include "array.h" +#include +#include +#include +#include + +enum AsciiChar { + NUL, SOH, STX, ETX, EOT, ENQ, ACK, BEL, BS, TAB, LF, VT, FF, CR, SO, SI, + DLE, DC1, DC2, DC3, DC4, NAK, SYN, ETB, CAN, EM, SUB, ESC, FS, GS, RS, US, + SP, XCL, QOT, HSH, DLR, PRC, AMP, APO, LPA, RPA, AST, PLU, COM, HYP, STP, DIV, + NO0, NO1, NO2, NO3, NO4, NO5, NO6, NO7, NO8, NO9, CLN, SCL, LT, EQL, GT, QTN, + AT, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, + P, Q, R, S, T, U, V, W, X, Y, Z, LBX, BSL, RBX, CAT, UND, + ACN, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, + p, q, r, s, t, u, v, w, x, y, z, LBR, BAR, RBR, TLD, DEL +}; + +struct Saver { + const char *label; + void (*save)(std::ofstream &file, const SaveState &state); + void (*load)(std::ifstream &file, SaveState &state); + unsigned char labelsize; +}; + +static inline bool operator<(const Saver &l, const Saver &r) { + return std::strcmp(l.label, r.label) < 0; +} + +static void put24(std::ofstream &file, const unsigned long data) { + file.put(data >> 16 & 0xFF); + file.put(data >> 8 & 0xFF); + file.put(data & 0xFF); +} + +static void put32(std::ofstream &file, const unsigned long data) { + file.put(data >> 24 & 0xFF); + file.put(data >> 16 & 0xFF); + file.put(data >> 8 & 0xFF); + file.put(data & 0xFF); +} + +static void write(std::ofstream &file, const unsigned char data) { + static const char inf[] = { 0x00, 0x00, 0x01 }; + + file.write(inf, sizeof(inf)); + file.put(data & 0xFF); +} + +static void write(std::ofstream &file, const unsigned short data) { + static const char inf[] = { 0x00, 0x00, 0x02 }; + + file.write(inf, sizeof(inf)); + file.put(data >> 8 & 0xFF); + file.put(data & 0xFF); +} + +static void write(std::ofstream &file, const unsigned long data) { + static const char inf[] = { 0x00, 0x00, 0x04 }; + + file.write(inf, sizeof(inf)); + put32(file, data); +} + +static inline void write(std::ofstream &file, const bool data) { + write(file, static_cast(data)); +} + +static void write(std::ofstream &file, const unsigned char *data, const unsigned long sz) { + put24(file, sz); + file.write(reinterpret_cast(data), sz); +} + +static void write(std::ofstream &file, const bool *data, const unsigned long sz) { + put24(file, sz); + + for (unsigned long i = 0; i < sz; ++i) + file.put(data[i]); +} + +static unsigned long get24(std::ifstream &file) { + unsigned long tmp = file.get() & 0xFF; + + tmp = tmp << 8 | (file.get() & 0xFF); + + return tmp << 8 | (file.get() & 0xFF); +} + +static unsigned long read(std::ifstream &file) { + unsigned long size = get24(file); + + if (size > 4) { + file.ignore(size - 4); + size = 4; + } + + unsigned long out = 0; + + switch (size) { + case 4: out = (out | (file.get() & 0xFF)) << 8; + case 3: out = (out | (file.get() & 0xFF)) << 8; + case 2: out = (out | (file.get() & 0xFF)) << 8; + case 1: out = out | (file.get() & 0xFF); + } + + return out; +} + +static inline void read(std::ifstream &file, unsigned char &data) { + data = read(file) & 0xFF; +} + +static inline void read(std::ifstream &file, unsigned short &data) { + data = read(file) & 0xFFFF; +} + +static inline void read(std::ifstream &file, unsigned long &data) { + data = read(file); +} + +static inline void read(std::ifstream &file, bool &data) { + data = read(file); +} + +static void read(std::ifstream &file, unsigned char *data, unsigned long sz) { + const unsigned long size = get24(file); + + if (size < sz) + sz = size; + + file.read(reinterpret_cast(data), sz); + file.ignore(size - sz); + + if (static_cast(0x100)) { + for (unsigned long i = 0; i < sz; ++i) + data[i] &= 0xFF; + } +} + +static void read(std::ifstream &file, bool *data, unsigned long sz) { + const unsigned long size = get24(file); + + if (size < sz) + sz = size; + + for (unsigned long i = 0; i < sz; ++i) + data[i] = file.get(); + + file.ignore(size - sz); +} + +class SaverList { +public: + typedef std::vector list_t; + typedef list_t::const_iterator const_iterator; + +private: + list_t list; + unsigned char maxLabelsize_; + +public: + SaverList(); + const_iterator begin() const { return list.begin(); } + const_iterator end() const { return list.end(); } + unsigned maxLabelsize() const { return maxLabelsize_; } +}; + +SaverList::SaverList() { +#define ADD(arg) do { \ + struct Func { \ + static void save(std::ofstream &file, const SaveState &state) { write(file, state.arg); } \ + static void load(std::ifstream &file, SaveState &state) { read(file, state.arg); } \ + }; \ + \ + Saver saver = { label, Func::save, Func::load, sizeof label }; \ + list.push_back(saver); \ +} while (0) + +#define ADDPTR(arg) do { \ + struct Func { \ + static void save(std::ofstream &file, const SaveState &state) { write(file, state.arg.get(), state.arg.getSz()); } \ + static void load(std::ifstream &file, SaveState &state) { read(file, state.arg.ptr, state.arg.getSz()); } \ + }; \ + \ + Saver saver = { label, Func::save, Func::load, sizeof label }; \ + list.push_back(saver); \ +} while (0) + + { static const char label[] = { c,c, NUL }; ADD(cpu.cycleCounter); } + { static const char label[] = { p,c, NUL }; ADD(cpu.PC); } + { static const char label[] = { s,p, NUL }; ADD(cpu.SP); } + { static const char label[] = { a, NUL }; ADD(cpu.A); } + { static const char label[] = { b, NUL }; ADD(cpu.B); } + { static const char label[] = { c, NUL }; ADD(cpu.C); } + { static const char label[] = { d, NUL }; ADD(cpu.D); } + { static const char label[] = { e, NUL }; ADD(cpu.E); } + { static const char label[] = { f, NUL }; ADD(cpu.F); } + { static const char label[] = { h, NUL }; ADD(cpu.H); } + { static const char label[] = { l, NUL }; ADD(cpu.L); } + { static const char label[] = { s,k,i,p, NUL }; ADD(cpu.skip); } + { static const char label[] = { h,a,l,t, NUL }; ADD(cpu.halted); } + { static const char label[] = { v,r,a,m, NUL }; ADDPTR(mem.vram); } + { static const char label[] = { s,r,a,m, NUL }; ADDPTR(mem.sram); } + { static const char label[] = { w,r,a,m, NUL }; ADDPTR(mem.wram); } + { static const char label[] = { h,r,a,m, NUL }; ADDPTR(mem.ioamhram); } + { static const char label[] = { l,d,i,v,u,p, NUL }; ADD(mem.div_lastUpdate); } + { static const char label[] = { l,t,i,m,a,u,p, NUL }; ADD(mem.tima_lastUpdate); } + { static const char label[] = { t,m,a,t,i,m,e, NUL }; ADD(mem.tmatime); } + { static const char label[] = { s,e,r,i,a,l,t, NUL }; ADD(mem.next_serialtime); } + { static const char label[] = { l,o,d,m,a,u,p, NUL }; ADD(mem.lastOamDmaUpdate); } + { static const char label[] = { m,i,n,i,n,t,t, NUL }; ADD(mem.minIntTime); } + { static const char label[] = { r,o,m,b,a,n,k, NUL }; ADD(mem.rombank); } + { static const char label[] = { d,m,a,s,r,c, NUL }; ADD(mem.dmaSource); } + { static const char label[] = { d,m,a,d,s,t, NUL }; ADD(mem.dmaDestination); } + { static const char label[] = { r,a,m,b,a,n,k, NUL }; ADD(mem.rambank); } + { static const char label[] = { o,d,m,a,p,o,s, NUL }; ADD(mem.oamDmaPos); } + { static const char label[] = { i,m,e, NUL }; ADD(mem.IME); } + { static const char label[] = { s,r,a,m,o,n, NUL }; ADD(mem.enable_ram); } + { static const char label[] = { r,a,m,b,m,o,d, NUL }; ADD(mem.rambank_mode); } + { static const char label[] = { h,d,m,a, NUL }; ADD(mem.hdma_transfer); } + { static const char label[] = { b,g,p, NUL }; ADDPTR(ppu.bgpData); } + { static const char label[] = { o,b,j,p, NUL }; ADDPTR(ppu.objpData); } + { static const char label[] = { s,p,o,s,b,u,f, NUL }; ADDPTR(ppu.oamReaderBuf); } + { static const char label[] = { s,p,s,z,b,u,f, NUL }; ADDPTR(ppu.oamReaderSzbuf); } + { static const char label[] = { v,c,y,c,l,e,s, NUL }; ADD(ppu.videoCycles); } + { static const char label[] = { e,d,M,NO0,t,i,m, NUL }; ADD(ppu.enableDisplayM0Time); } + { static const char label[] = { w,i,n,y,p,o,s, NUL }; ADD(ppu.winYPos); } + { static const char label[] = { d,r,a,w,c,y,c, NUL }; ADD(ppu.drawStartCycle); } + { static const char label[] = { s,c,r,d,c,y,c, NUL }; ADD(ppu.scReadOffset); } + { static const char label[] = { l,c,d,c, NUL }; ADD(ppu.lcdc); } + { static const char label[] = { s,c,x,NO0, NUL }; ADD(ppu.scx[0]); } + { static const char label[] = { s,c,x,NO1, NUL }; ADD(ppu.scx[1]); } + { static const char label[] = { s,c,y,NO0, NUL }; ADD(ppu.scy[0]); } + { static const char label[] = { s,c,y,NO1, NUL }; ADD(ppu.scy[1]); } + { static const char label[] = { s,c,x,AMP,NO7, NUL }; ADD(ppu.scxAnd7); } + { static const char label[] = { w,e,m,a,s,t,r, NUL }; ADD(ppu.weMaster); } + { static const char label[] = { w,x, NUL }; ADD(ppu.wx); } + { static const char label[] = { w,y, NUL }; ADD(ppu.wy); } + { static const char label[] = { l,y,c,s,k,i,p, NUL }; ADD(ppu.lycIrqSkip); } + { static const char label[] = { s,p,u,c,n,t,r, NUL }; ADD(spu.cycleCounter); } + { static const char label[] = { s,w,p,c,n,t,r, NUL }; ADD(spu.ch1.sweep.counter); } + { static const char label[] = { s,w,p,s,h,d,w, NUL }; ADD(spu.ch1.sweep.shadow); } + { static const char label[] = { s,w,p,n,e,g, NUL }; ADD(spu.ch1.sweep.negging); } + { static const char label[] = { d,u,t,NO1,c,t,r, NUL }; ADD(spu.ch1.duty.nextPosUpdate); } + { static const char label[] = { d,u,t,NO1,p,o,s, NUL }; ADD(spu.ch1.duty.pos); } + { static const char label[] = { e,n,v,NO1,c,t,r, NUL }; ADD(spu.ch1.env.counter); } + { static const char label[] = { e,n,v,NO1,v,o,l, NUL }; ADD(spu.ch1.env.volume); } + { static const char label[] = { l,e,n,NO1,c,t,r, NUL }; ADD(spu.ch1.lcounter.counter); } + { static const char label[] = { l,e,n,NO1,v,a,l, NUL }; ADD(spu.ch1.lcounter.lengthCounter); } + { static const char label[] = { n,r,NO1,NO0, NUL }; ADD(spu.ch1.sweep.nr0); } + { static const char label[] = { n,r,NO1,NO3, NUL }; ADD(spu.ch1.duty.nr3); } + { static const char label[] = { n,r,NO1,NO4, NUL }; ADD(spu.ch1.nr4); } + { static const char label[] = { c,NO1,m,a,s,t,r, NUL }; ADD(spu.ch1.master); } + { static const char label[] = { d,u,t,NO2,c,t,r, NUL }; ADD(spu.ch2.duty.nextPosUpdate); } + { static const char label[] = { d,u,t,NO2,p,o,s, NUL }; ADD(spu.ch2.duty.pos); } + { static const char label[] = { e,n,v,NO2,c,t,r, NUL }; ADD(spu.ch2.env.counter); } + { static const char label[] = { e,n,v,NO2,v,o,l, NUL }; ADD(spu.ch2.env.volume); } + { static const char label[] = { l,e,n,NO2,c,t,r, NUL }; ADD(spu.ch2.lcounter.counter); } + { static const char label[] = { l,e,n,NO2,v,a,l, NUL }; ADD(spu.ch2.lcounter.lengthCounter); } + { static const char label[] = { n,r,NO2,NO3, NUL }; ADD(spu.ch2.duty.nr3); } + { static const char label[] = { n,r,NO2,NO4, NUL }; ADD(spu.ch2.nr4); } + { static const char label[] = { c,NO2,m,a,s,t,r, NUL }; ADD(spu.ch2.master); } + { static const char label[] = { w,a,v,e,r,a,m, NUL }; ADDPTR(spu.ch3.waveRam); } + { static const char label[] = { l,e,n,NO3,c,t,r, NUL }; ADD(spu.ch3.lcounter.counter); } + { static const char label[] = { l,e,n,NO3,v,a,l, NUL }; ADD(spu.ch3.lcounter.lengthCounter); } + { static const char label[] = { w,a,v,e,c,t,r, NUL }; ADD(spu.ch3.waveCounter); } + { static const char label[] = { l,w,a,v,r,d,t, NUL }; ADD(spu.ch3.lastReadTime); } + { static const char label[] = { w,a,v,e,p,o,s, NUL }; ADD(spu.ch3.wavePos); } + { static const char label[] = { w,a,v,s,m,p,l, NUL }; ADD(spu.ch3.sampleBuf); } + { static const char label[] = { n,r,NO3,NO3, NUL }; ADD(spu.ch3.nr3); } + { static const char label[] = { n,r,NO3,NO4, NUL }; ADD(spu.ch3.nr4); } + { static const char label[] = { c,NO3,m,a,s,t,r, NUL }; ADD(spu.ch3.master); } + { static const char label[] = { l,f,s,r,c,t,r, NUL }; ADD(spu.ch4.lfsr.counter); } + { static const char label[] = { l,f,s,r,r,e,g, NUL }; ADD(spu.ch4.lfsr.reg); } + { static const char label[] = { e,n,v,NO4,c,t,r, NUL }; ADD(spu.ch4.env.counter); } + { static const char label[] = { e,n,v,NO4,v,o,l, NUL }; ADD(spu.ch4.env.volume); } + { static const char label[] = { l,e,n,NO4,c,t,r, NUL }; ADD(spu.ch4.lcounter.counter); } + { static const char label[] = { l,e,n,NO4,v,a,l, NUL }; ADD(spu.ch4.lcounter.lengthCounter); } + { static const char label[] = { n,r,NO4,NO4, NUL }; ADD(spu.ch4.nr4); } + { static const char label[] = { c,NO4,m,a,s,t,r, NUL }; ADD(spu.ch4.master); } + { static const char label[] = { r,t,c,b,a,s,e, NUL }; ADD(rtc.baseTime); } + { static const char label[] = { r,t,c,h,a,l,t, NUL }; ADD(rtc.haltTime); } + { static const char label[] = { r,t,c,i,n,d,x, NUL }; ADD(rtc.index); } + { static const char label[] = { r,t,c,d,h, NUL }; ADD(rtc.dataDh); } + { static const char label[] = { r,t,c,d,l, NUL }; ADD(rtc.dataDl); } + { static const char label[] = { r,t,c,h, NUL }; ADD(rtc.dataH); } + { static const char label[] = { r,t,c,m, NUL }; ADD(rtc.dataM); } + { static const char label[] = { r,t,c,s, NUL }; ADD(rtc.dataS); } + { static const char label[] = { r,t,c,l,l,d, NUL }; ADD(rtc.lastLatchData); } + +#undef ADD +#undef ADDPTR +#undef ADDTIME + + list.resize(list.size()); + std::sort(list.begin(), list.end()); + + maxLabelsize_ = 0; + + for (std::size_t i = 0; i < list.size(); ++i) { + if (list[i].labelsize > maxLabelsize_) + maxLabelsize_ = list[i].labelsize; + } +} + +static void writeSnapShot(std::ofstream &file, const Gambatte::uint_least32_t *pixels, const unsigned pitch) { + put24(file, pixels ? StateSaver::SS_WIDTH * StateSaver::SS_HEIGHT * sizeof(Gambatte::uint_least32_t) : 0); + + if (pixels) { + Gambatte::uint_least32_t buf[StateSaver::SS_WIDTH]; + + for (unsigned h = StateSaver::SS_HEIGHT; h--;) { + for (unsigned x = 0; x < StateSaver::SS_WIDTH; ++x) { + unsigned long rb = 0; + unsigned long g = 0; + + static const unsigned w[StateSaver::SS_DIV] = { 3, 5, 5, 3 }; + + for (unsigned y = 0; y < StateSaver::SS_DIV; ++y) + for (unsigned xx = 0; xx < StateSaver::SS_DIV; ++xx) { + rb += (pixels[x * StateSaver::SS_DIV + y * pitch + xx] & 0xFF00FF) * w[y] * w[xx]; + g += (pixels[x * StateSaver::SS_DIV + y * pitch + xx] & 0x00FF00) * w[y] * w[xx]; + } + + buf[x] = (rb >> 8 & 0xFF00FF) | (g >> 8 & 0x00FF00); + } + + file.write(reinterpret_cast(buf), sizeof(buf)); + pixels += pitch * StateSaver::SS_DIV; + } + } +} + +static SaverList list; + +void StateSaver::saveState(const SaveState &state, const char *filename) { + std::ofstream file(filename, std::ios_base::binary); + + if (file.fail()) + return; + + { static const char ver[] = { 0, 0 }; file.write(ver, sizeof(ver)); } + + writeSnapShot(file, state.ppu.drawBuffer.get(), state.ppu.drawBuffer.getSz() / 144); + + for (SaverList::const_iterator it = list.begin(); it != list.end(); ++it) { + file.write(it->label, it->labelsize); + (*it->save)(file, state); + } +} + +bool StateSaver::loadState(SaveState &state, const char *filename) { + std::ifstream file(filename, std::ios_base::binary); + + if (file.fail() || file.get() != 0) + return false; + + file.ignore(); + file.ignore(get24(file)); + + Array labelbuf(list.maxLabelsize()); + const Saver labelbufSaver = { label: labelbuf, save: 0, load: 0, labelsize: (unsigned char)list.maxLabelsize() }; + + SaverList::const_iterator done = list.begin(); + + while (file.good() && done != list.end()) { + file.getline(labelbuf, list.maxLabelsize(), NUL); + + SaverList::const_iterator it = done; + + if (std::strcmp(labelbuf, it->label)) { + it = std::lower_bound(it + 1, list.end(), labelbufSaver); + + if (it == list.end() || std::strcmp(labelbuf, it->label)) { + file.ignore(get24(file)); + continue; + } + } else + ++done; + + (*it->load)(file, state); + } + + state.cpu.cycleCounter &= 0x7FFFFFFF; + state.spu.cycleCounter &= 0x7FFFFFFF; + + return true; +} diff --git a/supergameboy/libgambatte/src/statesaver.h b/supergameboy/libgambatte/src/statesaver.h new file mode 100644 index 00000000..ea9ce8b3 --- /dev/null +++ b/supergameboy/libgambatte/src/statesaver.h @@ -0,0 +1,37 @@ +/*************************************************************************** + * Copyright (C) 2008 by Sindre Aamås * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#ifndef STATESAVER_H +#define STATESAVER_H + +class SaveState; + +class StateSaver { + StateSaver(); + +public: + enum { SS_SHIFT = 2 }; + enum { SS_DIV = 1 << 2 }; + enum { SS_WIDTH = 160 >> SS_SHIFT }; + enum { SS_HEIGHT = 144 >> SS_SHIFT }; + + static void saveState(const SaveState &state, const char *filename); + static bool loadState(SaveState &state, const char *filename); +}; + +#endif diff --git a/supergameboy/libgambatte/src/video.cpp b/supergameboy/libgambatte/src/video.cpp new file mode 100644 index 00000000..875afa43 --- /dev/null +++ b/supergameboy/libgambatte/src/video.cpp @@ -0,0 +1,1474 @@ +/*************************************************************************** + * Copyright (C) 2007 by Sindre Aam�s * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#include "video.h" +#include "videoblitter.h" +#include "video/filters/filter.h" +#include "video/filters/catrom2x.h" +#include "video/filters/catrom3x.h" +#include "video/filters/kreed2xsai.h" +#include "video/filters/maxsthq2x.h" +#include "video/filters/maxsthq3x.h" +#include "filterinfo.h" +#include "savestate.h" +#include "video/basic_add_event.h" +#include +#include + +static void addEventIfActivated(event_queue &q, VideoEvent *const e, const unsigned long newTime) { + e->setTime(newTime); + + if (newTime != VideoEvent::DISABLED_TIME) + q.push(e); +} + +void LCD::setDmgPalette(unsigned long *const palette, const unsigned long *const dmgColors, const unsigned data) { + palette[0] = dmgColors[data & 3]; + palette[1] = dmgColors[data >> 2 & 3]; + palette[2] = dmgColors[data >> 4 & 3]; + palette[3] = dmgColors[data >> 6 & 3]; +} + +unsigned long LCD::gbcToRgb32(const unsigned bgr15) { + const unsigned long r = bgr15 & 0x1F; + const unsigned long g = bgr15 >> 5 & 0x1F; + const unsigned long b = bgr15 >> 10 & 0x1F; + + return ((r * 13 + g * 2 + b) >> 1) << 16 | ((g * 3 + b) << 9) | ((r * 3 + g * 2 + b * 11) >> 1); +} + +unsigned long LCD::gbcToRgb16(const unsigned bgr15) { + const unsigned r = bgr15 & 0x1F; + const unsigned g = bgr15 >> 5 & 0x1F; + const unsigned b = bgr15 >> 10 & 0x1F; + + return (((r * 13 + g * 2 + b + 8) << 7) & 0xF800) | ((g * 3 + b + 1) >> 1) << 5 | ((r * 3 + g * 2 + b * 11 + 8) >> 4); +} + +unsigned long LCD::gbcToUyvy(const unsigned bgr15) { + const unsigned r5 = bgr15 & 0x1F; + const unsigned g5 = bgr15 >> 5 & 0x1F; + const unsigned b5 = bgr15 >> 10 & 0x1F; + + // y = (r5 * 926151 + g5 * 1723530 + b5 * 854319) / 510000 + 16; + // u = (b5 * 397544 - r5 * 68824 - g5 * 328720) / 225930 + 128; + // v = (r5 * 491176 - g5 * 328720 - b5 * 162456) / 178755 + 128; + + const unsigned long y = (r5 * 116 + g5 * 216 + b5 * 107 + 16 * 64 + 32) >> 6; + const unsigned long u = (b5 * 225 - r5 * 39 - g5 * 186 + 128 * 128 + 64) >> 7; + const unsigned long v = (r5 * 176 - g5 * 118 - b5 * 58 + 128 * 64 + 32) >> 6; + +#ifdef WORDS_BIGENDIAN + return u << 24 | y << 16 | v << 8 | y; +#else + return y << 24 | v << 16 | y << 8 | u; +#endif +} + +LCD::LCD(const unsigned char *const oamram, const unsigned char *const vram_in) : + vram(vram_in), + bgTileData(vram), + bgTileMap(vram + 0x1800), + wdTileMap(bgTileMap), + vBlitter(NULL), + filter(NULL), + dbuffer(NULL), + draw(NULL), + gbcToFormat(gbcToRgb32), + dmgColors(dmgColorsRgb32), + lastUpdate(0), + videoCycles(0), + dpitch(0), + winYPos(0), + m3EventQueue(11, VideoEventComparer()), + irqEventQueue(4, VideoEventComparer()), + vEventQueue(5, VideoEventComparer()), + win(m3EventQueue, lyCounter, m3ExtraCycles), + scxReader(m3EventQueue, /*wyReg.reader3(),*/ win.wxReader, win.we.enableChecker(), win.we.disableChecker(), m3ExtraCycles), + spriteMapper(m3ExtraCycles, lyCounter, oamram), + m3ExtraCycles(spriteMapper, scxReader, win), + breakEvent(drawStartCycle, scReadOffset), + mode3Event(m3EventQueue, vEventQueue, mode0Irq, irqEvent), + lycIrq(ifReg), + mode0Irq(lyCounter, lycIrq, m3ExtraCycles, ifReg), + mode1Irq(ifReg), + mode2Irq(lyCounter, lycIrq, ifReg), + irqEvent(irqEventQueue), + drawStartCycle(90), + scReadOffset(90), + ifReg(0), + tileIndexSign(0), + statReg(0), + doubleSpeed(false), + enabled(false), + cgb(false), + bgEnable(false), + spriteEnable(false) +{ + std::memset(bgpData, 0, sizeof(bgpData)); + std::memset(objpData, 0, sizeof(objpData)); + + for (unsigned i = 0; i < sizeof(dmgColorsRgb32) / sizeof(unsigned long); ++i) { + setDmgPaletteColor(i, (3 - (i & 3)) * 85 * 0x010101); + } + + filters.push_back(NULL); + filters.push_back(new Catrom2x); + filters.push_back(new Catrom3x); + filters.push_back(new Kreed_2xSaI); + filters.push_back(new MaxSt_Hq2x); + filters.push_back(new MaxSt_Hq3x); + + reset(oamram, false); + setDoubleSpeed(false); + + setVideoFilter(0); +} + +LCD::~LCD() { +// delete []filter_buffer; + for (std::size_t i = 0; i < filters.size(); ++i) + delete filters[i]; +} + +void LCD::reset(const unsigned char *const oamram, const bool cgb_in) { + cgb = cgb_in; + spriteMapper.reset(oamram, cgb_in); + setDBuffer(); +} + +void LCD::resetVideoState(const unsigned long cycleCounter) { + vEventQueue.clear(); + m3EventQueue.clear(); + irqEventQueue.clear(); + + lyCounter.reset(videoCycles, lastUpdate); + vEventQueue.push(&lyCounter); + + spriteMapper.resetVideoState(); + m3ExtraCycles.invalidateCache(); + + addEventIfActivated(m3EventQueue, &scxReader, ScxReader::schedule(lyCounter, cycleCounter)); + addEventIfActivated(m3EventQueue, &win.wxReader, WxReader::schedule(scxReader.scxAnd7(), lyCounter, win.wxReader, cycleCounter)); + addEventIfActivated(m3EventQueue, &win.wyReg.reader1(), Wy::WyReader1::schedule(lyCounter, cycleCounter)); + addEventIfActivated(m3EventQueue, &win.wyReg.reader2(), Wy::WyReader2::schedule(lyCounter, cycleCounter)); + addEventIfActivated(m3EventQueue, &win.wyReg.reader3(), Wy::WyReader3::schedule(win.wxReader.getSource(), scxReader, lyCounter, cycleCounter)); + addEventIfActivated(m3EventQueue, &win.wyReg.reader4(), Wy::WyReader4::schedule(lyCounter, cycleCounter)); + addEventIfActivated(m3EventQueue, &spriteMapper, SpriteMapper::schedule(lyCounter, cycleCounter)); + addEventIfActivated(m3EventQueue, &win.we.enableChecker(), We::WeEnableChecker::schedule(scxReader.scxAnd7(), win.wxReader.wx(), lyCounter, cycleCounter)); + addEventIfActivated(m3EventQueue, &win.we.disableChecker(), We::WeDisableChecker::schedule(scxReader.scxAnd7(), win.wxReader.wx(), lyCounter, cycleCounter)); + addEventIfActivated(m3EventQueue, &win.weMasterChecker, WeMasterChecker::schedule(win.wyReg.getSource(), win.we.getSource(), lyCounter, cycleCounter)); + + addEventIfActivated(irqEventQueue, &lycIrq, LycIrq::schedule(statReg, lycIrq.lycReg(), lyCounter, cycleCounter)); + addEventIfActivated(irqEventQueue, &mode0Irq, Mode0Irq::schedule(statReg, m3ExtraCycles, lyCounter, cycleCounter)); + addEventIfActivated(irqEventQueue, &mode1Irq, Mode1Irq::schedule(lyCounter, cycleCounter)); + addEventIfActivated(irqEventQueue, &mode2Irq, Mode2Irq::schedule(statReg, lyCounter, cycleCounter)); + + addEventIfActivated(vEventQueue, &mode3Event, Mode3Event::schedule(m3EventQueue)); + addEventIfActivated(vEventQueue, &irqEvent, IrqEvent::schedule(irqEventQueue)); + addEventIfActivated(vEventQueue, &scReader, ScReader::schedule(lastUpdate, videoCycles, scReadOffset, doubleSpeed)); + addEventIfActivated(vEventQueue, &breakEvent, BreakEvent::schedule(lyCounter)); +} + +void LCD::setDoubleSpeed(const bool ds) { + doubleSpeed = ds; + lyCounter.setDoubleSpeed(doubleSpeed); + scxReader.setDoubleSpeed(doubleSpeed); + win.wxReader.setDoubleSpeed(doubleSpeed); + scReader.setDoubleSpeed(doubleSpeed); + breakEvent.setDoubleSpeed(doubleSpeed); + lycIrq.setDoubleSpeed(doubleSpeed); + mode1Irq.setDoubleSpeed(doubleSpeed); +} + +void LCD::setStatePtrs(SaveState &state) { + state.ppu.drawBuffer.set(static_cast(dbuffer), dpitch * 144); + state.ppu.bgpData.set(bgpData, sizeof bgpData); + state.ppu.objpData.set(objpData, sizeof objpData); + spriteMapper.setStatePtrs(state); +} + +void LCD::saveState(SaveState &state) const { + state.ppu.videoCycles = videoCycles; + state.ppu.winYPos = winYPos; + state.ppu.drawStartCycle = drawStartCycle; + state.ppu.scReadOffset = scReadOffset; + state.ppu.lcdc = enabled << 7 | ((wdTileMap - vram - 0x1800) >> 4) | (tileIndexSign ^ 0x80) >> 3 | ((bgTileMap - vram - 0x1800) >> 7) | spriteEnable << 1 | bgEnable; + state.ppu.lycIrqSkip = lycIrq.skips(); + + spriteMapper.saveState(state); + scReader.saveState(state); + scxReader.saveState(state); + win.weMasterChecker.saveState(state); + win.wxReader.saveState(state); + win.wyReg.saveState(state); + win.we.saveState(state); +} + +void LCD::loadState(const SaveState &state, const unsigned char *oamram) { + statReg = state.mem.ioamhram.get()[0x141]; + ifReg = 0; + + setDoubleSpeed(cgb & state.mem.ioamhram.get()[0x14D] >> 7); + + lastUpdate = state.cpu.cycleCounter; + videoCycles = std::min(state.ppu.videoCycles, 70223ul); + winYPos = state.ppu.winYPos > 143 ? 0xFF : state.ppu.winYPos; + drawStartCycle = state.ppu.drawStartCycle; + scReadOffset = state.ppu.scReadOffset; + enabled = state.ppu.lcdc >> 7 & 1; + wdTileMap = vram + 0x1800 + (state.ppu.lcdc >> 6 & 1) * 0x400; + tileIndexSign = ((state.ppu.lcdc >> 4 & 1) ^ 1) * 0x80; + bgTileData = vram + ((state.ppu.lcdc >> 4 & 1) ^ 1) * 0x1000; + bgTileMap = vram + 0x1800 + (state.ppu.lcdc >> 3 & 1) * 0x400; + spriteEnable = state.ppu.lcdc >> 1 & 1; + bgEnable = state.ppu.lcdc & 1; + + lycIrq.setM2IrqEnabled(statReg >> 5 & 1); + lycIrq.setLycReg(state.mem.ioamhram.get()[0x145]); + lycIrq.setSkip(state.ppu.lycIrqSkip); + mode1Irq.setM1StatIrqEnabled(statReg >> 4 & 1); + + win.we.setSource(state.mem.ioamhram.get()[0x140] >> 5 & 1); + spriteMapper.setLargeSpritesSource(state.mem.ioamhram.get()[0x140] >> 2 & 1); + scReader.setScySource(state.mem.ioamhram.get()[0x142]); + scxReader.setSource(state.mem.ioamhram.get()[0x143]); + breakEvent.setScxSource(state.mem.ioamhram.get()[0x143]); + scReader.setScxSource(state.mem.ioamhram.get()[0x143]); + win.wyReg.setSource(state.mem.ioamhram.get()[0x14A]); + win.wxReader.setSource(state.mem.ioamhram.get()[0x14B]); + + spriteMapper.loadState(state); + scReader.loadState(state); + scxReader.loadState(state); + win.weMasterChecker.loadState(state); + win.wxReader.loadState(state); + win.wyReg.loadState(state); + win.we.loadState(state); + + resetVideoState(lastUpdate); + spriteMapper.oamChange(oamram, lastUpdate); + refreshPalettes(); +} + +void LCD::refreshPalettes() { + if (cgb) { + for (unsigned i = 0; i < 8 * 8; i += 2) { + bgPalette[i >> 1] = (*gbcToFormat)(bgpData[i] | bgpData[i + 1] << 8); + spPalette[i >> 1] = (*gbcToFormat)(objpData[i] | objpData[i + 1] << 8); + } + } else { + setDmgPalette(bgPalette, dmgColors, bgpData[0]); + setDmgPalette(spPalette, dmgColors + 4, objpData[0]); + setDmgPalette(spPalette + 4, dmgColors + 8, objpData[1]); + } +} + +void LCD::setVideoBlitter(Gambatte::VideoBlitter *vb) { + vBlitter = vb; + + if (vBlitter) { + vBlitter->setBufferDimensions(videoWidth(), videoHeight()); + pb = vBlitter->inBuffer(); + } + + setDBuffer(); +} + +void LCD::videoBufferChange() { + if (vBlitter) { + pb = vBlitter->inBuffer(); + setDBuffer(); + } +} + +void LCD::setVideoFilter(const unsigned n) { + const unsigned oldw = videoWidth(); + const unsigned oldh = videoHeight(); + + if (filter) + filter->outit(); + + filter = filters.at(n < filters.size() ? n : 0); + + if (filter) { + filter->init(); + } + + if (vBlitter && (oldw != videoWidth() || oldh != videoHeight())) { + vBlitter->setBufferDimensions(videoWidth(), videoHeight()); + pb = vBlitter->inBuffer(); + } + + setDBuffer(); +} + +std::vector LCD::filterInfo() const { + std::vector v; + + static Gambatte::FilterInfo noInfo = { "None", 160, 144 }; + v.push_back(&noInfo); + + for (std::size_t i = 1; i < filters.size(); ++i) + v.push_back(&filters[i]->info()); + + return v; +} + +unsigned int LCD::videoWidth() const { + return filter ? filter->info().outWidth : 160; +} + +unsigned int LCD::videoHeight() const { + return filter ? filter->info().outHeight : 144; +} + +template +static void blitOsdElement(Gambatte::uint_least32_t *d, const Gambatte::uint_least32_t *s, const unsigned width, unsigned h, const unsigned dpitch, Blend blend) { + while (h--) { + for (unsigned w = width; w--;) { + if (*s != 0xFFFFFFFF) + *d = blend(*s, *d); + + ++d; + ++s; + } + + d += dpitch - width; + } +} + +template +struct Blend { + enum { SW = weight - 1 }; + enum { LOWMASK = SW * 0x010101ul }; + Gambatte::uint_least32_t operator()(const Gambatte::uint_least32_t s, const Gambatte::uint_least32_t d) const { + return (s * SW + d - (((s & LOWMASK) * SW + (d & LOWMASK)) & LOWMASK)) / weight; + } +}; + +void LCD::updateScreen(const unsigned long cycleCounter) { + update(cycleCounter); + + if (pb.pixels) { + if (dbuffer && osdElement.get()) { + const Gambatte::uint_least32_t *s = osdElement->update(); + + if (s) { + Gambatte::uint_least32_t *d = static_cast(dbuffer) + osdElement->y() * dpitch + osdElement->x(); + + switch (osdElement->opacity()) { + case OsdElement::SEVEN_EIGHTHS: blitOsdElement(d, s, osdElement->w(), osdElement->h(), dpitch, Blend<8>()); break; + case OsdElement::THREE_FOURTHS: blitOsdElement(d, s, osdElement->w(), osdElement->h(), dpitch, Blend<4>()); break; + } + } else + osdElement.reset(); + } + + if (filter) { + filter->filter(static_cast(tmpbuf ? tmpbuf : pb.pixels), (tmpbuf ? videoWidth() : pb.pitch)); + } + + if (tmpbuf) { + switch (pb.format) { + case Gambatte::PixelBuffer::RGB16: + rgb32ToRgb16(tmpbuf, static_cast(pb.pixels), videoWidth(), videoHeight(), pb.pitch); + break; + case Gambatte::PixelBuffer::UYVY: + rgb32ToUyvy(tmpbuf, static_cast(pb.pixels), videoWidth(), videoHeight(), pb.pitch); + break; + default: break; + } + } + + if (vBlitter) + vBlitter->blit(); + } +} + +template +static void clear(T *buf, const unsigned long color, const unsigned dpitch) { + unsigned lines = 144; + + while (lines--) { + std::fill_n(buf, 160, color); + buf += dpitch; + } +} + +void LCD::enableChange(const unsigned long cycleCounter) { + update(cycleCounter); + enabled = !enabled; + + if (enabled) { + lycIrq.setSkip(false); + videoCycles = 0; + lastUpdate = cycleCounter; + winYPos = 0xFF; + win.weMasterChecker.unset(); + spriteMapper.enableDisplay(cycleCounter); + resetVideoState(cycleCounter); + } + + if (!enabled && dbuffer) { + const unsigned long color = cgb ? (*gbcToFormat)(0xFFFF) : dmgColors[0]; + + clear(static_cast(dbuffer), color, dpitch); + +// updateScreen(cycleCounter); + } +} + +//FIXME: needs testing +void LCD::lyWrite(const unsigned long cycleCounter) { + update(cycleCounter); + lycIrq.setSkip(false); + videoCycles = 0; + lastUpdate = cycleCounter; + winYPos = 0xFF; + win.weMasterChecker.unset(); + resetVideoState(cycleCounter); + +// if ((statReg & 0x40) && lycIrq.lycReg() == 0) +// ifReg |= 2; +} + +void LCD::preResetCounter(const unsigned long cycleCounter) { + preSpeedChange(cycleCounter); +} + +void LCD::postResetCounter(const unsigned long oldCC, const unsigned long cycleCounter) { + lastUpdate = cycleCounter - (oldCC - lastUpdate); + spriteMapper.resetCycleCounter(oldCC, cycleCounter); + resetVideoState(cycleCounter); +} + +void LCD::preSpeedChange(const unsigned long cycleCounter) { + update(cycleCounter); + spriteMapper.preCounterChange(cycleCounter); +} + +void LCD::postSpeedChange(const unsigned long cycleCounter) { + setDoubleSpeed(!doubleSpeed); + + resetVideoState(cycleCounter); +} + +bool LCD::isMode0IrqPeriod(const unsigned long cycleCounter) { + if (cycleCounter >= vEventQueue.top()->time()) + update(cycleCounter); + + const unsigned timeToNextLy = lyCounter.time() - cycleCounter; + + return /*memory.enable_display && */lyCounter.ly() < 144 && timeToNextLy <= (456U - (169 + doubleSpeed * 3U + 80 + m3ExtraCycles(lyCounter.ly()) + 1 - doubleSpeed)) << doubleSpeed && timeToNextLy > 4; +} + +bool LCD::isMode2IrqPeriod(const unsigned long cycleCounter) { + if (cycleCounter >= lyCounter.time()) + update(cycleCounter); + + const unsigned nextLy = lyCounter.time() - cycleCounter; + + return /*memory.enable_display && */lyCounter.ly() < 143 && nextLy <= 4; +} + +bool LCD::isLycIrqPeriod(const unsigned lycReg, const unsigned endCycles, const unsigned long cycleCounter) { + if (cycleCounter >= lyCounter.time()) + update(cycleCounter); + + const unsigned timeToNextLy = lyCounter.time() - cycleCounter; + + return (lyCounter.ly() == lycReg && timeToNextLy > endCycles) || (lycReg == 0 && lyCounter.ly() == 153 && timeToNextLy <= (456U - 8U) << doubleSpeed); +} + +bool LCD::isMode1IrqPeriod(const unsigned long cycleCounter) { + if (cycleCounter >= lyCounter.time()) + update(cycleCounter); + + const unsigned timeToNextLy = lyCounter.time() - cycleCounter; + + return lyCounter.ly() > 143 && (lyCounter.ly() < 153 || timeToNextLy > 4U - doubleSpeed * 4U); +} + +bool LCD::isHdmaPeriod(const unsigned long cycleCounter) { + if (cycleCounter >= vEventQueue.top()->time()) + update(cycleCounter); + + const unsigned timeToNextLy = lyCounter.time() - cycleCounter; + + return /*memory.enable_display && */lyCounter.ly() < 144 && timeToNextLy <= ((456U - (169U + doubleSpeed * 3U + 80U + m3ExtraCycles(lyCounter.ly()) + 2 - doubleSpeed)) << doubleSpeed) && timeToNextLy > 4; +} + +unsigned long LCD::nextHdmaTime(const unsigned long cycleCounter) { + if (cycleCounter >= vEventQueue.top()->time()) + update(cycleCounter); + + unsigned line = lyCounter.ly(); + int next = static_cast(169 + doubleSpeed * 3U + 80 + 2 - doubleSpeed) - static_cast(lyCounter.lineCycles(cycleCounter)); + + if (line < 144 && next + static_cast(m3ExtraCycles(line)) <= 0) { + next += 456; + ++line; + } + + if (line > 143) { + next += (154 - line) * 456; + line = 0; + } + + next += m3ExtraCycles(line); + + return cycleCounter + (static_cast(next) << doubleSpeed); +} + +bool LCD::vramAccessible(const unsigned long cycleCounter) { + if (cycleCounter >= vEventQueue.top()->time()) + update(cycleCounter); + + bool accessible = true; + + if (enabled && lyCounter.ly() < 144) { + const unsigned lineCycles = lyCounter.lineCycles(cycleCounter); + + if (lineCycles > 79 && lineCycles < 80 + 169 + doubleSpeed * 3U + m3ExtraCycles(lyCounter.ly())) + accessible = false; + } + + return accessible; +} + +bool LCD::cgbpAccessible(const unsigned long cycleCounter) { + if (cycleCounter >= vEventQueue.top()->time()) + update(cycleCounter); + + bool accessible = true; + + if (enabled && lyCounter.ly() < 144) { + const unsigned lineCycles = lyCounter.lineCycles(cycleCounter); + + if (lineCycles > 79U + doubleSpeed && lineCycles < 80U + 169U + doubleSpeed * 3U + m3ExtraCycles(lyCounter.ly()) + 4U - doubleSpeed * 2U) + accessible = false; + } + + return accessible; +} + +bool LCD::oamAccessible(const unsigned long cycleCounter) { + bool accessible = true; + + if (enabled) { + if (cycleCounter >= vEventQueue.top()->time()) + update(cycleCounter); + + accessible = spriteMapper.oamAccessible(cycleCounter); + } + + return accessible; +} + +void LCD::weChange(const bool newValue, const unsigned long cycleCounter) { + if (cycleCounter >= vEventQueue.top()->time()) + update(cycleCounter); + + win.we.setSource(newValue); + addFixedtimeEvent(m3EventQueue, &win.weMasterChecker, WeMasterChecker::schedule(win.wyReg.getSource(), newValue, lyCounter, cycleCounter)); + addFixedtimeEvent(m3EventQueue, &win.we.disableChecker(), We::WeDisableChecker::schedule(scxReader.scxAnd7(), win.wxReader.wx(), lyCounter, cycleCounter)); + addFixedtimeEvent(m3EventQueue, &win.we.enableChecker(), We::WeEnableChecker::schedule(scxReader.scxAnd7(), win.wxReader.wx(), lyCounter, cycleCounter)); + addUnconditionalEvent(vEventQueue, &mode3Event, Mode3Event::schedule(m3EventQueue)); +} + +void LCD::wxChange(const unsigned newValue, const unsigned long cycleCounter) { + if (cycleCounter >= vEventQueue.top()->time()) + update(cycleCounter); + + win.wxReader.setSource(newValue); + addEvent(m3EventQueue, &win.wxReader, WxReader::schedule(scxReader.scxAnd7(), lyCounter, win.wxReader, cycleCounter)); + + if (win.wyReg.reader3().time() != VideoEvent::DISABLED_TIME) + addEvent(m3EventQueue, &win.wyReg.reader3(), Wy::WyReader3::schedule(win.wxReader.getSource(), scxReader, lyCounter, cycleCounter)); + + addUnconditionalEvent(vEventQueue, &mode3Event, Mode3Event::schedule(m3EventQueue)); +} + +void LCD::wyChange(const unsigned newValue, const unsigned long cycleCounter) { + if (cycleCounter >= vEventQueue.top()->time()) + update(cycleCounter); + + win.wyReg.setSource(newValue); + addFixedtimeEvent(m3EventQueue, &win.wyReg.reader1(), Wy::WyReader1::schedule(lyCounter, cycleCounter)); + addFixedtimeEvent(m3EventQueue, &win.wyReg.reader2(), Wy::WyReader2::schedule(lyCounter, cycleCounter)); + addFixedtimeEvent(m3EventQueue, &win.wyReg.reader3(), Wy::WyReader3::schedule(win.wxReader.getSource(), scxReader, lyCounter, cycleCounter)); + addFixedtimeEvent(m3EventQueue, &win.wyReg.reader4(), Wy::WyReader4::schedule(lyCounter, cycleCounter)); + addEvent(m3EventQueue, &win.weMasterChecker, WeMasterChecker::schedule(win.wyReg.getSource(), win.we.getSource(), lyCounter, cycleCounter)); + addUnconditionalEvent(vEventQueue, &mode3Event, Mode3Event::schedule(m3EventQueue)); +} + +void LCD::scxChange(const unsigned newScx, const unsigned long cycleCounter) { + update(cycleCounter); + + scxReader.setSource(newScx); + breakEvent.setScxSource(newScx); + scReader.setScxSource(newScx); + + addFixedtimeEvent(m3EventQueue, &scxReader, ScxReader::schedule(lyCounter, cycleCounter)); + + if (win.wyReg.reader3().time() != VideoEvent::DISABLED_TIME) + addEvent(m3EventQueue, &win.wyReg.reader3(), Wy::WyReader3::schedule(win.wxReader.getSource(), scxReader, lyCounter, cycleCounter)); + + addUnconditionalEvent(vEventQueue, &mode3Event, Mode3Event::schedule(m3EventQueue)); + + const unsigned lineCycles = lyCounter.lineCycles(cycleCounter); + + if (lineCycles < 82U + doubleSpeed * 4U) + drawStartCycle = 90 + doubleSpeed * 4U + (newScx & 7); + else + addFixedtimeEvent(vEventQueue, &breakEvent, BreakEvent::schedule(lyCounter)); + + if (lineCycles < 86U + doubleSpeed * 2U) + scReadOffset = std::max(drawStartCycle - (newScx & 7), 90U + doubleSpeed * 4U); + + addEvent(vEventQueue, &scReader, ScReader::schedule(lastUpdate, videoCycles, scReadOffset, doubleSpeed)); +} + +void LCD::scyChange(const unsigned newValue, const unsigned long cycleCounter) { + update(cycleCounter); + + scReader.setScySource(newValue); + addFixedtimeEvent(vEventQueue, &scReader, ScReader::schedule(lastUpdate, videoCycles, scReadOffset, doubleSpeed)); +} + +void LCD::spriteSizeChange(const bool newLarge, const unsigned long cycleCounter) { + update(cycleCounter); + + spriteMapper.oamChange(cycleCounter); + spriteMapper.setLargeSpritesSource(newLarge); + addFixedtimeEvent(m3EventQueue, &spriteMapper, SpriteMapper::schedule(lyCounter, cycleCounter)); + addUnconditionalEvent(vEventQueue, &mode3Event, Mode3Event::schedule(m3EventQueue)); +} + +void LCD::oamChange(const unsigned long cycleCounter) { + update(cycleCounter); + + spriteMapper.oamChange(cycleCounter); + addFixedtimeEvent(m3EventQueue, &spriteMapper, SpriteMapper::schedule(lyCounter, cycleCounter)); + addUnconditionalEvent(vEventQueue, &mode3Event, Mode3Event::schedule(m3EventQueue)); +} + +void LCD::oamChange(const unsigned char *const oamram, const unsigned long cycleCounter) { + update(cycleCounter); + + spriteMapper.oamChange(oamram, cycleCounter); + addFixedtimeEvent(m3EventQueue, &spriteMapper, SpriteMapper::schedule(lyCounter, cycleCounter)); + addUnconditionalEvent(vEventQueue, &mode3Event, Mode3Event::schedule(m3EventQueue)); +} + +void LCD::wdTileMapSelectChange(const bool newValue, const unsigned long cycleCounter) { + update(cycleCounter); + + wdTileMap = vram + 0x1800 + newValue * 0x400; +} + +void LCD::bgTileMapSelectChange(const bool newValue, const unsigned long cycleCounter) { + update(cycleCounter); + + bgTileMap = vram + 0x1800 + newValue * 0x400; +} + +void LCD::bgTileDataSelectChange(const bool newValue, const unsigned long cycleCounter) { + update(cycleCounter); + + tileIndexSign = (newValue ^ 1) * 0x80; + bgTileData = vram + (newValue ^ 1) * 0x1000; +} + +void LCD::spriteEnableChange(const bool newValue, const unsigned long cycleCounter) { + update(cycleCounter); + + spriteEnable = newValue; +} + +void LCD::bgEnableChange(const bool newValue, const unsigned long cycleCounter) { + update(cycleCounter); + + bgEnable = newValue; +} + +void LCD::lcdstatChange(const unsigned data, const unsigned long cycleCounter) { + if (cycleCounter >= vEventQueue.top()->time()) + update(cycleCounter); + + const unsigned old = statReg; + statReg = data; + mode1Irq.setM1StatIrqEnabled(data & 0x10); + lycIrq.setM2IrqEnabled(data & 0x20); + + if (!enabled) + return; + + const bool lycIrqPeriod = isLycIrqPeriod(lycIrq.lycReg(), lycIrq.lycReg() == 153 ? lyCounter.lineTime() - (4 << (doubleSpeed*2)) : 4 - doubleSpeed * 4U, cycleCounter); + + if (lycIrq.lycReg() < 154 && ((data ^ old) & 0x40)) { + if (data & 0x40) { + if (lycIrqPeriod) + ifReg |= 2; + } else { + if (!doubleSpeed && lycIrq.time() - cycleCounter < 5 && (!(old & 0x20) || lycIrq.lycReg() > 143 || lycIrq.lycReg() == 0)) + ifReg |= 2; + } + + addFixedtimeEvent(irqEventQueue, &lycIrq, LycIrq::schedule(data, lycIrq.lycReg(), lyCounter, cycleCounter)); + } + + if ((((data & 0x10) && !(old & 0x10)) || !cgb) && !((old & 0x40) && lycIrqPeriod) && isMode1IrqPeriod(cycleCounter)) + ifReg |= 2; + + if ((data ^ old) & 0x08) { + if (data & 0x08) { + if (!((old & 0x40) && lycIrqPeriod) && isMode0IrqPeriod(cycleCounter)) + ifReg |= 2; + } else { + if (mode0Irq.time() - cycleCounter < 3 && (lycIrq.time() == VideoEvent::DISABLED_TIME || lyCounter.ly() != lycIrq.lycReg())) + ifReg |= 2; + } + + addFixedtimeEvent(irqEventQueue, &mode0Irq, Mode0Irq::schedule(data, m3ExtraCycles, lyCounter, cycleCounter)); + } + + if ((data & 0x28) == 0x20 && (old & 0x28) != 0x20 && isMode2IrqPeriod(cycleCounter)) { + ifReg |= 2; + } + + addFixedtimeEvent(irqEventQueue, &mode2Irq, Mode2Irq::schedule(data, lyCounter, cycleCounter)); + + addEvent(vEventQueue, &irqEvent, IrqEvent::schedule(irqEventQueue)); +} + +void LCD::lycRegChange(const unsigned data, const unsigned long cycleCounter) { + if (data == lycIrq.lycReg()) + return; + + if (cycleCounter >= vEventQueue.top()->time()) + update(cycleCounter); + + const unsigned old = lycIrq.lycReg(); + lycIrq.setLycReg(data); + + if (!(enabled && (statReg & 0x40))) + return; + + if (!doubleSpeed && lycIrq.time() - cycleCounter < 5 && (!(statReg & 0x20) || old > 143 || old == 0)) + ifReg |= 2; + + addEvent(irqEventQueue, &lycIrq, LycIrq::schedule(statReg, lycIrq.lycReg(), lyCounter, cycleCounter)); + + if (data < 154) { + if (isLycIrqPeriod(data, data == 153 ? lyCounter.lineTime() - doubleSpeed * 8U : 8, cycleCounter)) + ifReg |= 2; + + if (lycIrq.isSkipPeriod(cycleCounter, doubleSpeed)) + lycIrq.setSkip(true); + } + + addEvent(vEventQueue, &irqEvent, IrqEvent::schedule(irqEventQueue)); +} + +unsigned long LCD::nextIrqEvent() const { + if (!enabled) + return VideoEvent::DISABLED_TIME; + + if (mode0Irq.time() != VideoEvent::DISABLED_TIME && mode3Event.time() < irqEvent.time()) + return mode3Event.time(); + + return irqEvent.time(); +} + +unsigned LCD::getIfReg(const unsigned long cycleCounter) { + if (cycleCounter >= vEventQueue.top()->time()) + update(cycleCounter); + + return ifReg; +} + +void LCD::setIfReg(const unsigned ifReg_in, const unsigned long cycleCounter) { + if (cycleCounter >= vEventQueue.top()->time()) + update(cycleCounter); + + ifReg = ifReg_in; +} + +unsigned LCD::get_stat(const unsigned lycReg, const unsigned long cycleCounter) { + unsigned stat = 0; + + if (enabled) { + if (cycleCounter >= vEventQueue.top()->time()) + update(cycleCounter); + + const unsigned timeToNextLy = lyCounter.time() - cycleCounter; + + if (lyCounter.ly() > 143) { + if (lyCounter.ly() < 153 || timeToNextLy > 4 - doubleSpeed * 4U) + stat = 1; + } else { + const unsigned lineCycles = 456 - (timeToNextLy >> doubleSpeed); + + if (lineCycles < 80) { + if (!spriteMapper.inactivePeriodAfterDisplayEnable(cycleCounter)) + stat = 2; + } else if (lineCycles < 80 + 169 + doubleSpeed * 3U + m3ExtraCycles(lyCounter.ly())) + stat = 3; + } + + if ((lyCounter.ly() == lycReg && timeToNextLy > 4 - doubleSpeed * 4U) || + (lycReg == 0 && lyCounter.ly() == 153 && timeToNextLy >> doubleSpeed <= 456 - 8)) { + stat |= 4; + } + } + + return stat; +} + +void LCD::do_update(unsigned cycles) { + if (lyCounter.ly() < 144) { + const unsigned lineCycles = lyCounter.lineCycles(lastUpdate); + const unsigned xpos = lineCycles < drawStartCycle ? 0 : lineCycles - drawStartCycle; + + const unsigned endLineCycles = lineCycles + cycles; + unsigned endX = endLineCycles < drawStartCycle ? 0 : endLineCycles - drawStartCycle; + + if (endX > 160) + endX = 160; + + if (xpos < endX) + (this->*draw)(xpos, lyCounter.ly(), endX); + } else if (lyCounter.ly() == 144) { + winYPos = 0xFF; + //scy[0] = scy[1] = memory.fastread(0xFF42); + //scx[0] = scx[1] = memory.fastread(0xFF43); + win.weMasterChecker.unset(); + } + + videoCycles += cycles; + + if (videoCycles >= 70224U) + videoCycles -= 70224U; +} + +inline void LCD::event() { + vEventQueue.top()->doEvent(); + + if (vEventQueue.top()->time() == VideoEvent::DISABLED_TIME) + vEventQueue.pop(); + else + vEventQueue.modify_root(vEventQueue.top()); +} + +void LCD::update(const unsigned long cycleCounter) { + if (!enabled) + return; + + for (;;) { + const unsigned cycles = (std::max(std::min(cycleCounter, static_cast(vEventQueue.top()->time())), lastUpdate) - lastUpdate) >> doubleSpeed; + do_update(cycles); + lastUpdate += cycles << doubleSpeed; + + if (cycleCounter >= vEventQueue.top()->time()) + event(); + else + break; + } +} + +void LCD::setDBuffer() { + tmpbuf.reset(pb.format == Gambatte::PixelBuffer::RGB32 ? 0 : videoWidth() * videoHeight()); + + if (cgb) + draw = &LCD::cgb_draw; + else + draw = &LCD::dmg_draw; + + gbcToFormat = &gbcToRgb32; + dmgColors = dmgColorsRgb32; + + if (filter) { + dbuffer = filter->inBuffer(); + dpitch = filter->inPitch(); + } else if (pb.format == Gambatte::PixelBuffer::RGB32) { + dbuffer = pb.pixels; + dpitch = pb.pitch; + } else { + dbuffer = tmpbuf; + dpitch = 160; + } + + if (dbuffer == NULL) + draw = &LCD::null_draw; + + refreshPalettes(); +} + +void LCD::setDmgPaletteColor(const unsigned index, const unsigned long rgb32) { + dmgColorsRgb32[index] = rgb32; + dmgColorsRgb16[index] = rgb32ToRgb16(rgb32); + dmgColorsUyvy[index] = ::rgb32ToUyvy(rgb32); +} + +void LCD::setDmgPaletteColor(const unsigned palNum, const unsigned colorNum, const unsigned long rgb32) { + if (palNum > 2 || colorNum > 3) + return; + + setDmgPaletteColor((palNum * 4) | colorNum, rgb32); + refreshPalettes(); +} + +void LCD::null_draw(unsigned /*xpos*/, const unsigned ypos, const unsigned endX) { + const bool enableWindow = win.enabled(ypos); + + if (enableWindow && winYPos == 0xFF) + winYPos = /*ypos - wyReg.value()*/ 0; + + if (endX == 160) { + if (enableWindow) + ++winYPos; + } +} + +template +void LCD::cgb_draw(unsigned xpos, const unsigned ypos, const unsigned endX) { + const unsigned effectiveScx = scReader.scx(); + + const bool enableWindow = win.enabled(ypos); + + if (enableWindow && winYPos == 0xFF) + winYPos = /*ypos - wyReg.value()*/ 0; + + T *const bufLine = static_cast(dbuffer) + ypos * static_cast(dpitch); + + if (!(enableWindow && win.wxReader.wx() <= xpos + 7)) { + const unsigned fby = scReader.scy() + ypos /*& 0xFF*/; + const unsigned end = std::min(enableWindow ? win.wxReader.wx() - 7 : 160U, endX); + + cgb_bg_drawPixels(bufLine, xpos, end, scxReader.scxAnd7(), ((xpos + effectiveScx) & ~7) + ((xpos + drawStartCycle - scReadOffset) & 7), + bgTileMap + (fby & 0xF8) * 4, bgTileData, fby & 7); + } + + if (enableWindow && endX + 7 > win.wxReader.wx()) { + const unsigned start = std::max(win.wxReader.wx() < 7 ? 0U : (win.wxReader.wx() - 7), xpos); + + cgb_bg_drawPixels(bufLine, start, endX, 7u - win.wxReader.wx(), start + (7u - win.wxReader.wx()), + wdTileMap + (winYPos & 0xF8) * 4, bgTileData, winYPos & 7); + } + + if (endX == 160) { + if (spriteEnable) + cgb_drawSprites(bufLine, ypos); + + if (enableWindow) + ++winYPos; + } +} + +template +void LCD::dmg_draw(unsigned xpos, const unsigned ypos, const unsigned endX) { + const unsigned effectiveScx = scReader.scx(); + + const bool enableWindow = win.enabled(ypos); + + if (enableWindow && winYPos == 0xFF) + winYPos = /*ypos - wyReg.value()*/ 0; + + T *const bufLine = static_cast(dbuffer) + ypos * static_cast(dpitch); + + if (bgEnable) { + if (!(enableWindow && win.wxReader.wx() <= xpos + 7)) { + const unsigned fby = scReader.scy() + ypos /*& 0xFF*/; + const unsigned end = std::min(enableWindow ? win.wxReader.wx() - 7 : 160U, endX); + + bg_drawPixels(bufLine, xpos, end, scxReader.scxAnd7(), ((xpos + effectiveScx) & ~7) + ((xpos + drawStartCycle - scReadOffset) & 7), + bgTileMap + (fby & 0xF8) * 4, bgTileData + (fby & 7) * 2); + } + + if (enableWindow && endX + 7 > win.wxReader.wx()) { + const unsigned start = std::max(win.wxReader.wx() < 7 ? 0U : (win.wxReader.wx() - 7), xpos); + + bg_drawPixels(bufLine, start, endX, 7u - win.wxReader.wx(), start + (7u - win.wxReader.wx()), + wdTileMap + (winYPos & 0xF8) * 4, bgTileData + (winYPos & 7) * 2); + } + } else + std::fill_n(bufLine + xpos, endX - xpos, bgPalette[0]); + + if (endX == 160) { + if (spriteEnable) + drawSprites(bufLine, ypos); + + if (enableWindow) + ++winYPos; + } +} + +#define FLIP(u8) ( (((u8) & 0x01) << 7) | (((u8) & 0x02) << 5) | (((u8) & 0x04) << 3) | (((u8) & 0x08) << 1) | \ +(((u8) & 0x10) >> 1) | (((u8) & 0x20) >> 3) | (((u8) & 0x40) >> 5) | (((u8) & 0x80) >> 7) ) + +#define FLIP_ROW(n) FLIP((n)|0x0), FLIP((n)|0x1), FLIP((n)|0x2), FLIP((n)|0x3), FLIP((n)|0x4), FLIP((n)|0x5), FLIP((n)|0x6), FLIP((n)|0x7), \ +FLIP((n)|0x8), FLIP((n)|0x9), FLIP((n)|0xA), FLIP((n)|0xB), FLIP((n)|0xC), FLIP((n)|0xD), FLIP((n)|0xE), FLIP((n)|0xF) + +static const unsigned char xflipt[0x100] = { + FLIP_ROW(0x00), FLIP_ROW(0x10), FLIP_ROW(0x20), FLIP_ROW(0x30), + FLIP_ROW(0x40), FLIP_ROW(0x50), FLIP_ROW(0x60), FLIP_ROW(0x70), + FLIP_ROW(0x80), FLIP_ROW(0x90), FLIP_ROW(0xA0), FLIP_ROW(0xB0), + FLIP_ROW(0xC0), FLIP_ROW(0xD0), FLIP_ROW(0xE0), FLIP_ROW(0xF0) +}; + +#undef FLIP_ROW +#undef FLIP + +#define PREP(u8) (u8) + +#define EXPAND(u8) ((PREP(u8) << 7 & 0x4000) | (PREP(u8) << 6 & 0x1000) | (PREP(u8) << 5 & 0x0400) | (PREP(u8) << 4 & 0x0100) | \ + (PREP(u8) << 3 & 0x0040) | (PREP(u8) << 2 & 0x0010) | (PREP(u8) << 1 & 0x0004) | (PREP(u8) & 0x0001)) + +#define EXPAND_ROW(n) EXPAND((n)|0x0), EXPAND((n)|0x1), EXPAND((n)|0x2), EXPAND((n)|0x3), \ + EXPAND((n)|0x4), EXPAND((n)|0x5), EXPAND((n)|0x6), EXPAND((n)|0x7), \ + EXPAND((n)|0x8), EXPAND((n)|0x9), EXPAND((n)|0xA), EXPAND((n)|0xB), \ + EXPAND((n)|0xC), EXPAND((n)|0xD), EXPAND((n)|0xE), EXPAND((n)|0xF) + +#define EXPAND_TABLE EXPAND_ROW(0x00), EXPAND_ROW(0x10), EXPAND_ROW(0x20), EXPAND_ROW(0x30), \ + EXPAND_ROW(0x40), EXPAND_ROW(0x50), EXPAND_ROW(0x60), EXPAND_ROW(0x70), \ + EXPAND_ROW(0x80), EXPAND_ROW(0x90), EXPAND_ROW(0xA0), EXPAND_ROW(0xB0), \ + EXPAND_ROW(0xC0), EXPAND_ROW(0xD0), EXPAND_ROW(0xE0), EXPAND_ROW(0xF0) + +static const unsigned short expand_lut[0x200] = { + EXPAND_TABLE, + +#undef PREP +#define PREP(u8) (((u8) << 7 & 0x80) | ((u8) << 5 & 0x40) | ((u8) << 3 & 0x20) | ((u8) << 1 & 0x10) | \ + ((u8) >> 1 & 0x08) | ((u8) >> 3 & 0x04) | ((u8) >> 5 & 0x02) | ((u8) >> 7 & 0x01)) + + EXPAND_TABLE +}; + +#undef EXPAND_TABLE +#undef EXPAND_ROW +#undef EXPAND +#undef PREP + +//shoud work for the window too, if -wx is passed as scx. +//tilemap and tiledata must point to the areas in the first vram bank +//the second vram bank has to be placed immediately after the first one in memory (0x4000 continous bytes that cover both). +//tilemap needs to be offset to the right line +template +void LCD::cgb_bg_drawPixels(T * const buffer_line, unsigned xpos, const unsigned end, const unsigned scx, unsigned tilemappos, + const unsigned char *const tilemap, const unsigned char *const tiledata, const unsigned tileline) +{ + const unsigned sign = tileIndexSign; + unsigned shift = (7 - ((scx + xpos) & 7)) * 2; + T *buf = buffer_line + xpos; + T *const bufend = buffer_line + end; + + while (buf < bufend) { + if ((tilemappos & 7) || bufend - buf < 8) { + const unsigned char *const maptmp = tilemap + (tilemappos >> 3 & 0x1F); + const unsigned attributes = maptmp[0x2000]; + const unsigned char *const dataptr = tiledata + (attributes << 10 & 0x2000) + + maptmp[0] * 16 - (maptmp[0] & sign) * 32 + ((attributes & 0x40) ? 7 - tileline : tileline) * 2; + const unsigned short *const exp_lut = expand_lut + (attributes << 3 & 0x100); + + const unsigned data = exp_lut[dataptr[0]] + exp_lut[dataptr[1]] * 2; + const unsigned long *const palette = bgPalette + (attributes & 7) * 4; + + do { + *buf++ = palette[data >> shift & 3]; + shift = (shift - 2) & 15; + } while ((++tilemappos & 7) && buf < bufend); + } + + while (bufend - buf > 7) { + const unsigned char *const maptmp = tilemap + (tilemappos >> 3 & 0x1F); + const unsigned attributes = maptmp[0x2000]; + const unsigned char *const dataptr = tiledata + (attributes << 10 & 0x2000) + + maptmp[0] * 16 - (maptmp[0] & sign) * 32 + ((attributes & 0x40) ? 7 - tileline : tileline) * 2; + const unsigned short *const exp_lut = expand_lut + (attributes << 3 & 0x100); + + const unsigned data = exp_lut[dataptr[0]] + exp_lut[dataptr[1]] * 2; + const unsigned long *const palette = bgPalette + (attributes & 7) * 4; + + buf[0] = palette[data >> shift & 3]; + buf[1] = palette[data >> ((shift - 2) & 15) & 3]; + buf[2] = palette[data >> ((shift - 4) & 15) & 3]; + buf[3] = palette[data >> ((shift - 6) & 15) & 3]; + buf[4] = palette[data >> ((shift - 8) & 15) & 3]; + buf[5] = palette[data >> ((shift - 10) & 15) & 3]; + buf[6] = palette[data >> ((shift - 12) & 15) & 3]; + buf[7] = palette[data >> ((shift - 14) & 15) & 3]; + + buf += 8; + tilemappos += 8; + } + } +} + +static unsigned cgb_prioritizedBG_mask(const unsigned spx, const unsigned bgStart, const unsigned bgEnd, const unsigned scx, + const unsigned char *const tilemap, const unsigned char *const tiledata, const unsigned tileline, const unsigned sign) { + const unsigned spStart = spx < bgStart + 8 ? bgStart + 8 - spx : 0; + + unsigned bgbyte; + + { + const unsigned pos = scx + spx - 8 + spStart; + const unsigned char *maptmp = tilemap + (pos >> 3 & 0x1F); + unsigned tile = maptmp[0]; + unsigned attributes = maptmp[0x2000]; + + const unsigned char *const data = tiledata + (attributes << 10 & 0x2000) + + tile * 16 - (tile & sign) * 32 + ((attributes & 0x40) ? 7 - tileline : tileline) * 2; + + bgbyte = (attributes & 0x20) ? xflipt[data[0] | data[1]] : (data[0] | data[1]); + + const unsigned offset = pos & 7; + + if (offset) { + bgbyte <<= offset; + maptmp = tilemap + (((pos >> 3) + 1) & 0x1F); + tile = maptmp[0]; + attributes = maptmp[0x2000]; + + const unsigned char *const data = tiledata + (attributes << 10 & 0x2000) + + tile * 16 - (tile & sign) * 32 + ((attributes & 0x40) ? 7 - tileline : tileline) * 2; + + bgbyte |= ((attributes & 0x20) ? xflipt[data[0] | data[1]] : (data[0] | data[1])) >> (8 - offset); + } + } + + bgbyte >>= spStart; + const unsigned spEnd = spx > bgEnd ? bgEnd + 8 - spx : 8; + const unsigned mask = ~bgbyte | 0xFF >> spEnd; + + return mask; +} + +static unsigned cgb_toplayerBG_mask(const unsigned spx, const unsigned bgStart, const unsigned bgEnd, const unsigned scx, + const unsigned char *const tilemap, const unsigned char *const tiledata, const unsigned tileline, const unsigned sign) { + const unsigned spStart = spx < bgStart + 8 ? bgStart + 8 - spx : 0; + + unsigned bgbyte = 0; + + { + const unsigned pos = scx + spx - 8 + spStart; + const unsigned char *maptmp = tilemap + (pos >> 3 & 0x1F); + unsigned attributes = maptmp[0x2000]; + + if (attributes & 0x80) { + const unsigned tile = maptmp[0]; + + const unsigned char *const data = tiledata + (attributes << 10 & 0x2000) + + tile * 16 - (tile & sign) * 32 + ((attributes & 0x40) ? 7 - tileline : tileline) * 2; + + bgbyte = (attributes & 0x20) ? xflipt[data[0] | data[1]] : (data[0] | data[1]); + } + + const unsigned offset = pos & 7; + + if (offset) { + bgbyte <<= offset; + maptmp = tilemap + (((pos >> 3) + 1) & 0x1F); + attributes = maptmp[0x2000]; + + if (attributes & 0x80) { + const unsigned tile = maptmp[0]; + + const unsigned char *const data = tiledata + (attributes << 10 & 0x2000) + + tile * 16 - (tile & sign) * 32 + ((attributes & 0x40) ? 7 - tileline : tileline) * 2; + + bgbyte |= ((attributes & 0x20) ? xflipt[data[0] | data[1]] : (data[0] | data[1])) >> (8 - offset); + } + } + } + + bgbyte >>= spStart; + const unsigned spEnd = spx > bgEnd ? bgEnd + 8 - spx : 8; + const unsigned mask = ~bgbyte | 0xFF >> spEnd; + + return mask; +} + +template +void LCD::cgb_drawSprites(T * const buffer_line, const unsigned ypos) { + const unsigned scy = scReader.scy() + ypos /*& 0xFF*/; + const unsigned wx = win.wxReader.wx() < 7 ? 0 : win.wxReader.wx() - 7; + const bool enableWindow = win.enabled(ypos); + const unsigned char *const spriteMapLine = spriteMapper.sprites(ypos); + + for (int i = spriteMapper.numSprites(ypos) - 1; i >= 0; --i) { + const unsigned spNrX2 = spriteMapLine[i]; + const unsigned spx = spriteMapper.posbuf()[spNrX2 + 1]; + + if (spx < 168 && spx) { + unsigned spLine = ypos + 16 - spriteMapper.posbuf()[spNrX2]; + unsigned spTile = spriteMapper.oamram()[spNrX2 * 2 + 2]; + const unsigned attributes = spriteMapper.oamram()[spNrX2 * 2 + 3]; + + if (spriteMapper.largeSprites(spNrX2 >> 1)) { + if (attributes & 0x40) //yflip + spLine = 15 - spLine; + + if (spLine < 8) + spTile &= 0xFE; + else { + spLine -= 8; + spTile |= 0x01; + } + } else { + if (attributes & 0x40) //yflip + spLine = 7 - spLine; + } + + const unsigned char *const data = vram + ((attributes * 0x400) & 0x2000) + spTile * 16 + spLine * 2; + + unsigned byte1 = data[0]; + unsigned byte2 = data[1]; + + if (attributes & 0x20) { + byte1 = xflipt[byte1]; + byte2 = xflipt[byte2]; + } + + //(Sprites with priority-bit are still allowed to cover other sprites according to GBdev-faq.) + if (bgEnable) { + unsigned mask = 0xFF; + + if (attributes & 0x80) { + if (!(enableWindow && (wx == 0 || spx >= wx + 8u))) + mask = cgb_prioritizedBG_mask(spx, 0, enableWindow ? wx : 160, scReader.scx(), + bgTileMap + ((scy & 0xF8) << 2), bgTileData, scy & 7, tileIndexSign); + if (enableWindow && spx > wx) + mask &= cgb_prioritizedBG_mask(spx, wx, 160, 0u - wx, wdTileMap + ((winYPos & 0xF8) << 2), bgTileData, winYPos & 7, tileIndexSign); + } else { + if (!(enableWindow && (wx == 0 || spx >= wx + 8u))) + mask = cgb_toplayerBG_mask(spx, 0, enableWindow ? wx : 160, scReader.scx(), + bgTileMap + ((scy & 0xF8) << 2), bgTileData, scy & 7, tileIndexSign); + if (enableWindow && spx > wx) + mask &= cgb_toplayerBG_mask(spx, wx, 160, 0u - wx, wdTileMap + ((winYPos & 0xF8) << 2), bgTileData, winYPos & 7, tileIndexSign); + } + + byte1 &= mask; + byte2 &= mask; + } + + const unsigned bytes = expand_lut[byte1] + expand_lut[byte2] * 2; + const unsigned long *const palette = spPalette + (attributes & 7) * 4; + + if (spx > 7 && spx < 161) { + T * const buf = buffer_line + spx - 8; + unsigned color; + + if ((color = bytes >> 14 )) + buf[0] = palette[color]; + if ((color = bytes >> 12 & 3)) + buf[1] = palette[color]; + if ((color = bytes >> 10 & 3)) + buf[2] = palette[color]; + if ((color = bytes >> 8 & 3)) + buf[3] = palette[color]; + if ((color = bytes >> 6 & 3)) + buf[4] = palette[color]; + if ((color = bytes >> 4 & 3)) + buf[5] = palette[color]; + if ((color = bytes >> 2 & 3)) + buf[6] = palette[color]; + if ((color = bytes & 3)) + buf[7] = palette[color]; + } else { + const unsigned end = spx >= 160 ? 160 : spx; + unsigned xpos = spx <= 8 ? 0 : (spx - 8); + unsigned shift = (7 - (xpos + 8 - spx)) * 2; + + while (xpos < end) { + if (const unsigned color = bytes >> shift & 3) + buffer_line[xpos] = palette[color]; + + shift -= 2; + ++xpos; + } + } + } + } +} + + +//shoud work for the window too, if -wx is passed as scx. +//tilemap and tiledata need to be offset to the right line +template +void LCD::bg_drawPixels(T * const buffer_line, unsigned xpos, const unsigned end, const unsigned scx, unsigned tilemappos, + const unsigned char *const tilemap, const unsigned char *const tiledata) +{ + const unsigned sign = tileIndexSign; + unsigned shift = (7 - ((scx + xpos) & 7)) * 2; + T *buf = buffer_line + xpos; + T *const bufend = buffer_line + end; + + while (buf < bufend) { + if ((tilemappos & 7) || bufend - buf < 8) { + const unsigned tile = tilemap[tilemappos >> 3 & 0x1F]; + const unsigned char *const dataptr = tiledata + tile * 16 - (tile & sign) * 32; + const unsigned data = expand_lut[dataptr[0]] + expand_lut[dataptr[1]] * 2; + + do { + *buf++ = bgPalette[data >> shift & 3]; + shift = (shift - 2) & 15; + } while ((++tilemappos & 7) && buf < bufend); + } + + while (bufend - buf > 7) { + const unsigned tile = tilemap[tilemappos >> 3 & 0x1F]; + const unsigned char *const dataptr = tiledata + tile * 16 - (tile & sign) * 32; + const unsigned data = expand_lut[dataptr[0]] + expand_lut[dataptr[1]] * 2; + buf[0] = bgPalette[data >> shift & 3]; + buf[1] = bgPalette[data >> ((shift - 2) & 15) & 3]; + buf[2] = bgPalette[data >> ((shift - 4) & 15) & 3]; + buf[3] = bgPalette[data >> ((shift - 6) & 15) & 3]; + buf[4] = bgPalette[data >> ((shift - 8) & 15) & 3]; + buf[5] = bgPalette[data >> ((shift - 10) & 15) & 3]; + buf[6] = bgPalette[data >> ((shift - 12) & 15) & 3]; + buf[7] = bgPalette[data >> ((shift - 14) & 15) & 3]; + buf += 8; + tilemappos += 8; + } + } +} + +static unsigned prioritizedBG_mask(const unsigned spx, const unsigned bgStart, const unsigned bgEnd, const unsigned scx, + const unsigned char *const tilemap, const unsigned char *const tiledata, const unsigned sign) { + const unsigned spStart = spx < bgStart + 8 ? bgStart + 8 - spx : 0; + + unsigned bgbyte; + + { + const unsigned pos = scx + spx - 8 + spStart; + unsigned tile = tilemap[pos >> 3 & 0x1F]; + const unsigned char *data = tiledata + tile * 16 - (tile & sign) * 32; + bgbyte = data[0] | data[1]; + const unsigned offset = pos & 7; + + if (offset) { + bgbyte <<= offset; + tile = tilemap[((pos >> 3) + 1) & 0x1F]; + data = tiledata + tile * 16 - (tile & sign) * 32; + bgbyte |= (data[0] | data[1]) >> (8 - offset); + } + } + + bgbyte >>= spStart; + const unsigned spEnd = spx > bgEnd ? bgEnd + 8 - spx : 8; + const unsigned mask = ~bgbyte | 0xFF >> spEnd; + + return mask; +} + +template +void LCD::drawSprites(T * const buffer_line, const unsigned ypos) { + const unsigned scy = scReader.scy() + ypos /*& 0xFF*/; + const unsigned wx = win.wxReader.wx() < 7 ? 0 : win.wxReader.wx() - 7; + const bool enableWindow = win.enabled(ypos); + const unsigned char *const spriteMapLine = spriteMapper.sprites(ypos); + + for (int i = spriteMapper.numSprites(ypos) - 1; i >= 0; --i) { + const unsigned spNrX2 = spriteMapLine[i]; + const unsigned spx = spriteMapper.posbuf()[spNrX2 + 1]; + + if (spx < 168 && spx) { + unsigned spLine = ypos + 16 - spriteMapper.posbuf()[spNrX2]; + unsigned spTile = spriteMapper.oamram()[spNrX2 * 2 + 2]; + const unsigned attributes = spriteMapper.oamram()[spNrX2 * 2 + 3]; + + if (spriteMapper.largeSprites(spNrX2 >> 1)) { + if (attributes & 0x40) //yflip + spLine = 15 - spLine; + + if (spLine < 8) + spTile &= 0xFE; + else { + spLine -= 8; + spTile |= 0x01; + } + } else { + if (attributes & 0x40) //yflip + spLine = 7 - spLine; + } + + const unsigned char *const data = vram + spTile * 16 + spLine * 2; + + unsigned byte1 = data[0]; + unsigned byte2 = data[1]; + + if (attributes & 0x20) { + byte1 = xflipt[byte1]; + byte2 = xflipt[byte2]; + } + + //(Sprites with priority-bit are still allowed to cover other sprites according to GBdev-faq.) + if (attributes & 0x80) { + unsigned mask = 0xFF; + + if (bgEnable && !(enableWindow && (wx == 0 || spx >= wx + 8u))) + mask = prioritizedBG_mask(spx, 0, enableWindow ? wx : 160, scReader.scx(), + bgTileMap + ((scy & 0xF8) << 2), bgTileData + ((scy & 7) << 1), tileIndexSign); + if (enableWindow && spx > wx) + mask &= prioritizedBG_mask(spx, wx, 160, 0u - wx, wdTileMap + ((winYPos & 0xF8) << 2), bgTileData + ((winYPos & 7) << 1), tileIndexSign); + + byte1 &= mask; + byte2 &= mask; + } + + const unsigned bytes = expand_lut[byte1] + expand_lut[byte2] * 2; + const unsigned long *const palette = spPalette + ((attributes >> 2) & 4); + + if (spx > 7 && spx < 161) { + T * const buf = buffer_line + spx - 8; + unsigned color; + + if ((color = bytes >> 14 )) + buf[0] = palette[color]; + if ((color = bytes >> 12 & 3)) + buf[1] = palette[color]; + if ((color = bytes >> 10 & 3)) + buf[2] = palette[color]; + if ((color = bytes >> 8 & 3)) + buf[3] = palette[color]; + if ((color = bytes >> 6 & 3)) + buf[4] = palette[color]; + if ((color = bytes >> 4 & 3)) + buf[5] = palette[color]; + if ((color = bytes >> 2 & 3)) + buf[6] = palette[color]; + if ((color = bytes & 3)) + buf[7] = palette[color]; + } else { + const unsigned end = spx >= 160 ? 160 : spx; + unsigned xpos = spx <= 8 ? 0 : (spx - 8); + unsigned shift = (7 - (xpos + 8 - spx)) * 2; + + while (xpos < end) { + if (const unsigned color = bytes >> shift & 3) + buffer_line[xpos] = palette[color]; + + shift -= 2; + ++xpos; + } + } + } + } +} diff --git a/supergameboy/libgambatte/src/video.h b/supergameboy/libgambatte/src/video.h new file mode 100644 index 00000000..7271d1b1 --- /dev/null +++ b/supergameboy/libgambatte/src/video.h @@ -0,0 +1,293 @@ +/*************************************************************************** + * Copyright (C) 2007 by Sindre Aam�s * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#ifndef VIDEO_H +#define VIDEO_H + +namespace Gambatte { +class VideoBlitter; +struct FilterInfo; +} + +class Filter; +class SaveState; + +#include +#include +#include "event_queue.h" +#include "videoblitter.h" +#include "array.h" +#include "int.h" +#include "colorconversion.h" +#include "osd_element.h" + +#include "video/video_event_comparer.h" +#include "video/ly_counter.h" +#include "video/window.h" +#include "video/scx_reader.h" +#include "video/sprite_mapper.h" +#include "video/sc_reader.h" +#include "video/break_event.h" +#include "video/mode3_event.h" + +#include "video/lyc_irq.h" +#include "video/mode0_irq.h" +#include "video/mode1_irq.h" +#include "video/mode2_irq.h" +#include "video/irq_event.h" +#include "video/m3_extra_cycles.h" + +class LCD { + //static const uint8_t xflipt[0x100]; + unsigned long dmgColorsRgb32[3 * 4]; + unsigned long dmgColorsRgb16[3 * 4]; + unsigned long dmgColorsUyvy[3 * 4]; + + unsigned long bgPalette[8 * 4]; + unsigned long spPalette[8 * 4]; + + unsigned char bgpData[8 * 8]; + unsigned char objpData[8 * 8]; + + const unsigned char *const vram; + const unsigned char *bgTileData; + const unsigned char *bgTileMap; + const unsigned char *wdTileMap; + + Gambatte::VideoBlitter *vBlitter; + Filter *filter; + + void *dbuffer; + void (LCD::*draw)(unsigned xpos, unsigned ypos, unsigned endX); + unsigned long (*gbcToFormat)(unsigned bgr15); + const unsigned long *dmgColors; + + unsigned long lastUpdate; + unsigned long videoCycles; + + unsigned dpitch; + unsigned winYPos; + + event_queue m3EventQueue; + event_queue irqEventQueue; + event_queue vEventQueue; + + LyCounter lyCounter; + Window win; + ScxReader scxReader; + SpriteMapper spriteMapper; + M3ExtraCycles m3ExtraCycles; + ScReader scReader; + BreakEvent breakEvent; + Mode3Event mode3Event; + + LycIrq lycIrq; + Mode0Irq mode0Irq; + Mode1Irq mode1Irq; + Mode2Irq mode2Irq; + IrqEvent irqEvent; + + Gambatte::PixelBuffer pb; + Array tmpbuf; + Rgb32ToUyvy rgb32ToUyvy; + std::auto_ptr osdElement; + + std::vector filters; + + unsigned char drawStartCycle; + unsigned char scReadOffset; + unsigned char ifReg; + unsigned char tileIndexSign; + unsigned char statReg; + + bool doubleSpeed; + bool enabled; + bool cgb; + bool bgEnable; + bool spriteEnable; + + static void setDmgPalette(unsigned long *palette, const unsigned long *dmgColors, unsigned data); + void setDmgPaletteColor(unsigned index, unsigned long rgb32); + static unsigned long gbcToRgb32(unsigned bgr15); + static unsigned long gbcToRgb16(unsigned bgr15); + static unsigned long gbcToUyvy(unsigned bgr15); + + void refreshPalettes(); + void setDBuffer(); + void resetVideoState(unsigned long cycleCounter); + + void setDoubleSpeed(bool enabled); + + void event(); + + bool cgbpAccessible(unsigned long cycleCounter); + bool isMode0IrqPeriod(unsigned long cycleCounter); + bool isMode2IrqPeriod(unsigned long cycleCounter); + bool isLycIrqPeriod(unsigned lycReg, unsigned endCycles, unsigned long cycleCounter); + bool isMode1IrqPeriod(unsigned long cycleCounter); + + template void bg_drawPixels(T *buffer_line, unsigned xpos, unsigned end, unsigned scx, unsigned tilemappos, + const unsigned char *tilemap, const unsigned char *tiledata); + template void drawSprites(T *buffer_line, unsigned ypos); + + template void cgb_bg_drawPixels(T *buffer_line, unsigned xpos, unsigned end, unsigned scx, unsigned tilemappos, + const unsigned char *tilemap, const unsigned char *tiledata, unsigned tileline); + template void cgb_drawSprites(T *buffer_line, unsigned ypos); + + void null_draw(unsigned xpos, unsigned ypos, unsigned endX); + template void dmg_draw(unsigned xpos, unsigned ypos, unsigned endX); + template void cgb_draw(unsigned xpos, unsigned ypos, unsigned endX); + + void do_update(unsigned cycles); + +public: + void update(unsigned long cycleCounter); + + LCD(const unsigned char *oamram, const unsigned char *vram_in); + ~LCD(); + void reset(const unsigned char *oamram, bool cgb); + void setStatePtrs(SaveState &state); + void saveState(SaveState &state) const; + void loadState(const SaveState &state, const unsigned char *oamram); + void setVideoBlitter(Gambatte::VideoBlitter *vb); + void videoBufferChange(); + void setVideoFilter(unsigned n); + std::vector filterInfo() const; + unsigned videoWidth() const; + unsigned videoHeight() const; + void setDmgPaletteColor(unsigned palNum, unsigned colorNum, unsigned long rgb32); + + void setOsdElement(std::auto_ptr osdElement) { + this->osdElement = osdElement; + } + + void wdTileMapSelectChange(bool newValue, unsigned long cycleCounter); + void bgTileMapSelectChange(bool newValue, unsigned long cycleCounter); + void bgTileDataSelectChange(bool newValue, unsigned long cycleCounter); + void bgEnableChange(bool newValue, unsigned long cycleCounter); + void spriteEnableChange(bool newValue, unsigned long cycleCounter); + + void dmgBgPaletteChange(const unsigned data, const unsigned long cycleCounter) { + update(cycleCounter); + bgpData[0] = data; + setDmgPalette(bgPalette, dmgColors, data); + } + + void dmgSpPalette1Change(const unsigned data, const unsigned long cycleCounter) { + update(cycleCounter); + objpData[0] = data; + setDmgPalette(spPalette, dmgColors + 4, data); + } + + void dmgSpPalette2Change(const unsigned data, const unsigned long cycleCounter) { + update(cycleCounter); + objpData[1] = data; + setDmgPalette(spPalette + 4, dmgColors + 8, data); + } + + void cgbBgColorChange(unsigned index, const unsigned data, const unsigned long cycleCounter) { + if (bgpData[index] != data && cgbpAccessible(cycleCounter)) { + update(cycleCounter); + bgpData[index] = data; + index >>= 1; + bgPalette[index] = (*gbcToFormat)(bgpData[index << 1] | bgpData[(index << 1) + 1] << 8); + } + } + + void cgbSpColorChange(unsigned index, const unsigned data, const unsigned long cycleCounter) { + if (objpData[index] != data && cgbpAccessible(cycleCounter)) { + update(cycleCounter); + objpData[index] = data; + index >>= 1; + spPalette[index] = (*gbcToFormat)(objpData[index << 1] | objpData[(index << 1) + 1] << 8); + } + } + + unsigned cgbBgColorRead(const unsigned index, const unsigned long cycleCounter) { + return cgb & cgbpAccessible(cycleCounter) ? bgpData[index] : 0xFF; + } + + unsigned cgbSpColorRead(const unsigned index, const unsigned long cycleCounter) { + return cgb & cgbpAccessible(cycleCounter) ? objpData[index] : 0xFF; + } + + void updateScreen(unsigned long cc); + void enableChange(unsigned long cycleCounter); + void preResetCounter(unsigned long cycleCounter); + void postResetCounter(unsigned long oldCC, unsigned long cycleCounter); + void preSpeedChange(unsigned long cycleCounter); + void postSpeedChange(unsigned long cycleCounter); +// unsigned get_mode(unsigned cycleCounter) /*const*/; + bool vramAccessible(unsigned long cycleCounter); + bool oamAccessible(unsigned long cycleCounter); + void weChange(bool newValue, unsigned long cycleCounter); + void wxChange(unsigned newValue, unsigned long cycleCounter); + void wyChange(unsigned newValue, unsigned long cycleCounter); + void oamChange(unsigned long cycleCounter); + void oamChange(const unsigned char *oamram, unsigned long cycleCounter); + void scxChange(unsigned newScx, unsigned long cycleCounter); + void scyChange(unsigned newValue, unsigned long cycleCounter); + void spriteSizeChange(bool newLarge, unsigned long cycleCounter); + + void vramChange(const unsigned long cycleCounter) { + update(cycleCounter); + } + + unsigned get_stat(unsigned lycReg, unsigned long cycleCounter); + + unsigned getLyReg(const unsigned long cycleCounter) { + unsigned lyReg = 0; + + if (enabled) { + if (cycleCounter >= lyCounter.time()) + update(cycleCounter); + + lyReg = lyCounter.ly(); + + if (lyCounter.time() - cycleCounter <= 4) { + if (lyReg == 153) + lyReg = 0; + else + ++lyReg; + } else if (lyReg == 153) + lyReg = 0; + } + + return lyReg; + } + + unsigned long nextMode1IrqTime() const { + return mode1Irq.time(); + } + + void lyWrite(unsigned long cycleCounter); + void lcdstatChange(unsigned data, unsigned long cycleCounter); + void lycRegChange(unsigned data, unsigned long cycleCounter); + unsigned long nextIrqEvent() const; + unsigned getIfReg(unsigned long cycleCounter); + void setIfReg(unsigned ifReg_in, unsigned long cycleCounter); + + unsigned long nextHdmaTime(unsigned long cycleCounter); + bool isHdmaPeriod(unsigned long cycleCounter); + + unsigned long nextHdmaTimeInvalid() const { + return mode3Event.time(); + } +}; + +#endif diff --git a/supergameboy/libgambatte/src/video/basic_add_event.cpp b/supergameboy/libgambatte/src/video/basic_add_event.cpp new file mode 100644 index 00000000..4bc57a09 --- /dev/null +++ b/supergameboy/libgambatte/src/video/basic_add_event.cpp @@ -0,0 +1,75 @@ +/*************************************************************************** + * Copyright (C) 2008 by Sindre Aamås * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#include "basic_add_event.h" +#include "../event_queue.h" + +void addEvent(event_queue &q, VideoEvent *const e, const unsigned long newTime) { + const unsigned long oldTime = e->time(); + + if (oldTime != newTime) { + e->setTime(newTime); + + if (newTime < oldTime) { + if (oldTime == VideoEvent::DISABLED_TIME) + q.push(e); + else + q.dec(e, e); + } else { + if (newTime == VideoEvent::DISABLED_TIME) + q.remove(e); + else + q.inc(e, e); + } + } +} + +void addUnconditionalEvent(event_queue &q, VideoEvent *const e, const unsigned long newTime) { + const unsigned long oldTime = e->time(); + + e->setTime(newTime); + + if (newTime < oldTime) { + if (oldTime == VideoEvent::DISABLED_TIME) + q.push(e); + else + q.dec(e, e); + } else if (oldTime != newTime) { + q.inc(e, e); + } +} + +void addFixedtimeEvent(event_queue &q, VideoEvent *const e, const unsigned long newTime) { + const unsigned long oldTime = e->time(); + + if (oldTime != newTime) { + e->setTime(newTime); + + if (oldTime == VideoEvent::DISABLED_TIME) + q.push(e); + else + q.remove(e); + } +} + +void addUnconditionalFixedtimeEvent(event_queue &q, VideoEvent *const e, const unsigned long newTime) { + if (e->time() == VideoEvent::DISABLED_TIME) { + e->setTime(newTime); + q.push(e); + } +} diff --git a/supergameboy/libgambatte/src/video/basic_add_event.h b/supergameboy/libgambatte/src/video/basic_add_event.h new file mode 100644 index 00000000..780d7191 --- /dev/null +++ b/supergameboy/libgambatte/src/video/basic_add_event.h @@ -0,0 +1,56 @@ +/*************************************************************************** + * Copyright (C) 2007 by Sindre Aamås * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#ifndef BASIC_ADD_EVENT_H +#define BASIC_ADD_EVENT_H + +template class event_queue; + +#include "video_event.h" +#include "video_event_comparer.h" + +/*template +static inline void addEvent(T &event, const LyCounter &lyCounter, const unsigned long cycleCounter, event_queue &queue) { + if (event.time() == VideoEvent::DISABLED_TIME) { + event.schedule(lyCounter, cycleCounter); + queue.push(&event); + } +} + +template +static inline void addEvent(T &event, const unsigned data, const LyCounter &lyCounter, const unsigned long cycleCounter, event_queue &queue) { + if (event.time() == VideoEvent::DISABLED_TIME) { + event.schedule(data, lyCounter, cycleCounter); + queue.push(&event); + } +} + +template +static inline void addEvent(T &event, const unsigned data1, const unsigned data2, const LyCounter &lyCounter, const unsigned long cycleCounter, event_queue &queue) { + if (event.time() == VideoEvent::DISABLED_TIME) { + event.schedule(data1, data2, lyCounter, cycleCounter); + queue.push(&event); + } +}*/ + +void addEvent(event_queue &q, VideoEvent *e, unsigned long newTime); +void addUnconditionalEvent(event_queue &q, VideoEvent *e, unsigned long newTime); +void addFixedtimeEvent(event_queue &q, VideoEvent *e, unsigned long newTime); +void addUnconditionalFixedtimeEvent(event_queue &q, VideoEvent *e, unsigned long newTime); + +#endif diff --git a/supergameboy/libgambatte/src/video/break_event.cpp b/supergameboy/libgambatte/src/video/break_event.cpp new file mode 100644 index 00000000..e6e7ffbf --- /dev/null +++ b/supergameboy/libgambatte/src/video/break_event.cpp @@ -0,0 +1,35 @@ +/*************************************************************************** + * Copyright (C) 2007 by Sindre Aamås * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#include "break_event.h" + +BreakEvent::BreakEvent(unsigned char &drawStartCycle_in, unsigned char &scReadOffset_in) : + VideoEvent(3), + drawStartCycle(drawStartCycle_in), + scReadOffset(scReadOffset_in) +{ + setDoubleSpeed(false); + setScxSource(0); +} + +void BreakEvent::doEvent() { + scReadOffset = baseCycle; + drawStartCycle = baseCycle + (scxSrc & 7); + + setTime(DISABLED_TIME); +} diff --git a/supergameboy/libgambatte/src/video/break_event.h b/supergameboy/libgambatte/src/video/break_event.h new file mode 100644 index 00000000..9e7dcb82 --- /dev/null +++ b/supergameboy/libgambatte/src/video/break_event.h @@ -0,0 +1,59 @@ +/*************************************************************************** + * Copyright (C) 2007 by Sindre Aamås * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#ifndef BREAK_EVENT_H +#define BREAK_EVENT_H + +#include "video_event.h" +#include "ly_counter.h" +#include "basic_add_event.h" + +class BreakEvent : public VideoEvent { + unsigned char &drawStartCycle; + unsigned char &scReadOffset; + + unsigned char scxSrc; + unsigned char baseCycle; + +public: + BreakEvent(unsigned char &drawStartCycle_in, unsigned char &scReadOffset_in); + + void doEvent(); + + static unsigned long schedule(const LyCounter &lyCounter) { + return lyCounter.time(); + } + + void setDoubleSpeed(const bool dS) { + baseCycle = 90 + dS * 4; + } + + void setScxSource(const unsigned scxSrc_in) { + scxSrc = scxSrc_in; + } +}; + +static inline void addEvent(event_queue &q, BreakEvent *const e, const unsigned long newTime) { + addUnconditionalEvent(q, e, newTime); +} + +static inline void addFixedtimeEvent(event_queue &q, BreakEvent *const e, const unsigned long newTime) { + addUnconditionalFixedtimeEvent(q, e, newTime); +} + +#endif diff --git a/supergameboy/libgambatte/src/video/filters/catrom2x.cpp b/supergameboy/libgambatte/src/video/filters/catrom2x.cpp new file mode 100644 index 00000000..53a4c931 --- /dev/null +++ b/supergameboy/libgambatte/src/video/filters/catrom2x.cpp @@ -0,0 +1,194 @@ +/*************************************************************************** + * Copyright (C) 2007 by Sindre Aamås * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#include "catrom2x.h" +#include "filterinfo.h" +#include + +struct Colorsum { + Gambatte::uint_least32_t r, g, b; +}; + +static void merge_columns(Gambatte::uint_least32_t *dest, const Colorsum *sums) { + unsigned w = 160; + + while (w--) { + { + Gambatte::uint_least32_t rsum = sums[1].r; + Gambatte::uint_least32_t gsum = sums[1].g; + Gambatte::uint_least32_t bsum = sums[1].b; + + if (rsum & 0x80000000) rsum = 0; + if (gsum & 0x80000000) gsum = 0; + if (bsum & 0x80000000) bsum = 0; + + rsum <<= 12; + rsum += 0x008000; + gsum >>= 4; + gsum += 0x0080; + bsum += 0x0008; + bsum >>= 4; + + if (rsum > 0xFF0000) rsum = 0xFF0000; + if (gsum > 0x00FF00) gsum = 0x00FF00; + if (bsum > 0x0000FF) bsum = 0x0000FF; + + *dest++ = (rsum & 0xFF0000) | (gsum & 0x00FF00) | bsum; + } + + { + Gambatte::uint_least32_t rsum = sums[1].r * 9; + Gambatte::uint_least32_t gsum = sums[1].g * 9; + Gambatte::uint_least32_t bsum = sums[1].b * 9; + + rsum -= sums[0].r; + gsum -= sums[0].g; + bsum -= sums[0].b; + + rsum += sums[2].r * 9; + gsum += sums[2].g * 9; + bsum += sums[2].b * 9; + + rsum -= sums[3].r; + gsum -= sums[3].g; + bsum -= sums[3].b; + + if (rsum & 0x80000000) rsum = 0; + if (gsum & 0x80000000) gsum = 0; + if (bsum & 0x80000000) bsum = 0; + + rsum <<= 8; + rsum += 0x008000; + gsum >>= 8; + gsum += 0x000080; + bsum += 0x000080; + bsum >>= 8; + + if (rsum > 0xFF0000) rsum = 0xFF0000; + if (gsum > 0x00FF00) gsum = 0x00FF00; + if (bsum > 0x0000FF) bsum = 0x0000FF; + + *dest++ = (rsum & 0xFF0000) | (gsum & 0x00FF00) | bsum; + } + + ++sums; + } +} + +static void filter(Gambatte::uint_least32_t *dline, const unsigned pitch, const Gambatte::uint_least32_t *sline) { + Colorsum sums[163]; + + for (unsigned h = 144; h--;) { + { + const Gambatte::uint_least32_t *s = sline; + Colorsum *sum = sums; + unsigned n = 163; + + while (n--) { + unsigned long pixel = *s; + sum->r = pixel >> 12 & 0x000FF0 ; + pixel <<= 4; + sum->g = pixel & 0x0FF000; + sum->b = pixel & 0x000FF0; + + ++s; + ++sum; + } + } + + merge_columns(dline, sums); + dline += pitch; + + { + const Gambatte::uint_least32_t *s = sline; + Colorsum *sum = sums; + unsigned n = 163; + + while (n--) { + unsigned long pixel = *s; + unsigned long rsum = (pixel >> 16) * 9; + unsigned long gsum = (pixel & 0x00FF00) * 9; + unsigned long bsum = (pixel & 0x0000FF) * 9; + + pixel = s[-1*163]; + rsum -= pixel >> 16; + gsum -= pixel & 0x00FF00; + bsum -= pixel & 0x0000FF; + + pixel = s[1*163]; + rsum += (pixel >> 16) * 9; + gsum += (pixel & 0x00FF00) * 9; + bsum += (pixel & 0x0000FF) * 9; + + pixel = s[2*163]; + rsum -= pixel >> 16; + gsum -= pixel & 0x00FF00; + bsum -= pixel & 0x0000FF; + + sum->r = rsum; + sum->g = gsum; + sum->b = bsum; + + ++s; + ++sum; + } + } + + merge_columns(dline, sums); + dline += pitch; + sline += 163; + } +} + +Catrom2x::Catrom2x() { + buffer = NULL; +} + +Catrom2x::~Catrom2x() { + delete []buffer; +} + +void Catrom2x::init() { + delete []buffer; + + buffer = new Gambatte::uint_least32_t[147 * 163]; + std::memset(buffer, 0, 147ul * 163 * sizeof(Gambatte::uint_least32_t)); +} + +void Catrom2x::outit() { + delete []buffer; + buffer = NULL; +} + +const Gambatte::FilterInfo& Catrom2x::info() { + static Gambatte::FilterInfo fInfo = { "Bicubic Catmull-Rom Spline 2x", 160 * 2, 144 * 2 }; + + return fInfo; +} + +Gambatte::uint_least32_t* Catrom2x::inBuffer() { + return buffer + 164; +} + +unsigned Catrom2x::inPitch() { + return 163; +} + +void Catrom2x::filter(Gambatte::uint_least32_t *const dbuffer, const unsigned pitch) { + ::filter(dbuffer, pitch, buffer + 163); +} diff --git a/supergameboy/libgambatte/src/video/filters/catrom2x.h b/supergameboy/libgambatte/src/video/filters/catrom2x.h new file mode 100644 index 00000000..df657f04 --- /dev/null +++ b/supergameboy/libgambatte/src/video/filters/catrom2x.h @@ -0,0 +1,40 @@ +/*************************************************************************** + * Copyright (C) 2007 by Sindre Aamås * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#ifndef CATROM2X_H +#define CATROM2X_H + +#include "filter.h" + +struct FilterInfo; + +class Catrom2x : public Filter { + Gambatte::uint_least32_t *buffer; + +public: + Catrom2x(); + ~Catrom2x(); + void init(); + void outit(); + const Gambatte::FilterInfo& info(); + void filter(Gambatte::uint_least32_t *dbuffer, unsigned pitch); + Gambatte::uint_least32_t* inBuffer(); + unsigned inPitch(); +}; + +#endif diff --git a/supergameboy/libgambatte/src/video/filters/catrom3x.cpp b/supergameboy/libgambatte/src/video/filters/catrom3x.cpp new file mode 100644 index 00000000..09a03f6a --- /dev/null +++ b/supergameboy/libgambatte/src/video/filters/catrom3x.cpp @@ -0,0 +1,360 @@ +/*************************************************************************** + * Copyright (C) 2007 by Sindre Aam�s * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#include "catrom3x.h" +#include "filterinfo.h" +#include + +struct Colorsum { + Gambatte::uint_least32_t r, g, b; +}; + +static void merge_columns(Gambatte::uint_least32_t *dest, const Colorsum *sums) { + unsigned w = 160; + + while (w--) { + { + Gambatte::uint_least32_t rsum = sums[1].r; + Gambatte::uint_least32_t gsum = sums[1].g; + Gambatte::uint_least32_t bsum = sums[1].b; + + if (rsum & 0x80000000) + rsum = 0; + else if (rsum > 6869) + rsum = 0xFF0000; + else { + rsum *= 607; + rsum <<= 2; + rsum += 0x008000; + rsum &= 0xFF0000; + } + + if (gsum & 0x80000000) + gsum = 0; + else if (gsum > 1758567) + gsum = 0xFF00; + else { + gsum *= 607; + gsum >>= 14; + gsum += 0x000080; + gsum &= 0x00FF00; + } + + if (bsum & 0x80000000) + bsum = 0; + else if (bsum > 6869) + bsum = 0xFF; + else { + bsum *= 607; + bsum += 8192; + bsum >>= 14; + } + + /*rsum/=27; + rsum<<=8; + gsum/=27; + gsum<<=5; + bsum<<=4; + bsum+=27; + bsum/=54; + rsum+=0x008000; + gsum+=0x000080; + + if(rsum>0xFF0000) rsum=0xFF0000; + if(gsum>0x00FF00) gsum=0x00FF00; + if(bsum>0x0000FF) bsum=0x0000FF;*/ + + *dest++ = rsum/*&0xFF0000*/ | gsum/*&0x00FF00*/ | bsum; + } + { + Gambatte::uint_least32_t rsum = sums[1].r * 21; + Gambatte::uint_least32_t gsum = sums[1].g * 21; + Gambatte::uint_least32_t bsum = sums[1].b * 21; + + rsum -= sums[0].r << 1; + gsum -= sums[0].g << 1; + bsum -= sums[0].b << 1; + + rsum += sums[2].r * 9; + gsum += sums[2].g * 9; + bsum += sums[2].b * 9; + + rsum -= sums[3].r; + gsum -= sums[3].g; + bsum -= sums[3].b; + + if (rsum & 0x80000000) + rsum = 0; + else if (rsum > 185578) + rsum = 0xFF0000; + else { + rsum *= 719; + rsum >>= 3; + rsum += 0x008000; + rsum &= 0xFF0000; + } + + if (gsum & 0x80000000) + gsum = 0; + else if (gsum > 47508223) + gsum = 0x00FF00; + else { + gsum >>= 8; + gsum *= 719; + gsum >>= 11; + gsum += 0x000080; + gsum &= 0x00FF00; + } + + if (bsum & 0x80000000) + bsum = 0; + else if (bsum > 185578) + bsum = 0x0000FF; + else { + bsum *= 719; + bsum += 0x040000; + bsum >>= 19; + } + + /*rsum/=729; + rsum<<=8; + gsum/=729; + gsum<<=5; + bsum<<=4; + bsum+=729; + bsum/=1458; + rsum+=0x008000; + gsum+=0x000080; + + if(rsum>0xFF0000) rsum=0xFF0000; + if(gsum>0x00FF00) gsum=0x00FF00; + if(bsum>0x0000FF) bsum=0x0000FF;*/ + + *dest++ = rsum/*&0xFF0000*/ | gsum/*&0x00FF00*/ | bsum; + } + { + Gambatte::uint_least32_t rsum = sums[1].r * 9; + Gambatte::uint_least32_t gsum = sums[1].g * 9; + Gambatte::uint_least32_t bsum = sums[1].b * 9; + + rsum -= sums[0].r; + gsum -= sums[0].g; + bsum -= sums[0].b; + + rsum += sums[2].r * 21; + gsum += sums[2].g * 21; + bsum += sums[2].b * 21; + + rsum -= sums[3].r << 1; + gsum -= sums[3].g << 1; + bsum -= sums[3].b << 1; + + if (rsum & 0x80000000) + rsum = 0; + else if (rsum > 185578) + rsum = 0xFF0000; + else { + rsum *= 719; + rsum >>= 3; + rsum += 0x008000; + rsum &= 0xFF0000; + } + + if (gsum & 0x80000000) + gsum = 0; + else if (gsum > 47508223) + gsum = 0xFF00; + else { + gsum >>= 8; + gsum *= 719; + gsum >>= 11; + gsum += 0x000080; + gsum &= 0x00FF00; + } + + if (bsum & 0x80000000) + bsum = 0; + else if (bsum > 185578) + bsum = 0x0000FF; + else { + bsum *= 719; + bsum += 0x040000; + bsum >>= 19; + } + + /*rsum/=729; + rsum<<=8; + gsum/=729; + gsum<<=5; + bsum<<=4; + bsum+=729; + bsum/=1458; + rsum+=0x008000; + gsum+=0x000080; + + if(rsum>0xFF0000) rsum=0xFF0000; + if(gsum>0x00FF00) gsum=0x00FF00; + if(bsum>0x0000FF) bsum=0x0000FF;*/ + + *dest++ = rsum/*&0xFF0000*/ | gsum/*&0x00FF00*/ | bsum; + } + ++sums; + } +} + +static void filter(Gambatte::uint_least32_t *dline, const unsigned pitch, const Gambatte::uint_least32_t *sline) { + Colorsum sums[163]; + + for (unsigned h = 144; h--;) { + { + const Gambatte::uint_least32_t *s = sline; + Colorsum *sum = sums; + unsigned n = 163; + + while (n--) { + const unsigned long pixel = *s; + sum->r = (pixel >> 16) * 27; + sum->g = (pixel & 0x00FF00) * 27; + sum->b = (pixel & 0x0000FF) * 27; + + ++s; + ++sum; + } + } + + merge_columns(dline, sums); + dline += pitch; + + { + const Gambatte::uint_least32_t *s = sline; + Colorsum *sum = sums; + unsigned n = 163; + + while (n--) { + unsigned long pixel = *s; + unsigned long rsum = (pixel >> 16) * 21; + unsigned long gsum = (pixel & 0x00FF00) * 21; + unsigned long bsum = (pixel & 0x0000FF) * 21; + + pixel = s[-1 * 163]; + rsum -= (pixel >> 16) << 1; + pixel <<= 1; + gsum -= pixel & 0x01FE00; + bsum -= pixel & 0x0001FE; + + pixel = s[1 * 163]; + rsum += (pixel >> 16) * 9; + gsum += (pixel & 0x00FF00) * 9; + bsum += (pixel & 0x0000FF) * 9; + + pixel = s[2 * 163]; + rsum -= pixel >> 16; + gsum -= pixel & 0x00FF00; + bsum -= pixel & 0x0000FF; + + sum->r = rsum; + sum->g = gsum; + sum->b = bsum; + + ++s; + ++sum; + } + } + + merge_columns(dline, sums); + dline += pitch; + + { + const Gambatte::uint_least32_t *s = sline; + Colorsum *sum = sums; + unsigned n = 163; + + while (n--) { + unsigned long pixel = *s; + unsigned long rsum = (pixel >> 16) * 9; + unsigned long gsum = (pixel & 0x00FF00) * 9; + unsigned long bsum = (pixel & 0x0000FF) * 9; + + pixel = s[-1 * 163]; + rsum -= pixel >> 16; + gsum -= pixel & 0x00FF00; + bsum -= pixel & 0x0000FF; + + pixel = s[1 * 163]; + rsum += (pixel >> 16) * 21; + gsum += (pixel & 0x00FF00) * 21; + bsum += (pixel & 0x0000FF) * 21; + + pixel = s[2 * 163]; + rsum -= (pixel >> 16) << 1; + pixel <<= 1; + gsum -= pixel & 0x01FE00; + bsum -= pixel & 0x0001FE; + + sum->r = rsum; + sum->g = gsum; + sum->b = bsum; + + ++s; + ++sum; + } + } + + merge_columns(dline, sums); + dline += pitch; + sline += 163; + } +} + +Catrom3x::Catrom3x() { + buffer = NULL; +} + +Catrom3x::~Catrom3x() { + delete []buffer; +} + +void Catrom3x::init() { + delete []buffer; + + buffer = new Gambatte::uint_least32_t[147 * 163]; + std::memset(buffer, 0, 147ul * 163 * sizeof(Gambatte::uint_least32_t)); +} + +void Catrom3x::outit() { + delete []buffer; + buffer = NULL; +} + +const Gambatte::FilterInfo& Catrom3x::info() { + static Gambatte::FilterInfo fInfo = { "Bicubic Catmull-Rom Spline 3x", 160 * 3, 144 * 3 }; + + return fInfo; +} + +Gambatte::uint_least32_t* Catrom3x::inBuffer() { + return buffer + 164; +} + +unsigned Catrom3x::inPitch() { + return 163; +} + +void Catrom3x::filter(Gambatte::uint_least32_t *const dbuffer, const unsigned pitch) { + ::filter(dbuffer, pitch, buffer + 163); +} diff --git a/supergameboy/libgambatte/src/video/filters/catrom3x.h b/supergameboy/libgambatte/src/video/filters/catrom3x.h new file mode 100644 index 00000000..64f47827 --- /dev/null +++ b/supergameboy/libgambatte/src/video/filters/catrom3x.h @@ -0,0 +1,40 @@ +/*************************************************************************** + * Copyright (C) 2007 by Sindre Aamås * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#ifndef CATROM3X_H +#define CATROM3X_H + +#include "filter.h" + +struct FilterInfo; + +class Catrom3x : public Filter { + Gambatte::uint_least32_t *buffer; + +public: + Catrom3x(); + ~Catrom3x(); + void init(); + void outit(); + const Gambatte::FilterInfo& info(); + void filter(Gambatte::uint_least32_t *dbuffer, unsigned pitch); + Gambatte::uint_least32_t* inBuffer(); + unsigned inPitch(); +}; + +#endif diff --git a/supergameboy/libgambatte/src/video/filters/filter.h b/supergameboy/libgambatte/src/video/filters/filter.h new file mode 100644 index 00000000..72e3bf7d --- /dev/null +++ b/supergameboy/libgambatte/src/video/filters/filter.h @@ -0,0 +1,39 @@ +/*************************************************************************** + * Copyright (C) 2007 by Sindre Aamås * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#ifndef FILTER_H +#define FILTER_H + +#include "int.h" + +namespace Gambatte { +struct FilterInfo; +} + +class Filter { +public: + virtual ~Filter() {} + virtual void init() {}; + virtual void outit() {}; + virtual const Gambatte::FilterInfo& info() = 0; + virtual void filter(Gambatte::uint_least32_t *dbuffer, unsigned pitch) = 0; + virtual Gambatte::uint_least32_t* inBuffer() = 0; + virtual unsigned inPitch() = 0; +}; + +#endif diff --git a/supergameboy/libgambatte/src/video/filters/kreed2xsai.cpp b/supergameboy/libgambatte/src/video/filters/kreed2xsai.cpp new file mode 100644 index 00000000..70c261b3 --- /dev/null +++ b/supergameboy/libgambatte/src/video/filters/kreed2xsai.cpp @@ -0,0 +1,243 @@ +/*************************************************************************** + * Copyright (C) 2007 by Sindre Aam�s * + * aamas@stud.ntnu.no * + * * + * Copyright (C) 1999 Derek Liauw Kie Fa (Kreed) * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#include "kreed2xsai.h" +#include "filterinfo.h" +#include + +static inline int getResult1(const unsigned long a, const unsigned long b, const unsigned long c, const unsigned long d) { + int x = 0; + int y = 0; + int r = 0; + + if (a == c) ++x; + else if (b == c) ++y; + + if (a == d) ++x; + else if (b == d) ++y; + + if (x <= 1) ++r; + + if (y <= 1) --r; + + return r; +} + +static inline int getResult2(const unsigned long a, const unsigned long b, const unsigned long c, const unsigned long d) { + int x = 0; + int y = 0; + int r = 0; + + if (a == c) ++x; + else if (b == c) ++y; + + if (a == d) ++x; + else if (b == d) ++y; + + if (x <= 1) --r; + + if (y <= 1) ++r; + + return r; +} + +static inline unsigned long interpolate(const unsigned long a, const unsigned long b) { + return (a + b - ((a ^ b) & 0x010101)) >> 1; +} + +static inline unsigned long qInterpolate(const unsigned long a, const unsigned long b, const unsigned long c, const unsigned long d) { + const unsigned long lowBits = ((a & 0x030303) + (b & 0x030303) + (c & 0x030303) + (d & 0x030303)) & 0x030303; + + return (a + b + c + d - lowBits) >> 2; +} + +static void filter(Gambatte::uint_least32_t *dstPtr, const unsigned dstPitch, + const Gambatte::uint_least32_t *srcPtr, const unsigned srcPitch, const unsigned width, unsigned height) +{ + while (height--) { + const Gambatte::uint_least32_t *bP = srcPtr; + Gambatte::uint_least32_t *dP = dstPtr; + + for (unsigned finish = width; finish--;) { + register unsigned long colorA, colorB; + unsigned long colorC, colorD, + colorE, colorF, colorG, colorH, + colorI, colorJ, colorK, colorL, + + colorM, colorN, colorO, colorP; + unsigned long product, product1, product2; + + //--------------------------------------- + // Map of the pixels: I|E F|J + // G|A B|K + // H|C D|L + // M|N O|P + colorI = *(bP - srcPitch - 1); + colorE = *(bP - srcPitch); + colorF = *(bP - srcPitch + 1); + colorJ = *(bP - srcPitch + 2); + + colorG = *(bP - 1); + colorA = *(bP); + colorB = *(bP + 1); + colorK = *(bP + 2); + + colorH = *(bP + srcPitch - 1); + colorC = *(bP + srcPitch); + colorD = *(bP + srcPitch + 1); + colorL = *(bP + srcPitch + 2); + + colorM = *(bP + srcPitch * 2 - 1); + colorN = *(bP + srcPitch * 2); + colorO = *(bP + srcPitch * 2 + 1); + colorP = *(bP + srcPitch * 2 + 2); + + if (colorA == colorD && colorB != colorC) { + if ((colorA == colorE && colorB == colorL) || + (colorA == colorC && colorA == colorF + && colorB != colorE && colorB == colorJ)) { + product = colorA; + } else { + product = interpolate(colorA, colorB); + } + + if ((colorA == colorG && colorC == colorO) || + (colorA == colorB && colorA == colorH + && colorG != colorC && colorC == colorM)) { + product1 = colorA; + } else { + product1 = interpolate(colorA, colorC); + } + product2 = colorA; + } else if (colorB == colorC && colorA != colorD) { + if ((colorB == colorF && colorA == colorH) || + (colorB == colorE && colorB == colorD + && colorA != colorF && colorA == colorI)) { + product = colorB; + } else { + product = interpolate(colorA, colorB); + } + + if ((colorC == colorH && colorA == colorF) || + (colorC == colorG && colorC == colorD + && colorA != colorH && colorA == colorI)) { + product1 = colorC; + } else { + product1 = interpolate(colorA, colorC); + } + product2 = colorB; + } else if (colorA == colorD && colorB == colorC) { + if (colorA == colorB) { + product = colorA; + product1 = colorA; + product2 = colorA; + } else { + register int r = 0; + + product1 = interpolate(colorA, colorC); + product = interpolate(colorA, colorB); + + r += getResult1(colorA, colorB, colorG, colorE); + r += getResult2(colorB, colorA, colorK, colorF); + r += getResult2(colorB, colorA, colorH, colorN); + r += getResult1(colorA, colorB, colorL, colorO); + + if (r > 0) + product2 = colorA; + else if (r < 0) + product2 = colorB; + else { + product2 = qInterpolate(colorA, colorB, colorC, colorD); + } + } + } else { + product2 = qInterpolate(colorA, colorB, colorC, colorD); + + if (colorA == colorC && colorA == colorF + && colorB != colorE && colorB == colorJ) { + product = colorA; + } else if (colorB == colorE && colorB == colorD + && colorA != colorF && colorA == colorI) { + product = colorB; + } else { + product = interpolate(colorA, colorB); + } + + if (colorA == colorB && colorA == colorH + && colorG != colorC && colorC == colorM) { + product1 = colorA; + } else if (colorC == colorG && colorC == colorD + && colorA != colorH && colorA == colorI) { + product1 = colorC; + } else { + product1 = interpolate(colorA, colorC); + } + } + *dP = colorA; + *(dP + 1) = product; + *(dP + dstPitch) = product1; + *(dP + dstPitch + 1) = product2; + + ++bP; + dP += 2; + } + + srcPtr += srcPitch; + dstPtr += dstPitch * 2; + } +} + +Kreed_2xSaI::Kreed_2xSaI() { + buffer = NULL; +} + +Kreed_2xSaI::~Kreed_2xSaI() { + delete []buffer; +} + +void Kreed_2xSaI::init() { + delete []buffer; + + buffer = new Gambatte::uint_least32_t[145 * 161]; + std::memset(buffer, 0, 145ul * 161 * sizeof(Gambatte::uint_least32_t)); +} + +void Kreed_2xSaI::outit() { + delete []buffer; + buffer = NULL; +} + +const Gambatte::FilterInfo& Kreed_2xSaI::info() { + static Gambatte::FilterInfo fInfo = { "Kreed's 2xSaI", 160 * 2, 144 * 2 }; + + return fInfo; +} + +Gambatte::uint_least32_t* Kreed_2xSaI::inBuffer() { + return buffer; +} + +unsigned Kreed_2xSaI::inPitch() { + return 161; +} + +void Kreed_2xSaI::filter(Gambatte::uint_least32_t *const dbuffer, const unsigned pitch) { + ::filter(dbuffer, pitch, buffer, 161, 160, 144); +} diff --git a/supergameboy/libgambatte/src/video/filters/kreed2xsai.h b/supergameboy/libgambatte/src/video/filters/kreed2xsai.h new file mode 100644 index 00000000..f2feffc0 --- /dev/null +++ b/supergameboy/libgambatte/src/video/filters/kreed2xsai.h @@ -0,0 +1,40 @@ +/*************************************************************************** + * Copyright (C) 2007 by Sindre Aamås * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#ifndef KREED2XSAI_H +#define KREED2XSAI_H + +#include "filter.h" + +struct FilterInfo; + +class Kreed_2xSaI : public Filter { + Gambatte::uint_least32_t *buffer; + +public: + Kreed_2xSaI(); + ~Kreed_2xSaI(); + void init(); + void outit(); + const Gambatte::FilterInfo& info(); + void filter(Gambatte::uint_least32_t *dbuffer, unsigned pitch); + Gambatte::uint_least32_t* inBuffer(); + unsigned inPitch(); +}; + +#endif diff --git a/supergameboy/libgambatte/src/video/filters/maxsthq2x.cpp b/supergameboy/libgambatte/src/video/filters/maxsthq2x.cpp new file mode 100644 index 00000000..a818d62a --- /dev/null +++ b/supergameboy/libgambatte/src/video/filters/maxsthq2x.cpp @@ -0,0 +1,2875 @@ +/*************************************************************************** + * Copyright (C) 2007 by Sindre Aam�s * + * aamas@stud.ntnu.no * + * * + * Copyright (C) 2003 MaxSt * + * maxst@hiend3d.com * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ +#include "maxsthq2x.h" +#include "filterinfo.h" +#include + +static /*inline*/ unsigned long Interp1(const unsigned long c1, const unsigned long c2) { + const unsigned long lowbits = ((c1 & 0x030303) * 3 + (c2 & 0x030303)) & 0x030303; + + return (c1 * 3 + c2 - lowbits) >> 2; +} + +static /*inline*/ unsigned long Interp2(const unsigned long c1, const unsigned long c2, const unsigned long c3) { + const unsigned long lowbits = ((c1 * 2 & 0x020202) + (c2 & 0x030303) + (c3 & 0x030303)) & 0x030303; + + return (c1 * 2 + c2 + c3 - lowbits) >> 2; +} + +static /*inline*/ unsigned long Interp6(const unsigned long c1, const unsigned long c2, const unsigned long c3) { + const unsigned long lowbits = ((c1 & 0x070707) * 5 + (c2 * 2 & 0x060606) + (c3 & 0x070707)) & 0x070707; + + return ((c1 * 5 + c2 * 2 + c3) - lowbits) >> 3; +} + +static /*inline*/ unsigned long Interp7(const unsigned long c1, const unsigned long c2, const unsigned long c3) { + const unsigned long lowbits = ((c1 & 0x070707) * 6 + (c2 & 0x070707) + (c3 & 0x070707)) & 0x070707; + + return ((c1 * 6 + c2 + c3) - lowbits) >> 3; +} + +static /*inline*/ unsigned long Interp9(unsigned long c1, const unsigned long c2, const unsigned long c3) { + const unsigned lowbits = ((c1 * 2 & 0x070707) + ((c2 & 0x070707) + (c3 & 0x070707)) * 3) & 0x070707; + + return (c1 * 2 + (c2 + c3) * 3 - lowbits) >> 3; +} + +static /*inline*/ unsigned long Interp10(const unsigned long c1, const unsigned long c2, const unsigned long c3) { + const unsigned lowbits = ((c1 & 0x0F0F0F) * 14 + (c2 & 0x0F0F0F) + (c3 & 0x0F0F0F)) & 0x0F0F0F; + + return (c1 * 14 + c2 + c3 - lowbits) >> 4; +} + +#define PIXEL00_0 *pOut = w[5]; +#define PIXEL00_10 *pOut = Interp1(w[5], w[1]); +#define PIXEL00_11 *pOut = Interp1(w[5], w[4]); +#define PIXEL00_12 *pOut = Interp1(w[5], w[2]); +#define PIXEL00_20 *pOut = Interp2(w[5], w[4], w[2]); +#define PIXEL00_21 *pOut = Interp2(w[5], w[1], w[2]); +#define PIXEL00_22 *pOut = Interp2(w[5], w[1], w[4]); +#define PIXEL00_60 *pOut = Interp6(w[5], w[2], w[4]); +#define PIXEL00_61 *pOut = Interp6(w[5], w[4], w[2]); +#define PIXEL00_70 *pOut = Interp7(w[5], w[4], w[2]); +#define PIXEL00_90 *pOut = Interp9(w[5], w[4], w[2]); +#define PIXEL00_100 *pOut = Interp10(w[5], w[4], w[2]); +#define PIXEL01_0 *(pOut+1) = w[5]; +#define PIXEL01_10 *(pOut+1) = Interp1(w[5], w[3]); +#define PIXEL01_11 *(pOut+1) = Interp1(w[5], w[2]); +#define PIXEL01_12 *(pOut+1) = Interp1(w[5], w[6]); +#define PIXEL01_20 *(pOut+1) = Interp2(w[5], w[2], w[6]); +#define PIXEL01_21 *(pOut+1) = Interp2(w[5], w[3], w[6]); +#define PIXEL01_22 *(pOut+1) = Interp2(w[5], w[3], w[2]); +#define PIXEL01_60 *(pOut+1) = Interp6(w[5], w[6], w[2]); +#define PIXEL01_61 *(pOut+1) = Interp6(w[5], w[2], w[6]); +#define PIXEL01_70 *(pOut+1) = Interp7(w[5], w[2], w[6]); +#define PIXEL01_90 *(pOut+1) = Interp9(w[5], w[2], w[6]); +#define PIXEL01_100 *(pOut+1) = Interp10(w[5], w[2], w[6]); +#define PIXEL10_0 *(pOut+dstPitch) = w[5]; +#define PIXEL10_10 *(pOut+dstPitch) = Interp1(w[5], w[7]); +#define PIXEL10_11 *(pOut+dstPitch) = Interp1(w[5], w[8]); +#define PIXEL10_12 *(pOut+dstPitch) = Interp1(w[5], w[4]); +#define PIXEL10_20 *(pOut+dstPitch) = Interp2(w[5], w[8], w[4]); +#define PIXEL10_21 *(pOut+dstPitch) = Interp2(w[5], w[7], w[4]); +#define PIXEL10_22 *(pOut+dstPitch) = Interp2(w[5], w[7], w[8]); +#define PIXEL10_60 *(pOut+dstPitch) = Interp6(w[5], w[4], w[8]); +#define PIXEL10_61 *(pOut+dstPitch) = Interp6(w[5], w[8], w[4]); +#define PIXEL10_70 *(pOut+dstPitch) = Interp7(w[5], w[8], w[4]); +#define PIXEL10_90 *(pOut+dstPitch) = Interp9(w[5], w[8], w[4]); +#define PIXEL10_100 *(pOut+dstPitch) = Interp10(w[5], w[8], w[4]); +#define PIXEL11_0 *(pOut+dstPitch+1) = w[5]; +#define PIXEL11_10 *(pOut+dstPitch+1) = Interp1(w[5], w[9]); +#define PIXEL11_11 *(pOut+dstPitch+1) = Interp1(w[5], w[6]); +#define PIXEL11_12 *(pOut+dstPitch+1) = Interp1(w[5], w[8]); +#define PIXEL11_20 *(pOut+dstPitch+1) = Interp2(w[5], w[6], w[8]); +#define PIXEL11_21 *(pOut+dstPitch+1) = Interp2(w[5], w[9], w[8]); +#define PIXEL11_22 *(pOut+dstPitch+1) = Interp2(w[5], w[9], w[6]); +#define PIXEL11_60 *(pOut+dstPitch+1) = Interp6(w[5], w[8], w[6]); +#define PIXEL11_61 *(pOut+dstPitch+1) = Interp6(w[5], w[6], w[8]); +#define PIXEL11_70 *(pOut+dstPitch+1) = Interp7(w[5], w[6], w[8]); +#define PIXEL11_90 *(pOut+dstPitch+1) = Interp9(w[5], w[6], w[8]); +#define PIXEL11_100 *(pOut+dstPitch+1) = Interp10(w[5], w[6], w[8]); + +static /*inline*/ bool Diff(const unsigned long w1, const unsigned long w2) { + const unsigned rdiff = (w1 >> 16) - (w2 >> 16); + const unsigned gdiff = (w1 >> 8 & 0xFF) - (w2 >> 8 & 0xFF); + const unsigned bdiff = (w1 & 0xFF) - (w2 & 0xFF); + + return rdiff + gdiff + bdiff + 0xC0U > 0xC0U * 2 || + rdiff - bdiff + 0x1CU > 0x1CU * 2 || + gdiff * 2 - rdiff - bdiff + 0x30U > 0x30U * 2; +} + +static void filter(Gambatte::uint_least32_t *pOut, const unsigned dstPitch, + const Gambatte::uint_least32_t *pIn, const unsigned Xres, const unsigned Yres) +{ + unsigned long w[10]; + + // +----+----+----+ + // | | | | + // | w1 | w2 | w3 | + // +----+----+----+ + // | | | | + // | w4 | w5 | w6 | + // +----+----+----+ + // | | | | + // | w7 | w8 | w9 | + // +----+----+----+ + + for (unsigned j = 0; j < Yres; j++) { + const unsigned prevline = j > 0 ? Xres : 0; + const unsigned nextline = j < Yres - 1 ? Xres : 0; + + for (unsigned i = 0; i < Xres; i++) { + w[2] = *(pIn - prevline); + w[5] = *(pIn); + w[8] = *(pIn + nextline); + + if (i > 0) { + w[1] = *(pIn - prevline - 1); + w[4] = *(pIn - 1); + w[7] = *(pIn + nextline - 1); + } else { + w[1] = w[2]; + w[4] = w[5]; + w[7] = w[8]; + } + + if (i < Xres - 1) { + w[3] = *(pIn - prevline + 1); + w[6] = *(pIn + 1); + w[9] = *(pIn + nextline + 1); + } else { + w[3] = w[2]; + w[6] = w[5]; + w[9] = w[8]; + } + + unsigned pattern = 0; + + { + unsigned flag = 1; + + const unsigned r1 = w[5] >> 16; + const unsigned g1 = w[5] >> 8 & 0xFF; + const unsigned b1 = w[5] & 0xFF; + + for (unsigned k = 1; k < 10; ++k) { + if (k == 5) continue; + + if (w[k] != w[5]) { + const unsigned rdiff = r1 - (w[k] >> 16); + const unsigned gdiff = g1 - (w[k] >> 8 & 0xFF); + const unsigned bdiff = b1 - (w[k] & 0xFF); + + if (rdiff + gdiff + bdiff + 0xC0U > 0xC0U * 2 || + rdiff - bdiff + 0x1CU > 0x1CU * 2 || + gdiff * 2 - rdiff - bdiff + 0x30U > 0x30U * 2) + pattern |= flag; + } + + flag <<= 1; + } + } + + switch (pattern) + { + case 0: + case 1: + case 4: + case 32: + case 128: + case 5: + case 132: + case 160: + case 33: + case 129: + case 36: + case 133: + case 164: + case 161: + case 37: + case 165: + { + PIXEL00_20 + PIXEL01_20 + PIXEL10_20 + PIXEL11_20 + break; + } + case 2: + case 34: + case 130: + case 162: + { + PIXEL00_22 + PIXEL01_21 + PIXEL10_20 + PIXEL11_20 + break; + } + case 16: + case 17: + case 48: + case 49: + { + PIXEL00_20 + PIXEL01_22 + PIXEL10_20 + PIXEL11_21 + break; + } + case 64: + case 65: + case 68: + case 69: + { + PIXEL00_20 + PIXEL01_20 + PIXEL10_21 + PIXEL11_22 + break; + } + case 8: + case 12: + case 136: + case 140: + { + PIXEL00_21 + PIXEL01_20 + PIXEL10_22 + PIXEL11_20 + break; + } + case 3: + case 35: + case 131: + case 163: + { + PIXEL00_11 + PIXEL01_21 + PIXEL10_20 + PIXEL11_20 + break; + } + case 6: + case 38: + case 134: + case 166: + { + PIXEL00_22 + PIXEL01_12 + PIXEL10_20 + PIXEL11_20 + break; + } + case 20: + case 21: + case 52: + case 53: + { + PIXEL00_20 + PIXEL01_11 + PIXEL10_20 + PIXEL11_21 + break; + } + case 144: + case 145: + case 176: + case 177: + { + PIXEL00_20 + PIXEL01_22 + PIXEL10_20 + PIXEL11_12 + break; + } + case 192: + case 193: + case 196: + case 197: + { + PIXEL00_20 + PIXEL01_20 + PIXEL10_21 + PIXEL11_11 + break; + } + case 96: + case 97: + case 100: + case 101: + { + PIXEL00_20 + PIXEL01_20 + PIXEL10_12 + PIXEL11_22 + break; + } + case 40: + case 44: + case 168: + case 172: + { + PIXEL00_21 + PIXEL01_20 + PIXEL10_11 + PIXEL11_20 + break; + } + case 9: + case 13: + case 137: + case 141: + { + PIXEL00_12 + PIXEL01_20 + PIXEL10_22 + PIXEL11_20 + break; + } + case 18: + case 50: + { + PIXEL00_22 + if (Diff(w[2], w[6])) + { + PIXEL01_10 + } + else + { + PIXEL01_20 + } + PIXEL10_20 + PIXEL11_21 + break; + } + case 80: + case 81: + { + PIXEL00_20 + PIXEL01_22 + PIXEL10_21 + if (Diff(w[6], w[8])) + { + PIXEL11_10 + } + else + { + PIXEL11_20 + } + break; + } + case 72: + case 76: + { + PIXEL00_21 + PIXEL01_20 + if (Diff(w[8], w[4])) + { + PIXEL10_10 + } + else + { + PIXEL10_20 + } + PIXEL11_22 + break; + } + case 10: + case 138: + { + if (Diff(w[4], w[2])) + { + PIXEL00_10 + } + else + { + PIXEL00_20 + } + PIXEL01_21 + PIXEL10_22 + PIXEL11_20 + break; + } + case 66: + { + PIXEL00_22 + PIXEL01_21 + PIXEL10_21 + PIXEL11_22 + break; + } + case 24: + { + PIXEL00_21 + PIXEL01_22 + PIXEL10_22 + PIXEL11_21 + break; + } + case 7: + case 39: + case 135: + { + PIXEL00_11 + PIXEL01_12 + PIXEL10_20 + PIXEL11_20 + break; + } + case 148: + case 149: + case 180: + { + PIXEL00_20 + PIXEL01_11 + PIXEL10_20 + PIXEL11_12 + break; + } + case 224: + case 228: + case 225: + { + PIXEL00_20 + PIXEL01_20 + PIXEL10_12 + PIXEL11_11 + break; + } + case 41: + case 169: + case 45: + { + PIXEL00_12 + PIXEL01_20 + PIXEL10_11 + PIXEL11_20 + break; + } + case 22: + case 54: + { + PIXEL00_22 + if (Diff(w[2], w[6])) + { + PIXEL01_0 + } + else + { + PIXEL01_20 + } + PIXEL10_20 + PIXEL11_21 + break; + } + case 208: + case 209: + { + PIXEL00_20 + PIXEL01_22 + PIXEL10_21 + if (Diff(w[6], w[8])) + { + PIXEL11_0 + } + else + { + PIXEL11_20 + } + break; + } + case 104: + case 108: + { + PIXEL00_21 + PIXEL01_20 + if (Diff(w[8], w[4])) + { + PIXEL10_0 + } + else + { + PIXEL10_20 + } + PIXEL11_22 + break; + } + case 11: + case 139: + { + if (Diff(w[4], w[2])) + { + PIXEL00_0 + } + else + { + PIXEL00_20 + } + PIXEL01_21 + PIXEL10_22 + PIXEL11_20 + break; + } + case 19: + case 51: + { + if (Diff(w[2], w[6])) + { + PIXEL00_11 + PIXEL01_10 + } + else + { + PIXEL00_60 + PIXEL01_90 + } + PIXEL10_20 + PIXEL11_21 + break; + } + case 146: + case 178: + { + PIXEL00_22 + if (Diff(w[2], w[6])) + { + PIXEL01_10 + PIXEL11_12 + } + else + { + PIXEL01_90 + PIXEL11_61 + } + PIXEL10_20 + break; + } + case 84: + case 85: + { + PIXEL00_20 + if (Diff(w[6], w[8])) + { + PIXEL01_11 + PIXEL11_10 + } + else + { + PIXEL01_60 + PIXEL11_90 + } + PIXEL10_21 + break; + } + case 112: + case 113: + { + PIXEL00_20 + PIXEL01_22 + if (Diff(w[6], w[8])) + { + PIXEL10_12 + PIXEL11_10 + } + else + { + PIXEL10_61 + PIXEL11_90 + } + break; + } + case 200: + case 204: + { + PIXEL00_21 + PIXEL01_20 + if (Diff(w[8], w[4])) + { + PIXEL10_10 + PIXEL11_11 + } + else + { + PIXEL10_90 + PIXEL11_60 + } + break; + } + case 73: + case 77: + { + if (Diff(w[8], w[4])) + { + PIXEL00_12 + PIXEL10_10 + } + else + { + PIXEL00_61 + PIXEL10_90 + } + PIXEL01_20 + PIXEL11_22 + break; + } + case 42: + case 170: + { + if (Diff(w[4], w[2])) + { + PIXEL00_10 + PIXEL10_11 + } + else + { + PIXEL00_90 + PIXEL10_60 + } + PIXEL01_21 + PIXEL11_20 + break; + } + case 14: + case 142: + { + if (Diff(w[4], w[2])) + { + PIXEL00_10 + PIXEL01_12 + } + else + { + PIXEL00_90 + PIXEL01_61 + } + PIXEL10_22 + PIXEL11_20 + break; + } + case 67: + { + PIXEL00_11 + PIXEL01_21 + PIXEL10_21 + PIXEL11_22 + break; + } + case 70: + { + PIXEL00_22 + PIXEL01_12 + PIXEL10_21 + PIXEL11_22 + break; + } + case 28: + { + PIXEL00_21 + PIXEL01_11 + PIXEL10_22 + PIXEL11_21 + break; + } + case 152: + { + PIXEL00_21 + PIXEL01_22 + PIXEL10_22 + PIXEL11_12 + break; + } + case 194: + { + PIXEL00_22 + PIXEL01_21 + PIXEL10_21 + PIXEL11_11 + break; + } + case 98: + { + PIXEL00_22 + PIXEL01_21 + PIXEL10_12 + PIXEL11_22 + break; + } + case 56: + { + PIXEL00_21 + PIXEL01_22 + PIXEL10_11 + PIXEL11_21 + break; + } + case 25: + { + PIXEL00_12 + PIXEL01_22 + PIXEL10_22 + PIXEL11_21 + break; + } + case 26: + case 31: + { + if (Diff(w[4], w[2])) + { + PIXEL00_0 + } + else + { + PIXEL00_20 + } + if (Diff(w[2], w[6])) + { + PIXEL01_0 + } + else + { + PIXEL01_20 + } + PIXEL10_22 + PIXEL11_21 + break; + } + case 82: + case 214: + { + PIXEL00_22 + if (Diff(w[2], w[6])) + { + PIXEL01_0 + } + else + { + PIXEL01_20 + } + PIXEL10_21 + if (Diff(w[6], w[8])) + { + PIXEL11_0 + } + else + { + PIXEL11_20 + } + break; + } + case 88: + case 248: + { + PIXEL00_21 + PIXEL01_22 + if (Diff(w[8], w[4])) + { + PIXEL10_0 + } + else + { + PIXEL10_20 + } + if (Diff(w[6], w[8])) + { + PIXEL11_0 + } + else + { + PIXEL11_20 + } + break; + } + case 74: + case 107: + { + if (Diff(w[4], w[2])) + { + PIXEL00_0 + } + else + { + PIXEL00_20 + } + PIXEL01_21 + if (Diff(w[8], w[4])) + { + PIXEL10_0 + } + else + { + PIXEL10_20 + } + PIXEL11_22 + break; + } + case 27: + { + if (Diff(w[4], w[2])) + { + PIXEL00_0 + } + else + { + PIXEL00_20 + } + PIXEL01_10 + PIXEL10_22 + PIXEL11_21 + break; + } + case 86: + { + PIXEL00_22 + if (Diff(w[2], w[6])) + { + PIXEL01_0 + } + else + { + PIXEL01_20 + } + PIXEL10_21 + PIXEL11_10 + break; + } + case 216: + { + PIXEL00_21 + PIXEL01_22 + PIXEL10_10 + if (Diff(w[6], w[8])) + { + PIXEL11_0 + } + else + { + PIXEL11_20 + } + break; + } + case 106: + { + PIXEL00_10 + PIXEL01_21 + if (Diff(w[8], w[4])) + { + PIXEL10_0 + } + else + { + PIXEL10_20 + } + PIXEL11_22 + break; + } + case 30: + { + PIXEL00_10 + if (Diff(w[2], w[6])) + { + PIXEL01_0 + } + else + { + PIXEL01_20 + } + PIXEL10_22 + PIXEL11_21 + break; + } + case 210: + { + PIXEL00_22 + PIXEL01_10 + PIXEL10_21 + if (Diff(w[6], w[8])) + { + PIXEL11_0 + } + else + { + PIXEL11_20 + } + break; + } + case 120: + { + PIXEL00_21 + PIXEL01_22 + if (Diff(w[8], w[4])) + { + PIXEL10_0 + } + else + { + PIXEL10_20 + } + PIXEL11_10 + break; + } + case 75: + { + if (Diff(w[4], w[2])) + { + PIXEL00_0 + } + else + { + PIXEL00_20 + } + PIXEL01_21 + PIXEL10_10 + PIXEL11_22 + break; + } + case 29: + { + PIXEL00_12 + PIXEL01_11 + PIXEL10_22 + PIXEL11_21 + break; + } + case 198: + { + PIXEL00_22 + PIXEL01_12 + PIXEL10_21 + PIXEL11_11 + break; + } + case 184: + { + PIXEL00_21 + PIXEL01_22 + PIXEL10_11 + PIXEL11_12 + break; + } + case 99: + { + PIXEL00_11 + PIXEL01_21 + PIXEL10_12 + PIXEL11_22 + break; + } + case 57: + { + PIXEL00_12 + PIXEL01_22 + PIXEL10_11 + PIXEL11_21 + break; + } + case 71: + { + PIXEL00_11 + PIXEL01_12 + PIXEL10_21 + PIXEL11_22 + break; + } + case 156: + { + PIXEL00_21 + PIXEL01_11 + PIXEL10_22 + PIXEL11_12 + break; + } + case 226: + { + PIXEL00_22 + PIXEL01_21 + PIXEL10_12 + PIXEL11_11 + break; + } + case 60: + { + PIXEL00_21 + PIXEL01_11 + PIXEL10_11 + PIXEL11_21 + break; + } + case 195: + { + PIXEL00_11 + PIXEL01_21 + PIXEL10_21 + PIXEL11_11 + break; + } + case 102: + { + PIXEL00_22 + PIXEL01_12 + PIXEL10_12 + PIXEL11_22 + break; + } + case 153: + { + PIXEL00_12 + PIXEL01_22 + PIXEL10_22 + PIXEL11_12 + break; + } + case 58: + { + if (Diff(w[4], w[2])) + { + PIXEL00_10 + } + else + { + PIXEL00_70 + } + if (Diff(w[2], w[6])) + { + PIXEL01_10 + } + else + { + PIXEL01_70 + } + PIXEL10_11 + PIXEL11_21 + break; + } + case 83: + { + PIXEL00_11 + if (Diff(w[2], w[6])) + { + PIXEL01_10 + } + else + { + PIXEL01_70 + } + PIXEL10_21 + if (Diff(w[6], w[8])) + { + PIXEL11_10 + } + else + { + PIXEL11_70 + } + break; + } + case 92: + { + PIXEL00_21 + PIXEL01_11 + if (Diff(w[8], w[4])) + { + PIXEL10_10 + } + else + { + PIXEL10_70 + } + if (Diff(w[6], w[8])) + { + PIXEL11_10 + } + else + { + PIXEL11_70 + } + break; + } + case 202: + { + if (Diff(w[4], w[2])) + { + PIXEL00_10 + } + else + { + PIXEL00_70 + } + PIXEL01_21 + if (Diff(w[8], w[4])) + { + PIXEL10_10 + } + else + { + PIXEL10_70 + } + PIXEL11_11 + break; + } + case 78: + { + if (Diff(w[4], w[2])) + { + PIXEL00_10 + } + else + { + PIXEL00_70 + } + PIXEL01_12 + if (Diff(w[8], w[4])) + { + PIXEL10_10 + } + else + { + PIXEL10_70 + } + PIXEL11_22 + break; + } + case 154: + { + if (Diff(w[4], w[2])) + { + PIXEL00_10 + } + else + { + PIXEL00_70 + } + if (Diff(w[2], w[6])) + { + PIXEL01_10 + } + else + { + PIXEL01_70 + } + PIXEL10_22 + PIXEL11_12 + break; + } + case 114: + { + PIXEL00_22 + if (Diff(w[2], w[6])) + { + PIXEL01_10 + } + else + { + PIXEL01_70 + } + PIXEL10_12 + if (Diff(w[6], w[8])) + { + PIXEL11_10 + } + else + { + PIXEL11_70 + } + break; + } + case 89: + { + PIXEL00_12 + PIXEL01_22 + if (Diff(w[8], w[4])) + { + PIXEL10_10 + } + else + { + PIXEL10_70 + } + if (Diff(w[6], w[8])) + { + PIXEL11_10 + } + else + { + PIXEL11_70 + } + break; + } + case 90: + { + if (Diff(w[4], w[2])) + { + PIXEL00_10 + } + else + { + PIXEL00_70 + } + if (Diff(w[2], w[6])) + { + PIXEL01_10 + } + else + { + PIXEL01_70 + } + if (Diff(w[8], w[4])) + { + PIXEL10_10 + } + else + { + PIXEL10_70 + } + if (Diff(w[6], w[8])) + { + PIXEL11_10 + } + else + { + PIXEL11_70 + } + break; + } + case 55: + case 23: + { + if (Diff(w[2], w[6])) + { + PIXEL00_11 + PIXEL01_0 + } + else + { + PIXEL00_60 + PIXEL01_90 + } + PIXEL10_20 + PIXEL11_21 + break; + } + case 182: + case 150: + { + PIXEL00_22 + if (Diff(w[2], w[6])) + { + PIXEL01_0 + PIXEL11_12 + } + else + { + PIXEL01_90 + PIXEL11_61 + } + PIXEL10_20 + break; + } + case 213: + case 212: + { + PIXEL00_20 + if (Diff(w[6], w[8])) + { + PIXEL01_11 + PIXEL11_0 + } + else + { + PIXEL01_60 + PIXEL11_90 + } + PIXEL10_21 + break; + } + case 241: + case 240: + { + PIXEL00_20 + PIXEL01_22 + if (Diff(w[6], w[8])) + { + PIXEL10_12 + PIXEL11_0 + } + else + { + PIXEL10_61 + PIXEL11_90 + } + break; + } + case 236: + case 232: + { + PIXEL00_21 + PIXEL01_20 + if (Diff(w[8], w[4])) + { + PIXEL10_0 + PIXEL11_11 + } + else + { + PIXEL10_90 + PIXEL11_60 + } + break; + } + case 109: + case 105: + { + if (Diff(w[8], w[4])) + { + PIXEL00_12 + PIXEL10_0 + } + else + { + PIXEL00_61 + PIXEL10_90 + } + PIXEL01_20 + PIXEL11_22 + break; + } + case 171: + case 43: + { + if (Diff(w[4], w[2])) + { + PIXEL00_0 + PIXEL10_11 + } + else + { + PIXEL00_90 + PIXEL10_60 + } + PIXEL01_21 + PIXEL11_20 + break; + } + case 143: + case 15: + { + if (Diff(w[4], w[2])) + { + PIXEL00_0 + PIXEL01_12 + } + else + { + PIXEL00_90 + PIXEL01_61 + } + PIXEL10_22 + PIXEL11_20 + break; + } + case 124: + { + PIXEL00_21 + PIXEL01_11 + if (Diff(w[8], w[4])) + { + PIXEL10_0 + } + else + { + PIXEL10_20 + } + PIXEL11_10 + break; + } + case 203: + { + if (Diff(w[4], w[2])) + { + PIXEL00_0 + } + else + { + PIXEL00_20 + } + PIXEL01_21 + PIXEL10_10 + PIXEL11_11 + break; + } + case 62: + { + PIXEL00_10 + if (Diff(w[2], w[6])) + { + PIXEL01_0 + } + else + { + PIXEL01_20 + } + PIXEL10_11 + PIXEL11_21 + break; + } + case 211: + { + PIXEL00_11 + PIXEL01_10 + PIXEL10_21 + if (Diff(w[6], w[8])) + { + PIXEL11_0 + } + else + { + PIXEL11_20 + } + break; + } + case 118: + { + PIXEL00_22 + if (Diff(w[2], w[6])) + { + PIXEL01_0 + } + else + { + PIXEL01_20 + } + PIXEL10_12 + PIXEL11_10 + break; + } + case 217: + { + PIXEL00_12 + PIXEL01_22 + PIXEL10_10 + if (Diff(w[6], w[8])) + { + PIXEL11_0 + } + else + { + PIXEL11_20 + } + break; + } + case 110: + { + PIXEL00_10 + PIXEL01_12 + if (Diff(w[8], w[4])) + { + PIXEL10_0 + } + else + { + PIXEL10_20 + } + PIXEL11_22 + break; + } + case 155: + { + if (Diff(w[4], w[2])) + { + PIXEL00_0 + } + else + { + PIXEL00_20 + } + PIXEL01_10 + PIXEL10_22 + PIXEL11_12 + break; + } + case 188: + { + PIXEL00_21 + PIXEL01_11 + PIXEL10_11 + PIXEL11_12 + break; + } + case 185: + { + PIXEL00_12 + PIXEL01_22 + PIXEL10_11 + PIXEL11_12 + break; + } + case 61: + { + PIXEL00_12 + PIXEL01_11 + PIXEL10_11 + PIXEL11_21 + break; + } + case 157: + { + PIXEL00_12 + PIXEL01_11 + PIXEL10_22 + PIXEL11_12 + break; + } + case 103: + { + PIXEL00_11 + PIXEL01_12 + PIXEL10_12 + PIXEL11_22 + break; + } + case 227: + { + PIXEL00_11 + PIXEL01_21 + PIXEL10_12 + PIXEL11_11 + break; + } + case 230: + { + PIXEL00_22 + PIXEL01_12 + PIXEL10_12 + PIXEL11_11 + break; + } + case 199: + { + PIXEL00_11 + PIXEL01_12 + PIXEL10_21 + PIXEL11_11 + break; + } + case 220: + { + PIXEL00_21 + PIXEL01_11 + if (Diff(w[8], w[4])) + { + PIXEL10_10 + } + else + { + PIXEL10_70 + } + if (Diff(w[6], w[8])) + { + PIXEL11_0 + } + else + { + PIXEL11_20 + } + break; + } + case 158: + { + if (Diff(w[4], w[2])) + { + PIXEL00_10 + } + else + { + PIXEL00_70 + } + if (Diff(w[2], w[6])) + { + PIXEL01_0 + } + else + { + PIXEL01_20 + } + PIXEL10_22 + PIXEL11_12 + break; + } + case 234: + { + if (Diff(w[4], w[2])) + { + PIXEL00_10 + } + else + { + PIXEL00_70 + } + PIXEL01_21 + if (Diff(w[8], w[4])) + { + PIXEL10_0 + } + else + { + PIXEL10_20 + } + PIXEL11_11 + break; + } + case 242: + { + PIXEL00_22 + if (Diff(w[2], w[6])) + { + PIXEL01_10 + } + else + { + PIXEL01_70 + } + PIXEL10_12 + if (Diff(w[6], w[8])) + { + PIXEL11_0 + } + else + { + PIXEL11_20 + } + break; + } + case 59: + { + if (Diff(w[4], w[2])) + { + PIXEL00_0 + } + else + { + PIXEL00_20 + } + if (Diff(w[2], w[6])) + { + PIXEL01_10 + } + else + { + PIXEL01_70 + } + PIXEL10_11 + PIXEL11_21 + break; + } + case 121: + { + PIXEL00_12 + PIXEL01_22 + if (Diff(w[8], w[4])) + { + PIXEL10_0 + } + else + { + PIXEL10_20 + } + if (Diff(w[6], w[8])) + { + PIXEL11_10 + } + else + { + PIXEL11_70 + } + break; + } + case 87: + { + PIXEL00_11 + if (Diff(w[2], w[6])) + { + PIXEL01_0 + } + else + { + PIXEL01_20 + } + PIXEL10_21 + if (Diff(w[6], w[8])) + { + PIXEL11_10 + } + else + { + PIXEL11_70 + } + break; + } + case 79: + { + if (Diff(w[4], w[2])) + { + PIXEL00_0 + } + else + { + PIXEL00_20 + } + PIXEL01_12 + if (Diff(w[8], w[4])) + { + PIXEL10_10 + } + else + { + PIXEL10_70 + } + PIXEL11_22 + break; + } + case 122: + { + if (Diff(w[4], w[2])) + { + PIXEL00_10 + } + else + { + PIXEL00_70 + } + if (Diff(w[2], w[6])) + { + PIXEL01_10 + } + else + { + PIXEL01_70 + } + if (Diff(w[8], w[4])) + { + PIXEL10_0 + } + else + { + PIXEL10_20 + } + if (Diff(w[6], w[8])) + { + PIXEL11_10 + } + else + { + PIXEL11_70 + } + break; + } + case 94: + { + if (Diff(w[4], w[2])) + { + PIXEL00_10 + } + else + { + PIXEL00_70 + } + if (Diff(w[2], w[6])) + { + PIXEL01_0 + } + else + { + PIXEL01_20 + } + if (Diff(w[8], w[4])) + { + PIXEL10_10 + } + else + { + PIXEL10_70 + } + if (Diff(w[6], w[8])) + { + PIXEL11_10 + } + else + { + PIXEL11_70 + } + break; + } + case 218: + { + if (Diff(w[4], w[2])) + { + PIXEL00_10 + } + else + { + PIXEL00_70 + } + if (Diff(w[2], w[6])) + { + PIXEL01_10 + } + else + { + PIXEL01_70 + } + if (Diff(w[8], w[4])) + { + PIXEL10_10 + } + else + { + PIXEL10_70 + } + if (Diff(w[6], w[8])) + { + PIXEL11_0 + } + else + { + PIXEL11_20 + } + break; + } + case 91: + { + if (Diff(w[4], w[2])) + { + PIXEL00_0 + } + else + { + PIXEL00_20 + } + if (Diff(w[2], w[6])) + { + PIXEL01_10 + } + else + { + PIXEL01_70 + } + if (Diff(w[8], w[4])) + { + PIXEL10_10 + } + else + { + PIXEL10_70 + } + if (Diff(w[6], w[8])) + { + PIXEL11_10 + } + else + { + PIXEL11_70 + } + break; + } + case 229: + { + PIXEL00_20 + PIXEL01_20 + PIXEL10_12 + PIXEL11_11 + break; + } + case 167: + { + PIXEL00_11 + PIXEL01_12 + PIXEL10_20 + PIXEL11_20 + break; + } + case 173: + { + PIXEL00_12 + PIXEL01_20 + PIXEL10_11 + PIXEL11_20 + break; + } + case 181: + { + PIXEL00_20 + PIXEL01_11 + PIXEL10_20 + PIXEL11_12 + break; + } + case 186: + { + if (Diff(w[4], w[2])) + { + PIXEL00_10 + } + else + { + PIXEL00_70 + } + if (Diff(w[2], w[6])) + { + PIXEL01_10 + } + else + { + PIXEL01_70 + } + PIXEL10_11 + PIXEL11_12 + break; + } + case 115: + { + PIXEL00_11 + if (Diff(w[2], w[6])) + { + PIXEL01_10 + } + else + { + PIXEL01_70 + } + PIXEL10_12 + if (Diff(w[6], w[8])) + { + PIXEL11_10 + } + else + { + PIXEL11_70 + } + break; + } + case 93: + { + PIXEL00_12 + PIXEL01_11 + if (Diff(w[8], w[4])) + { + PIXEL10_10 + } + else + { + PIXEL10_70 + } + if (Diff(w[6], w[8])) + { + PIXEL11_10 + } + else + { + PIXEL11_70 + } + break; + } + case 206: + { + if (Diff(w[4], w[2])) + { + PIXEL00_10 + } + else + { + PIXEL00_70 + } + PIXEL01_12 + if (Diff(w[8], w[4])) + { + PIXEL10_10 + } + else + { + PIXEL10_70 + } + PIXEL11_11 + break; + } + case 205: + case 201: + { + PIXEL00_12 + PIXEL01_20 + if (Diff(w[8], w[4])) + { + PIXEL10_10 + } + else + { + PIXEL10_70 + } + PIXEL11_11 + break; + } + case 174: + case 46: + { + if (Diff(w[4], w[2])) + { + PIXEL00_10 + } + else + { + PIXEL00_70 + } + PIXEL01_12 + PIXEL10_11 + PIXEL11_20 + break; + } + case 179: + case 147: + { + PIXEL00_11 + if (Diff(w[2], w[6])) + { + PIXEL01_10 + } + else + { + PIXEL01_70 + } + PIXEL10_20 + PIXEL11_12 + break; + } + case 117: + case 116: + { + PIXEL00_20 + PIXEL01_11 + PIXEL10_12 + if (Diff(w[6], w[8])) + { + PIXEL11_10 + } + else + { + PIXEL11_70 + } + break; + } + case 189: + { + PIXEL00_12 + PIXEL01_11 + PIXEL10_11 + PIXEL11_12 + break; + } + case 231: + { + PIXEL00_11 + PIXEL01_12 + PIXEL10_12 + PIXEL11_11 + break; + } + case 126: + { + PIXEL00_10 + if (Diff(w[2], w[6])) + { + PIXEL01_0 + } + else + { + PIXEL01_20 + } + if (Diff(w[8], w[4])) + { + PIXEL10_0 + } + else + { + PIXEL10_20 + } + PIXEL11_10 + break; + } + case 219: + { + if (Diff(w[4], w[2])) + { + PIXEL00_0 + } + else + { + PIXEL00_20 + } + PIXEL01_10 + PIXEL10_10 + if (Diff(w[6], w[8])) + { + PIXEL11_0 + } + else + { + PIXEL11_20 + } + break; + } + case 125: + { + if (Diff(w[8], w[4])) + { + PIXEL00_12 + PIXEL10_0 + } + else + { + PIXEL00_61 + PIXEL10_90 + } + PIXEL01_11 + PIXEL11_10 + break; + } + case 221: + { + PIXEL00_12 + if (Diff(w[6], w[8])) + { + PIXEL01_11 + PIXEL11_0 + } + else + { + PIXEL01_60 + PIXEL11_90 + } + PIXEL10_10 + break; + } + case 207: + { + if (Diff(w[4], w[2])) + { + PIXEL00_0 + PIXEL01_12 + } + else + { + PIXEL00_90 + PIXEL01_61 + } + PIXEL10_10 + PIXEL11_11 + break; + } + case 238: + { + PIXEL00_10 + PIXEL01_12 + if (Diff(w[8], w[4])) + { + PIXEL10_0 + PIXEL11_11 + } + else + { + PIXEL10_90 + PIXEL11_60 + } + break; + } + case 190: + { + PIXEL00_10 + if (Diff(w[2], w[6])) + { + PIXEL01_0 + PIXEL11_12 + } + else + { + PIXEL01_90 + PIXEL11_61 + } + PIXEL10_11 + break; + } + case 187: + { + if (Diff(w[4], w[2])) + { + PIXEL00_0 + PIXEL10_11 + } + else + { + PIXEL00_90 + PIXEL10_60 + } + PIXEL01_10 + PIXEL11_12 + break; + } + case 243: + { + PIXEL00_11 + PIXEL01_10 + if (Diff(w[6], w[8])) + { + PIXEL10_12 + PIXEL11_0 + } + else + { + PIXEL10_61 + PIXEL11_90 + } + break; + } + case 119: + { + if (Diff(w[2], w[6])) + { + PIXEL00_11 + PIXEL01_0 + } + else + { + PIXEL00_60 + PIXEL01_90 + } + PIXEL10_12 + PIXEL11_10 + break; + } + case 237: + case 233: + { + PIXEL00_12 + PIXEL01_20 + if (Diff(w[8], w[4])) + { + PIXEL10_0 + } + else + { + PIXEL10_100 + } + PIXEL11_11 + break; + } + case 175: + case 47: + { + if (Diff(w[4], w[2])) + { + PIXEL00_0 + } + else + { + PIXEL00_100 + } + PIXEL01_12 + PIXEL10_11 + PIXEL11_20 + break; + } + case 183: + case 151: + { + PIXEL00_11 + if (Diff(w[2], w[6])) + { + PIXEL01_0 + } + else + { + PIXEL01_100 + } + PIXEL10_20 + PIXEL11_12 + break; + } + case 245: + case 244: + { + PIXEL00_20 + PIXEL01_11 + PIXEL10_12 + if (Diff(w[6], w[8])) + { + PIXEL11_0 + } + else + { + PIXEL11_100 + } + break; + } + case 250: + { + PIXEL00_10 + PIXEL01_10 + if (Diff(w[8], w[4])) + { + PIXEL10_0 + } + else + { + PIXEL10_20 + } + if (Diff(w[6], w[8])) + { + PIXEL11_0 + } + else + { + PIXEL11_20 + } + break; + } + case 123: + { + if (Diff(w[4], w[2])) + { + PIXEL00_0 + } + else + { + PIXEL00_20 + } + PIXEL01_10 + if (Diff(w[8], w[4])) + { + PIXEL10_0 + } + else + { + PIXEL10_20 + } + PIXEL11_10 + break; + } + case 95: + { + if (Diff(w[4], w[2])) + { + PIXEL00_0 + } + else + { + PIXEL00_20 + } + if (Diff(w[2], w[6])) + { + PIXEL01_0 + } + else + { + PIXEL01_20 + } + PIXEL10_10 + PIXEL11_10 + break; + } + case 222: + { + PIXEL00_10 + if (Diff(w[2], w[6])) + { + PIXEL01_0 + } + else + { + PIXEL01_20 + } + PIXEL10_10 + if (Diff(w[6], w[8])) + { + PIXEL11_0 + } + else + { + PIXEL11_20 + } + break; + } + case 252: + { + PIXEL00_21 + PIXEL01_11 + if (Diff(w[8], w[4])) + { + PIXEL10_0 + } + else + { + PIXEL10_20 + } + if (Diff(w[6], w[8])) + { + PIXEL11_0 + } + else + { + PIXEL11_100 + } + break; + } + case 249: + { + PIXEL00_12 + PIXEL01_22 + if (Diff(w[8], w[4])) + { + PIXEL10_0 + } + else + { + PIXEL10_100 + } + if (Diff(w[6], w[8])) + { + PIXEL11_0 + } + else + { + PIXEL11_20 + } + break; + } + case 235: + { + if (Diff(w[4], w[2])) + { + PIXEL00_0 + } + else + { + PIXEL00_20 + } + PIXEL01_21 + if (Diff(w[8], w[4])) + { + PIXEL10_0 + } + else + { + PIXEL10_100 + } + PIXEL11_11 + break; + } + case 111: + { + if (Diff(w[4], w[2])) + { + PIXEL00_0 + } + else + { + PIXEL00_100 + } + PIXEL01_12 + if (Diff(w[8], w[4])) + { + PIXEL10_0 + } + else + { + PIXEL10_20 + } + PIXEL11_22 + break; + } + case 63: + { + if (Diff(w[4], w[2])) + { + PIXEL00_0 + } + else + { + PIXEL00_100 + } + if (Diff(w[2], w[6])) + { + PIXEL01_0 + } + else + { + PIXEL01_20 + } + PIXEL10_11 + PIXEL11_21 + break; + } + case 159: + { + if (Diff(w[4], w[2])) + { + PIXEL00_0 + } + else + { + PIXEL00_20 + } + if (Diff(w[2], w[6])) + { + PIXEL01_0 + } + else + { + PIXEL01_100 + } + PIXEL10_22 + PIXEL11_12 + break; + } + case 215: + { + PIXEL00_11 + if (Diff(w[2], w[6])) + { + PIXEL01_0 + } + else + { + PIXEL01_100 + } + PIXEL10_21 + if (Diff(w[6], w[8])) + { + PIXEL11_0 + } + else + { + PIXEL11_20 + } + break; + } + case 246: + { + PIXEL00_22 + if (Diff(w[2], w[6])) + { + PIXEL01_0 + } + else + { + PIXEL01_20 + } + PIXEL10_12 + if (Diff(w[6], w[8])) + { + PIXEL11_0 + } + else + { + PIXEL11_100 + } + break; + } + case 254: + { + PIXEL00_10 + if (Diff(w[2], w[6])) + { + PIXEL01_0 + } + else + { + PIXEL01_20 + } + if (Diff(w[8], w[4])) + { + PIXEL10_0 + } + else + { + PIXEL10_20 + } + if (Diff(w[6], w[8])) + { + PIXEL11_0 + } + else + { + PIXEL11_100 + } + break; + } + case 253: + { + PIXEL00_12 + PIXEL01_11 + if (Diff(w[8], w[4])) + { + PIXEL10_0 + } + else + { + PIXEL10_100 + } + if (Diff(w[6], w[8])) + { + PIXEL11_0 + } + else + { + PIXEL11_100 + } + break; + } + case 251: + { + if (Diff(w[4], w[2])) + { + PIXEL00_0 + } + else + { + PIXEL00_20 + } + PIXEL01_10 + if (Diff(w[8], w[4])) + { + PIXEL10_0 + } + else + { + PIXEL10_100 + } + if (Diff(w[6], w[8])) + { + PIXEL11_0 + } + else + { + PIXEL11_20 + } + break; + } + case 239: + { + if (Diff(w[4], w[2])) + { + PIXEL00_0 + } + else + { + PIXEL00_100 + } + PIXEL01_12 + if (Diff(w[8], w[4])) + { + PIXEL10_0 + } + else + { + PIXEL10_100 + } + PIXEL11_11 + break; + } + case 127: + { + if (Diff(w[4], w[2])) + { + PIXEL00_0 + } + else + { + PIXEL00_100 + } + if (Diff(w[2], w[6])) + { + PIXEL01_0 + } + else + { + PIXEL01_20 + } + if (Diff(w[8], w[4])) + { + PIXEL10_0 + } + else + { + PIXEL10_20 + } + PIXEL11_10 + break; + } + case 191: + { + if (Diff(w[4], w[2])) + { + PIXEL00_0 + } + else + { + PIXEL00_100 + } + if (Diff(w[2], w[6])) + { + PIXEL01_0 + } + else + { + PIXEL01_100 + } + PIXEL10_11 + PIXEL11_12 + break; + } + case 223: + { + if (Diff(w[4], w[2])) + { + PIXEL00_0 + } + else + { + PIXEL00_20 + } + if (Diff(w[2], w[6])) + { + PIXEL01_0 + } + else + { + PIXEL01_100 + } + PIXEL10_10 + if (Diff(w[6], w[8])) + { + PIXEL11_0 + } + else + { + PIXEL11_20 + } + break; + } + case 247: + { + PIXEL00_11 + if (Diff(w[2], w[6])) + { + PIXEL01_0 + } + else + { + PIXEL01_100 + } + PIXEL10_12 + if (Diff(w[6], w[8])) + { + PIXEL11_0 + } + else + { + PIXEL11_100 + } + break; + } + case 255: + { + if (Diff(w[4], w[2])) + { + PIXEL00_0 + } + else + { + PIXEL00_100 + } + if (Diff(w[2], w[6])) + { + PIXEL01_0 + } + else + { + PIXEL01_100 + } + if (Diff(w[8], w[4])) + { + PIXEL10_0 + } + else + { + PIXEL10_100 + } + if (Diff(w[6], w[8])) + { + PIXEL11_0 + } + else + { + PIXEL11_100 + } + break; + } + } + ++pIn; + pOut += 2; + } + pOut += dstPitch * 2 - Xres * 2; + } +} + +MaxSt_Hq2x::MaxSt_Hq2x() { + buffer = NULL; +} + +MaxSt_Hq2x::~MaxSt_Hq2x() { + outit(); +} + +void MaxSt_Hq2x::init() { + delete []buffer; + buffer = new Gambatte::uint_least32_t[144 * 160]; +} + +void MaxSt_Hq2x::outit() { + delete []buffer; + buffer = NULL; +} + +const Gambatte::FilterInfo& MaxSt_Hq2x::info() { + static const Gambatte::FilterInfo fInfo = { "MaxSt's Hq2x", 160 * 2, 144 * 2 }; + return fInfo; +} + +Gambatte::uint_least32_t* MaxSt_Hq2x::inBuffer() { + return buffer; +} + +unsigned MaxSt_Hq2x::inPitch() { + return 160; +} + +void MaxSt_Hq2x::filter(Gambatte::uint_least32_t *const dbuffer, const unsigned pitch) { + ::filter(dbuffer, pitch, buffer, 160, 144); +} diff --git a/supergameboy/libgambatte/src/video/filters/maxsthq2x.h b/supergameboy/libgambatte/src/video/filters/maxsthq2x.h new file mode 100644 index 00000000..ca2cf411 --- /dev/null +++ b/supergameboy/libgambatte/src/video/filters/maxsthq2x.h @@ -0,0 +1,41 @@ +/*************************************************************************** + * Copyright (C) 2007 by Sindre Aam�s * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#ifndef MAXSTHQ2X_H +#define MAXSTHQ2X_H + +#include "filter.h" + +struct FilterInfo; + +class MaxSt_Hq2x : public Filter { + Gambatte::uint_least32_t *buffer; + +public: + MaxSt_Hq2x(); + ~MaxSt_Hq2x(); + void init(); + void outit(); + const Gambatte::FilterInfo& info(); + void filter(Gambatte::uint_least32_t *dbuffer, unsigned pitch); + Gambatte::uint_least32_t* inBuffer(); + unsigned inPitch(); +}; + + +#endif diff --git a/supergameboy/libgambatte/src/video/filters/maxsthq3x.cpp b/supergameboy/libgambatte/src/video/filters/maxsthq3x.cpp new file mode 100644 index 00000000..996a221e --- /dev/null +++ b/supergameboy/libgambatte/src/video/filters/maxsthq3x.cpp @@ -0,0 +1,3845 @@ +/*************************************************************************** + * Copyright (C) 2007 by Sindre AamÃ¥s * + * aamas@stud.ntnu.no * + * * + * Copyright (C) 2003 MaxSt * + * maxst@hiend3d.com * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#include "maxsthq3x.h" +#include "filterinfo.h" +#include + +static /*inline*/ unsigned long Interp1(const unsigned long c1, const unsigned long c2) { + const unsigned long lowbits = ((c1 & 0x030303) * 3 + (c2 & 0x030303)) & 0x030303; + + return (c1 * 3 + c2 - lowbits) >> 2; +} + +static /*inline*/ unsigned long Interp2(const unsigned long c1, const unsigned long c2, const unsigned long c3) { + const unsigned long lowbits = ((c1 * 2 & 0x020202) + (c2 & 0x030303) + (c3 & 0x030303)) & 0x030303; + + return (c1 * 2 + c2 + c3 - lowbits) >> 2; +} + +static /*inline*/ unsigned long Interp3(const unsigned long c1, const unsigned long c2) { + const unsigned long lowbits = ((c1 & 0x070707) * 7 + (c2 & 0x070707)) & 0x070707; + + return (c1 * 7 + c2 - lowbits) >> 3; +} + +static /*inline*/ unsigned long Interp4(const unsigned long c1, const unsigned long c2, const unsigned long c3) { + const unsigned long lowbits = ((c1 * 2 & 0x0E0E0E) + ((c2 & 0x0F0F0F) + (c3 & 0x0F0F0F)) * 7) & 0x0F0F0F; + + return (c1 * 2 + (c2 + c3) * 7 - lowbits) >> 4; +} + +static /*inline*/ unsigned long Interp5(const unsigned long c1, const unsigned long c2) { + return (c1 + c2 - ((c1 ^ c2) & 0x010101)) >> 1; +} + +#define PIXEL00_1M *pOut = Interp1(w[5], w[1]); +#define PIXEL00_1U *pOut = Interp1(w[5], w[2]); +#define PIXEL00_1L *pOut = Interp1(w[5], w[4]); +#define PIXEL00_2 *pOut = Interp2(w[5], w[4], w[2]); +#define PIXEL00_4 *pOut = Interp4(w[5], w[4], w[2]); +#define PIXEL00_5 *pOut = Interp5(w[4], w[2]); +#define PIXEL00_C *pOut = w[5]; + +#define PIXEL01_1 *(pOut+1) = Interp1(w[5], w[2]); +#define PIXEL01_3 *(pOut+1) = Interp3(w[5], w[2]); +#define PIXEL01_6 *(pOut+1) = Interp1(w[2], w[5]); +#define PIXEL01_C *(pOut+1) = w[5]; + +#define PIXEL02_1M *(pOut+2) = Interp1(w[5], w[3]); +#define PIXEL02_1U *(pOut+2) = Interp1(w[5], w[2]); +#define PIXEL02_1R *(pOut+2) = Interp1(w[5], w[6]); +#define PIXEL02_2 *(pOut+2) = Interp2(w[5], w[2], w[6]); +#define PIXEL02_4 *(pOut+2) = Interp4(w[5], w[2], w[6]); +#define PIXEL02_5 *(pOut+2) = Interp5(w[2], w[6]); +#define PIXEL02_C *(pOut+2) = w[5]; + +#define PIXEL10_1 *(pOut+dstPitch) = Interp1(w[5], w[4]); +#define PIXEL10_3 *(pOut+dstPitch) = Interp3(w[5], w[4]); +#define PIXEL10_6 *(pOut+dstPitch) = Interp1(w[4], w[5]); +#define PIXEL10_C *(pOut+dstPitch) = w[5]; + +#define PIXEL11 *(pOut+dstPitch+1) = w[5]; + +#define PIXEL12_1 *(pOut+dstPitch+2) = Interp1(w[5], w[6]); +#define PIXEL12_3 *(pOut+dstPitch+2) = Interp3(w[5], w[6]); +#define PIXEL12_6 *(pOut+dstPitch+2) = Interp1(w[6], w[5]); +#define PIXEL12_C *(pOut+dstPitch+2) = w[5]; + +#define PIXEL20_1M *(pOut+dstPitch*2) = Interp1(w[5], w[7]); +#define PIXEL20_1D *(pOut+dstPitch*2) = Interp1(w[5], w[8]); +#define PIXEL20_1L *(pOut+dstPitch*2) = Interp1(w[5], w[4]); +#define PIXEL20_2 *(pOut+dstPitch*2) = Interp2(w[5], w[8], w[4]); +#define PIXEL20_4 *(pOut+dstPitch*2) = Interp4(w[5], w[8], w[4]); +#define PIXEL20_5 *(pOut+dstPitch*2) = Interp5(w[8], w[4]); +#define PIXEL20_C *(pOut+dstPitch*2) = w[5]; + +#define PIXEL21_1 *(pOut+dstPitch*2+1) = Interp1(w[5], w[8]); +#define PIXEL21_3 *(pOut+dstPitch*2+1) = Interp3(w[5], w[8]); +#define PIXEL21_6 *(pOut+dstPitch*2+1) = Interp1(w[8], w[5]); +#define PIXEL21_C *(pOut+dstPitch*2+1) = w[5]; + +#define PIXEL22_1M *(pOut+dstPitch*2+2) = Interp1(w[5], w[9]); +#define PIXEL22_1D *(pOut+dstPitch*2+2) = Interp1(w[5], w[8]); +#define PIXEL22_1R *(pOut+dstPitch*2+2) = Interp1(w[5], w[6]); +#define PIXEL22_2 *(pOut+dstPitch*2+2) = Interp2(w[5], w[6], w[8]); +#define PIXEL22_4 *(pOut+dstPitch*2+2) = Interp4(w[5], w[6], w[8]); +#define PIXEL22_5 *(pOut+dstPitch*2+2) = Interp5(w[6], w[8]); +#define PIXEL22_C *(pOut+dstPitch*2+2) = w[5]; + +static /*inline*/ bool Diff(const unsigned long w1, const unsigned long w2) { + const unsigned rdiff = (w1 >> 16) - (w2 >> 16); + const unsigned gdiff = (w1 >> 8 & 0xFF) - (w2 >> 8 & 0xFF); + const unsigned bdiff = (w1 & 0xFF) - (w2 & 0xFF); + + return rdiff + gdiff + bdiff + 0xC0U > 0xC0U * 2 || + rdiff - bdiff + 0x1CU > 0x1CU * 2 || + gdiff * 2 - rdiff - bdiff + 0x30U > 0x30U * 2; +} + +static void filter(Gambatte::uint_least32_t *pOut, const unsigned dstPitch, + const Gambatte::uint_least32_t *pIn, const unsigned Xres, const unsigned Yres) +{ + unsigned long w[10]; + + // +----+----+----+ + // | | | | + // | w1 | w2 | w3 | + // +----+----+----+ + // | | | | + // | w4 | w5 | w6 | + // +----+----+----+ + // | | | | + // | w7 | w8 | w9 | + // +----+----+----+ + + for (unsigned j = 0; j < Yres; j++) { + const unsigned prevline = j > 0 ? Xres : 0; + const unsigned nextline = j < Yres - 1 ? Xres : 0; + + for (unsigned i = 0; i < Xres; i++) { + w[2] = *(pIn - prevline); + w[5] = *(pIn); + w[8] = *(pIn + nextline); + + if (i > 0) { + w[1] = *(pIn - prevline - 1); + w[4] = *(pIn - 1); + w[7] = *(pIn + nextline - 1); + } else { + w[1] = w[2]; + w[4] = w[5]; + w[7] = w[8]; + } + + if (i < Xres - 1) { + w[3] = *(pIn - prevline + 1); + w[6] = *(pIn + 1); + w[9] = *(pIn + nextline + 1); + } else { + w[3] = w[2]; + w[6] = w[5]; + w[9] = w[8]; + } + + unsigned pattern = 0; + + { + unsigned flag = 1; + + const unsigned r1 = w[5] >> 16; + const unsigned g1 = w[5] >> 8 & 0xFF; + const unsigned b1 = w[5] & 0xFF; + + for (unsigned k = 1; k < 10; ++k) { + if (k == 5) continue; + + if (w[k] != w[5]) { + const unsigned rdiff = r1 - (w[k] >> 16); + const unsigned gdiff = g1 - (w[k] >> 8 & 0xFF); + const unsigned bdiff = b1 - (w[k] & 0xFF); + + if (rdiff + gdiff + bdiff + 0xC0U > 0xC0U * 2 || + rdiff - bdiff + 0x1CU > 0x1CU * 2 || + gdiff * 2 - rdiff - bdiff + 0x30U > 0x30U * 2) + pattern |= flag; + } + + flag <<= 1; + } + } + + switch (pattern) + { + case 0: + case 1: + case 4: + case 32: + case 128: + case 5: + case 132: + case 160: + case 33: + case 129: + case 36: + case 133: + case 164: + case 161: + case 37: + case 165: + { + PIXEL00_2 + PIXEL01_1 + PIXEL02_2 + PIXEL10_1 + PIXEL11 + PIXEL12_1 + PIXEL20_2 + PIXEL21_1 + PIXEL22_2 + break; + } + case 2: + case 34: + case 130: + case 162: + { + PIXEL00_1M + PIXEL01_C + PIXEL02_1M + PIXEL10_1 + PIXEL11 + PIXEL12_1 + PIXEL20_2 + PIXEL21_1 + PIXEL22_2 + break; + } + case 16: + case 17: + case 48: + case 49: + { + PIXEL00_2 + PIXEL01_1 + PIXEL02_1M + PIXEL10_1 + PIXEL11 + PIXEL12_C + PIXEL20_2 + PIXEL21_1 + PIXEL22_1M + break; + } + case 64: + case 65: + case 68: + case 69: + { + PIXEL00_2 + PIXEL01_1 + PIXEL02_2 + PIXEL10_1 + PIXEL11 + PIXEL12_1 + PIXEL20_1M + PIXEL21_C + PIXEL22_1M + break; + } + case 8: + case 12: + case 136: + case 140: + { + PIXEL00_1M + PIXEL01_1 + PIXEL02_2 + PIXEL10_C + PIXEL11 + PIXEL12_1 + PIXEL20_1M + PIXEL21_1 + PIXEL22_2 + break; + } + case 3: + case 35: + case 131: + case 163: + { + PIXEL00_1L + PIXEL01_C + PIXEL02_1M + PIXEL10_1 + PIXEL11 + PIXEL12_1 + PIXEL20_2 + PIXEL21_1 + PIXEL22_2 + break; + } + case 6: + case 38: + case 134: + case 166: + { + PIXEL00_1M + PIXEL01_C + PIXEL02_1R + PIXEL10_1 + PIXEL11 + PIXEL12_1 + PIXEL20_2 + PIXEL21_1 + PIXEL22_2 + break; + } + case 20: + case 21: + case 52: + case 53: + { + PIXEL00_2 + PIXEL01_1 + PIXEL02_1U + PIXEL10_1 + PIXEL11 + PIXEL12_C + PIXEL20_2 + PIXEL21_1 + PIXEL22_1M + break; + } + case 144: + case 145: + case 176: + case 177: + { + PIXEL00_2 + PIXEL01_1 + PIXEL02_1M + PIXEL10_1 + PIXEL11 + PIXEL12_C + PIXEL20_2 + PIXEL21_1 + PIXEL22_1D + break; + } + case 192: + case 193: + case 196: + case 197: + { + PIXEL00_2 + PIXEL01_1 + PIXEL02_2 + PIXEL10_1 + PIXEL11 + PIXEL12_1 + PIXEL20_1M + PIXEL21_C + PIXEL22_1R + break; + } + case 96: + case 97: + case 100: + case 101: + { + PIXEL00_2 + PIXEL01_1 + PIXEL02_2 + PIXEL10_1 + PIXEL11 + PIXEL12_1 + PIXEL20_1L + PIXEL21_C + PIXEL22_1M + break; + } + case 40: + case 44: + case 168: + case 172: + { + PIXEL00_1M + PIXEL01_1 + PIXEL02_2 + PIXEL10_C + PIXEL11 + PIXEL12_1 + PIXEL20_1D + PIXEL21_1 + PIXEL22_2 + break; + } + case 9: + case 13: + case 137: + case 141: + { + PIXEL00_1U + PIXEL01_1 + PIXEL02_2 + PIXEL10_C + PIXEL11 + PIXEL12_1 + PIXEL20_1M + PIXEL21_1 + PIXEL22_2 + break; + } + case 18: + case 50: + { + PIXEL00_1M + if (Diff(w[2], w[6])) + { + PIXEL01_C + PIXEL02_1M + PIXEL12_C + } + else + { + PIXEL01_3 + PIXEL02_4 + PIXEL12_3 + } + PIXEL10_1 + PIXEL11 + PIXEL20_2 + PIXEL21_1 + PIXEL22_1M + break; + } + case 80: + case 81: + { + PIXEL00_2 + PIXEL01_1 + PIXEL02_1M + PIXEL10_1 + PIXEL11 + PIXEL20_1M + if (Diff(w[6], w[8])) + { + PIXEL12_C + PIXEL21_C + PIXEL22_1M + } + else + { + PIXEL12_3 + PIXEL21_3 + PIXEL22_4 + } + break; + } + case 72: + case 76: + { + PIXEL00_1M + PIXEL01_1 + PIXEL02_2 + PIXEL11 + PIXEL12_1 + if (Diff(w[8], w[4])) + { + PIXEL10_C + PIXEL20_1M + PIXEL21_C + } + else + { + PIXEL10_3 + PIXEL20_4 + PIXEL21_3 + } + PIXEL22_1M + break; + } + case 10: + case 138: + { + if (Diff(w[4], w[2])) + { + PIXEL00_1M + PIXEL01_C + PIXEL10_C + } + else + { + PIXEL00_4 + PIXEL01_3 + PIXEL10_3 + } + PIXEL02_1M + PIXEL11 + PIXEL12_1 + PIXEL20_1M + PIXEL21_1 + PIXEL22_2 + break; + } + case 66: + { + PIXEL00_1M + PIXEL01_C + PIXEL02_1M + PIXEL10_1 + PIXEL11 + PIXEL12_1 + PIXEL20_1M + PIXEL21_C + PIXEL22_1M + break; + } + case 24: + { + PIXEL00_1M + PIXEL01_1 + PIXEL02_1M + PIXEL10_C + PIXEL11 + PIXEL12_C + PIXEL20_1M + PIXEL21_1 + PIXEL22_1M + break; + } + case 7: + case 39: + case 135: + { + PIXEL00_1L + PIXEL01_C + PIXEL02_1R + PIXEL10_1 + PIXEL11 + PIXEL12_1 + PIXEL20_2 + PIXEL21_1 + PIXEL22_2 + break; + } + case 148: + case 149: + case 180: + { + PIXEL00_2 + PIXEL01_1 + PIXEL02_1U + PIXEL10_1 + PIXEL11 + PIXEL12_C + PIXEL20_2 + PIXEL21_1 + PIXEL22_1D + break; + } + case 224: + case 228: + case 225: + { + PIXEL00_2 + PIXEL01_1 + PIXEL02_2 + PIXEL10_1 + PIXEL11 + PIXEL12_1 + PIXEL20_1L + PIXEL21_C + PIXEL22_1R + break; + } + case 41: + case 169: + case 45: + { + PIXEL00_1U + PIXEL01_1 + PIXEL02_2 + PIXEL10_C + PIXEL11 + PIXEL12_1 + PIXEL20_1D + PIXEL21_1 + PIXEL22_2 + break; + } + case 22: + case 54: + { + PIXEL00_1M + if (Diff(w[2], w[6])) + { + PIXEL01_C + PIXEL02_C + PIXEL12_C + } + else + { + PIXEL01_3 + PIXEL02_4 + PIXEL12_3 + } + PIXEL10_1 + PIXEL11 + PIXEL20_2 + PIXEL21_1 + PIXEL22_1M + break; + } + case 208: + case 209: + { + PIXEL00_2 + PIXEL01_1 + PIXEL02_1M + PIXEL10_1 + PIXEL11 + PIXEL20_1M + if (Diff(w[6], w[8])) + { + PIXEL12_C + PIXEL21_C + PIXEL22_C + } + else + { + PIXEL12_3 + PIXEL21_3 + PIXEL22_4 + } + break; + } + case 104: + case 108: + { + PIXEL00_1M + PIXEL01_1 + PIXEL02_2 + PIXEL11 + PIXEL12_1 + if (Diff(w[8], w[4])) + { + PIXEL10_C + PIXEL20_C + PIXEL21_C + } + else + { + PIXEL10_3 + PIXEL20_4 + PIXEL21_3 + } + PIXEL22_1M + break; + } + case 11: + case 139: + { + if (Diff(w[4], w[2])) + { + PIXEL00_C + PIXEL01_C + PIXEL10_C + } + else + { + PIXEL00_4 + PIXEL01_3 + PIXEL10_3 + } + PIXEL02_1M + PIXEL11 + PIXEL12_1 + PIXEL20_1M + PIXEL21_1 + PIXEL22_2 + break; + } + case 19: + case 51: + { + if (Diff(w[2], w[6])) + { + PIXEL00_1L + PIXEL01_C + PIXEL02_1M + PIXEL12_C + } + else + { + PIXEL00_2 + PIXEL01_6 + PIXEL02_5 + PIXEL12_1 + } + PIXEL10_1 + PIXEL11 + PIXEL20_2 + PIXEL21_1 + PIXEL22_1M + break; + } + case 146: + case 178: + { + if (Diff(w[2], w[6])) + { + PIXEL01_C + PIXEL02_1M + PIXEL12_C + PIXEL22_1D + } + else + { + PIXEL01_1 + PIXEL02_5 + PIXEL12_6 + PIXEL22_2 + } + PIXEL00_1M + PIXEL10_1 + PIXEL11 + PIXEL20_2 + PIXEL21_1 + break; + } + case 84: + case 85: + { + if (Diff(w[6], w[8])) + { + PIXEL02_1U + PIXEL12_C + PIXEL21_C + PIXEL22_1M + } + else + { + PIXEL02_2 + PIXEL12_6 + PIXEL21_1 + PIXEL22_5 + } + PIXEL00_2 + PIXEL01_1 + PIXEL10_1 + PIXEL11 + PIXEL20_1M + break; + } + case 112: + case 113: + { + if (Diff(w[6], w[8])) + { + PIXEL12_C + PIXEL20_1L + PIXEL21_C + PIXEL22_1M + } + else + { + PIXEL12_1 + PIXEL20_2 + PIXEL21_6 + PIXEL22_5 + } + PIXEL00_2 + PIXEL01_1 + PIXEL02_1M + PIXEL10_1 + PIXEL11 + break; + } + case 200: + case 204: + { + if (Diff(w[8], w[4])) + { + PIXEL10_C + PIXEL20_1M + PIXEL21_C + PIXEL22_1R + } + else + { + PIXEL10_1 + PIXEL20_5 + PIXEL21_6 + PIXEL22_2 + } + PIXEL00_1M + PIXEL01_1 + PIXEL02_2 + PIXEL11 + PIXEL12_1 + break; + } + case 73: + case 77: + { + if (Diff(w[8], w[4])) + { + PIXEL00_1U + PIXEL10_C + PIXEL20_1M + PIXEL21_C + } + else + { + PIXEL00_2 + PIXEL10_6 + PIXEL20_5 + PIXEL21_1 + } + PIXEL01_1 + PIXEL02_2 + PIXEL11 + PIXEL12_1 + PIXEL22_1M + break; + } + case 42: + case 170: + { + if (Diff(w[4], w[2])) + { + PIXEL00_1M + PIXEL01_C + PIXEL10_C + PIXEL20_1D + } + else + { + PIXEL00_5 + PIXEL01_1 + PIXEL10_6 + PIXEL20_2 + } + PIXEL02_1M + PIXEL11 + PIXEL12_1 + PIXEL21_1 + PIXEL22_2 + break; + } + case 14: + case 142: + { + if (Diff(w[4], w[2])) + { + PIXEL00_1M + PIXEL01_C + PIXEL02_1R + PIXEL10_C + } + else + { + PIXEL00_5 + PIXEL01_6 + PIXEL02_2 + PIXEL10_1 + } + PIXEL11 + PIXEL12_1 + PIXEL20_1M + PIXEL21_1 + PIXEL22_2 + break; + } + case 67: + { + PIXEL00_1L + PIXEL01_C + PIXEL02_1M + PIXEL10_1 + PIXEL11 + PIXEL12_1 + PIXEL20_1M + PIXEL21_C + PIXEL22_1M + break; + } + case 70: + { + PIXEL00_1M + PIXEL01_C + PIXEL02_1R + PIXEL10_1 + PIXEL11 + PIXEL12_1 + PIXEL20_1M + PIXEL21_C + PIXEL22_1M + break; + } + case 28: + { + PIXEL00_1M + PIXEL01_1 + PIXEL02_1U + PIXEL10_C + PIXEL11 + PIXEL12_C + PIXEL20_1M + PIXEL21_1 + PIXEL22_1M + break; + } + case 152: + { + PIXEL00_1M + PIXEL01_1 + PIXEL02_1M + PIXEL10_C + PIXEL11 + PIXEL12_C + PIXEL20_1M + PIXEL21_1 + PIXEL22_1D + break; + } + case 194: + { + PIXEL00_1M + PIXEL01_C + PIXEL02_1M + PIXEL10_1 + PIXEL11 + PIXEL12_1 + PIXEL20_1M + PIXEL21_C + PIXEL22_1R + break; + } + case 98: + { + PIXEL00_1M + PIXEL01_C + PIXEL02_1M + PIXEL10_1 + PIXEL11 + PIXEL12_1 + PIXEL20_1L + PIXEL21_C + PIXEL22_1M + break; + } + case 56: + { + PIXEL00_1M + PIXEL01_1 + PIXEL02_1M + PIXEL10_C + PIXEL11 + PIXEL12_C + PIXEL20_1D + PIXEL21_1 + PIXEL22_1M + break; + } + case 25: + { + PIXEL00_1U + PIXEL01_1 + PIXEL02_1M + PIXEL10_C + PIXEL11 + PIXEL12_C + PIXEL20_1M + PIXEL21_1 + PIXEL22_1M + break; + } + case 26: + case 31: + { + if (Diff(w[4], w[2])) + { + PIXEL00_C + PIXEL10_C + } + else + { + PIXEL00_4 + PIXEL10_3 + } + PIXEL01_C + if (Diff(w[2], w[6])) + { + PIXEL02_C + PIXEL12_C + } + else + { + PIXEL02_4 + PIXEL12_3 + } + PIXEL11 + PIXEL20_1M + PIXEL21_1 + PIXEL22_1M + break; + } + case 82: + case 214: + { + PIXEL00_1M + if (Diff(w[2], w[6])) + { + PIXEL01_C + PIXEL02_C + } + else + { + PIXEL01_3 + PIXEL02_4 + } + PIXEL10_1 + PIXEL11 + PIXEL12_C + PIXEL20_1M + if (Diff(w[6], w[8])) + { + PIXEL21_C + PIXEL22_C + } + else + { + PIXEL21_3 + PIXEL22_4 + } + break; + } + case 88: + case 248: + { + PIXEL00_1M + PIXEL01_1 + PIXEL02_1M + PIXEL11 + if (Diff(w[8], w[4])) + { + PIXEL10_C + PIXEL20_C + } + else + { + PIXEL10_3 + PIXEL20_4 + } + PIXEL21_C + if (Diff(w[6], w[8])) + { + PIXEL12_C + PIXEL22_C + } + else + { + PIXEL12_3 + PIXEL22_4 + } + break; + } + case 74: + case 107: + { + if (Diff(w[4], w[2])) + { + PIXEL00_C + PIXEL01_C + } + else + { + PIXEL00_4 + PIXEL01_3 + } + PIXEL02_1M + PIXEL10_C + PIXEL11 + PIXEL12_1 + if (Diff(w[8], w[4])) + { + PIXEL20_C + PIXEL21_C + } + else + { + PIXEL20_4 + PIXEL21_3 + } + PIXEL22_1M + break; + } + case 27: + { + if (Diff(w[4], w[2])) + { + PIXEL00_C + PIXEL01_C + PIXEL10_C + } + else + { + PIXEL00_4 + PIXEL01_3 + PIXEL10_3 + } + PIXEL02_1M + PIXEL11 + PIXEL12_C + PIXEL20_1M + PIXEL21_1 + PIXEL22_1M + break; + } + case 86: + { + PIXEL00_1M + if (Diff(w[2], w[6])) + { + PIXEL01_C + PIXEL02_C + PIXEL12_C + } + else + { + PIXEL01_3 + PIXEL02_4 + PIXEL12_3 + } + PIXEL10_1 + PIXEL11 + PIXEL20_1M + PIXEL21_C + PIXEL22_1M + break; + } + case 216: + { + PIXEL00_1M + PIXEL01_1 + PIXEL02_1M + PIXEL10_C + PIXEL11 + PIXEL20_1M + if (Diff(w[6], w[8])) + { + PIXEL12_C + PIXEL21_C + PIXEL22_C + } + else + { + PIXEL12_3 + PIXEL21_3 + PIXEL22_4 + } + break; + } + case 106: + { + PIXEL00_1M + PIXEL01_C + PIXEL02_1M + PIXEL11 + PIXEL12_1 + if (Diff(w[8], w[4])) + { + PIXEL10_C + PIXEL20_C + PIXEL21_C + } + else + { + PIXEL10_3 + PIXEL20_4 + PIXEL21_3 + } + PIXEL22_1M + break; + } + case 30: + { + PIXEL00_1M + if (Diff(w[2], w[6])) + { + PIXEL01_C + PIXEL02_C + PIXEL12_C + } + else + { + PIXEL01_3 + PIXEL02_4 + PIXEL12_3 + } + PIXEL10_C + PIXEL11 + PIXEL20_1M + PIXEL21_1 + PIXEL22_1M + break; + } + case 210: + { + PIXEL00_1M + PIXEL01_C + PIXEL02_1M + PIXEL10_1 + PIXEL11 + PIXEL20_1M + if (Diff(w[6], w[8])) + { + PIXEL12_C + PIXEL21_C + PIXEL22_C + } + else + { + PIXEL12_3 + PIXEL21_3 + PIXEL22_4 + } + break; + } + case 120: + { + PIXEL00_1M + PIXEL01_1 + PIXEL02_1M + PIXEL11 + PIXEL12_C + if (Diff(w[8], w[4])) + { + PIXEL10_C + PIXEL20_C + PIXEL21_C + } + else + { + PIXEL10_3 + PIXEL20_4 + PIXEL21_3 + } + PIXEL22_1M + break; + } + case 75: + { + if (Diff(w[4], w[2])) + { + PIXEL00_C + PIXEL01_C + PIXEL10_C + } + else + { + PIXEL00_4 + PIXEL01_3 + PIXEL10_3 + } + PIXEL02_1M + PIXEL11 + PIXEL12_1 + PIXEL20_1M + PIXEL21_C + PIXEL22_1M + break; + } + case 29: + { + PIXEL00_1U + PIXEL01_1 + PIXEL02_1U + PIXEL10_C + PIXEL11 + PIXEL12_C + PIXEL20_1M + PIXEL21_1 + PIXEL22_1M + break; + } + case 198: + { + PIXEL00_1M + PIXEL01_C + PIXEL02_1R + PIXEL10_1 + PIXEL11 + PIXEL12_1 + PIXEL20_1M + PIXEL21_C + PIXEL22_1R + break; + } + case 184: + { + PIXEL00_1M + PIXEL01_1 + PIXEL02_1M + PIXEL10_C + PIXEL11 + PIXEL12_C + PIXEL20_1D + PIXEL21_1 + PIXEL22_1D + break; + } + case 99: + { + PIXEL00_1L + PIXEL01_C + PIXEL02_1M + PIXEL10_1 + PIXEL11 + PIXEL12_1 + PIXEL20_1L + PIXEL21_C + PIXEL22_1M + break; + } + case 57: + { + PIXEL00_1U + PIXEL01_1 + PIXEL02_1M + PIXEL10_C + PIXEL11 + PIXEL12_C + PIXEL20_1D + PIXEL21_1 + PIXEL22_1M + break; + } + case 71: + { + PIXEL00_1L + PIXEL01_C + PIXEL02_1R + PIXEL10_1 + PIXEL11 + PIXEL12_1 + PIXEL20_1M + PIXEL21_C + PIXEL22_1M + break; + } + case 156: + { + PIXEL00_1M + PIXEL01_1 + PIXEL02_1U + PIXEL10_C + PIXEL11 + PIXEL12_C + PIXEL20_1M + PIXEL21_1 + PIXEL22_1D + break; + } + case 226: + { + PIXEL00_1M + PIXEL01_C + PIXEL02_1M + PIXEL10_1 + PIXEL11 + PIXEL12_1 + PIXEL20_1L + PIXEL21_C + PIXEL22_1R + break; + } + case 60: + { + PIXEL00_1M + PIXEL01_1 + PIXEL02_1U + PIXEL10_C + PIXEL11 + PIXEL12_C + PIXEL20_1D + PIXEL21_1 + PIXEL22_1M + break; + } + case 195: + { + PIXEL00_1L + PIXEL01_C + PIXEL02_1M + PIXEL10_1 + PIXEL11 + PIXEL12_1 + PIXEL20_1M + PIXEL21_C + PIXEL22_1R + break; + } + case 102: + { + PIXEL00_1M + PIXEL01_C + PIXEL02_1R + PIXEL10_1 + PIXEL11 + PIXEL12_1 + PIXEL20_1L + PIXEL21_C + PIXEL22_1M + break; + } + case 153: + { + PIXEL00_1U + PIXEL01_1 + PIXEL02_1M + PIXEL10_C + PIXEL11 + PIXEL12_C + PIXEL20_1M + PIXEL21_1 + PIXEL22_1D + break; + } + case 58: + { + if (Diff(w[4], w[2])) + { + PIXEL00_1M + } + else + { + PIXEL00_2 + } + PIXEL01_C + if (Diff(w[2], w[6])) + { + PIXEL02_1M + } + else + { + PIXEL02_2 + } + PIXEL10_C + PIXEL11 + PIXEL12_C + PIXEL20_1D + PIXEL21_1 + PIXEL22_1M + break; + } + case 83: + { + PIXEL00_1L + PIXEL01_C + if (Diff(w[2], w[6])) + { + PIXEL02_1M + } + else + { + PIXEL02_2 + } + PIXEL10_1 + PIXEL11 + PIXEL12_C + PIXEL20_1M + PIXEL21_C + if (Diff(w[6], w[8])) + { + PIXEL22_1M + } + else + { + PIXEL22_2 + } + break; + } + case 92: + { + PIXEL00_1M + PIXEL01_1 + PIXEL02_1U + PIXEL10_C + PIXEL11 + PIXEL12_C + if (Diff(w[8], w[4])) + { + PIXEL20_1M + } + else + { + PIXEL20_2 + } + PIXEL21_C + if (Diff(w[6], w[8])) + { + PIXEL22_1M + } + else + { + PIXEL22_2 + } + break; + } + case 202: + { + if (Diff(w[4], w[2])) + { + PIXEL00_1M + } + else + { + PIXEL00_2 + } + PIXEL01_C + PIXEL02_1M + PIXEL10_C + PIXEL11 + PIXEL12_1 + if (Diff(w[8], w[4])) + { + PIXEL20_1M + } + else + { + PIXEL20_2 + } + PIXEL21_C + PIXEL22_1R + break; + } + case 78: + { + if (Diff(w[4], w[2])) + { + PIXEL00_1M + } + else + { + PIXEL00_2 + } + PIXEL01_C + PIXEL02_1R + PIXEL10_C + PIXEL11 + PIXEL12_1 + if (Diff(w[8], w[4])) + { + PIXEL20_1M + } + else + { + PIXEL20_2 + } + PIXEL21_C + PIXEL22_1M + break; + } + case 154: + { + if (Diff(w[4], w[2])) + { + PIXEL00_1M + } + else + { + PIXEL00_2 + } + PIXEL01_C + if (Diff(w[2], w[6])) + { + PIXEL02_1M + } + else + { + PIXEL02_2 + } + PIXEL10_C + PIXEL11 + PIXEL12_C + PIXEL20_1M + PIXEL21_1 + PIXEL22_1D + break; + } + case 114: + { + PIXEL00_1M + PIXEL01_C + if (Diff(w[2], w[6])) + { + PIXEL02_1M + } + else + { + PIXEL02_2 + } + PIXEL10_1 + PIXEL11 + PIXEL12_C + PIXEL20_1L + PIXEL21_C + if (Diff(w[6], w[8])) + { + PIXEL22_1M + } + else + { + PIXEL22_2 + } + break; + } + case 89: + { + PIXEL00_1U + PIXEL01_1 + PIXEL02_1M + PIXEL10_C + PIXEL11 + PIXEL12_C + if (Diff(w[8], w[4])) + { + PIXEL20_1M + } + else + { + PIXEL20_2 + } + PIXEL21_C + if (Diff(w[6], w[8])) + { + PIXEL22_1M + } + else + { + PIXEL22_2 + } + break; + } + case 90: + { + if (Diff(w[4], w[2])) + { + PIXEL00_1M + } + else + { + PIXEL00_2 + } + PIXEL01_C + if (Diff(w[2], w[6])) + { + PIXEL02_1M + } + else + { + PIXEL02_2 + } + PIXEL10_C + PIXEL11 + PIXEL12_C + if (Diff(w[8], w[4])) + { + PIXEL20_1M + } + else + { + PIXEL20_2 + } + PIXEL21_C + if (Diff(w[6], w[8])) + { + PIXEL22_1M + } + else + { + PIXEL22_2 + } + break; + } + case 55: + case 23: + { + if (Diff(w[2], w[6])) + { + PIXEL00_1L + PIXEL01_C + PIXEL02_C + PIXEL12_C + } + else + { + PIXEL00_2 + PIXEL01_6 + PIXEL02_5 + PIXEL12_1 + } + PIXEL10_1 + PIXEL11 + PIXEL20_2 + PIXEL21_1 + PIXEL22_1M + break; + } + case 182: + case 150: + { + if (Diff(w[2], w[6])) + { + PIXEL01_C + PIXEL02_C + PIXEL12_C + PIXEL22_1D + } + else + { + PIXEL01_1 + PIXEL02_5 + PIXEL12_6 + PIXEL22_2 + } + PIXEL00_1M + PIXEL10_1 + PIXEL11 + PIXEL20_2 + PIXEL21_1 + break; + } + case 213: + case 212: + { + if (Diff(w[6], w[8])) + { + PIXEL02_1U + PIXEL12_C + PIXEL21_C + PIXEL22_C + } + else + { + PIXEL02_2 + PIXEL12_6 + PIXEL21_1 + PIXEL22_5 + } + PIXEL00_2 + PIXEL01_1 + PIXEL10_1 + PIXEL11 + PIXEL20_1M + break; + } + case 241: + case 240: + { + if (Diff(w[6], w[8])) + { + PIXEL12_C + PIXEL20_1L + PIXEL21_C + PIXEL22_C + } + else + { + PIXEL12_1 + PIXEL20_2 + PIXEL21_6 + PIXEL22_5 + } + PIXEL00_2 + PIXEL01_1 + PIXEL02_1M + PIXEL10_1 + PIXEL11 + break; + } + case 236: + case 232: + { + if (Diff(w[8], w[4])) + { + PIXEL10_C + PIXEL20_C + PIXEL21_C + PIXEL22_1R + } + else + { + PIXEL10_1 + PIXEL20_5 + PIXEL21_6 + PIXEL22_2 + } + PIXEL00_1M + PIXEL01_1 + PIXEL02_2 + PIXEL11 + PIXEL12_1 + break; + } + case 109: + case 105: + { + if (Diff(w[8], w[4])) + { + PIXEL00_1U + PIXEL10_C + PIXEL20_C + PIXEL21_C + } + else + { + PIXEL00_2 + PIXEL10_6 + PIXEL20_5 + PIXEL21_1 + } + PIXEL01_1 + PIXEL02_2 + PIXEL11 + PIXEL12_1 + PIXEL22_1M + break; + } + case 171: + case 43: + { + if (Diff(w[4], w[2])) + { + PIXEL00_C + PIXEL01_C + PIXEL10_C + PIXEL20_1D + } + else + { + PIXEL00_5 + PIXEL01_1 + PIXEL10_6 + PIXEL20_2 + } + PIXEL02_1M + PIXEL11 + PIXEL12_1 + PIXEL21_1 + PIXEL22_2 + break; + } + case 143: + case 15: + { + if (Diff(w[4], w[2])) + { + PIXEL00_C + PIXEL01_C + PIXEL02_1R + PIXEL10_C + } + else + { + PIXEL00_5 + PIXEL01_6 + PIXEL02_2 + PIXEL10_1 + } + PIXEL11 + PIXEL12_1 + PIXEL20_1M + PIXEL21_1 + PIXEL22_2 + break; + } + case 124: + { + PIXEL00_1M + PIXEL01_1 + PIXEL02_1U + PIXEL11 + PIXEL12_C + if (Diff(w[8], w[4])) + { + PIXEL10_C + PIXEL20_C + PIXEL21_C + } + else + { + PIXEL10_3 + PIXEL20_4 + PIXEL21_3 + } + PIXEL22_1M + break; + } + case 203: + { + if (Diff(w[4], w[2])) + { + PIXEL00_C + PIXEL01_C + PIXEL10_C + } + else + { + PIXEL00_4 + PIXEL01_3 + PIXEL10_3 + } + PIXEL02_1M + PIXEL11 + PIXEL12_1 + PIXEL20_1M + PIXEL21_C + PIXEL22_1R + break; + } + case 62: + { + PIXEL00_1M + if (Diff(w[2], w[6])) + { + PIXEL01_C + PIXEL02_C + PIXEL12_C + } + else + { + PIXEL01_3 + PIXEL02_4 + PIXEL12_3 + } + PIXEL10_C + PIXEL11 + PIXEL20_1D + PIXEL21_1 + PIXEL22_1M + break; + } + case 211: + { + PIXEL00_1L + PIXEL01_C + PIXEL02_1M + PIXEL10_1 + PIXEL11 + PIXEL20_1M + if (Diff(w[6], w[8])) + { + PIXEL12_C + PIXEL21_C + PIXEL22_C + } + else + { + PIXEL12_3 + PIXEL21_3 + PIXEL22_4 + } + break; + } + case 118: + { + PIXEL00_1M + if (Diff(w[2], w[6])) + { + PIXEL01_C + PIXEL02_C + PIXEL12_C + } + else + { + PIXEL01_3 + PIXEL02_4 + PIXEL12_3 + } + PIXEL10_1 + PIXEL11 + PIXEL20_1L + PIXEL21_C + PIXEL22_1M + break; + } + case 217: + { + PIXEL00_1U + PIXEL01_1 + PIXEL02_1M + PIXEL10_C + PIXEL11 + PIXEL20_1M + if (Diff(w[6], w[8])) + { + PIXEL12_C + PIXEL21_C + PIXEL22_C + } + else + { + PIXEL12_3 + PIXEL21_3 + PIXEL22_4 + } + break; + } + case 110: + { + PIXEL00_1M + PIXEL01_C + PIXEL02_1R + PIXEL11 + PIXEL12_1 + if (Diff(w[8], w[4])) + { + PIXEL10_C + PIXEL20_C + PIXEL21_C + } + else + { + PIXEL10_3 + PIXEL20_4 + PIXEL21_3 + } + PIXEL22_1M + break; + } + case 155: + { + if (Diff(w[4], w[2])) + { + PIXEL00_C + PIXEL01_C + PIXEL10_C + } + else + { + PIXEL00_4 + PIXEL01_3 + PIXEL10_3 + } + PIXEL02_1M + PIXEL11 + PIXEL12_C + PIXEL20_1M + PIXEL21_1 + PIXEL22_1D + break; + } + case 188: + { + PIXEL00_1M + PIXEL01_1 + PIXEL02_1U + PIXEL10_C + PIXEL11 + PIXEL12_C + PIXEL20_1D + PIXEL21_1 + PIXEL22_1D + break; + } + case 185: + { + PIXEL00_1U + PIXEL01_1 + PIXEL02_1M + PIXEL10_C + PIXEL11 + PIXEL12_C + PIXEL20_1D + PIXEL21_1 + PIXEL22_1D + break; + } + case 61: + { + PIXEL00_1U + PIXEL01_1 + PIXEL02_1U + PIXEL10_C + PIXEL11 + PIXEL12_C + PIXEL20_1D + PIXEL21_1 + PIXEL22_1M + break; + } + case 157: + { + PIXEL00_1U + PIXEL01_1 + PIXEL02_1U + PIXEL10_C + PIXEL11 + PIXEL12_C + PIXEL20_1M + PIXEL21_1 + PIXEL22_1D + break; + } + case 103: + { + PIXEL00_1L + PIXEL01_C + PIXEL02_1R + PIXEL10_1 + PIXEL11 + PIXEL12_1 + PIXEL20_1L + PIXEL21_C + PIXEL22_1M + break; + } + case 227: + { + PIXEL00_1L + PIXEL01_C + PIXEL02_1M + PIXEL10_1 + PIXEL11 + PIXEL12_1 + PIXEL20_1L + PIXEL21_C + PIXEL22_1R + break; + } + case 230: + { + PIXEL00_1M + PIXEL01_C + PIXEL02_1R + PIXEL10_1 + PIXEL11 + PIXEL12_1 + PIXEL20_1L + PIXEL21_C + PIXEL22_1R + break; + } + case 199: + { + PIXEL00_1L + PIXEL01_C + PIXEL02_1R + PIXEL10_1 + PIXEL11 + PIXEL12_1 + PIXEL20_1M + PIXEL21_C + PIXEL22_1R + break; + } + case 220: + { + PIXEL00_1M + PIXEL01_1 + PIXEL02_1U + PIXEL10_C + PIXEL11 + if (Diff(w[8], w[4])) + { + PIXEL20_1M + } + else + { + PIXEL20_2 + } + if (Diff(w[6], w[8])) + { + PIXEL12_C + PIXEL21_C + PIXEL22_C + } + else + { + PIXEL12_3 + PIXEL21_3 + PIXEL22_4 + } + break; + } + case 158: + { + if (Diff(w[4], w[2])) + { + PIXEL00_1M + } + else + { + PIXEL00_2 + } + if (Diff(w[2], w[6])) + { + PIXEL01_C + PIXEL02_C + PIXEL12_C + } + else + { + PIXEL01_3 + PIXEL02_4 + PIXEL12_3 + } + PIXEL10_C + PIXEL11 + PIXEL20_1M + PIXEL21_1 + PIXEL22_1D + break; + } + case 234: + { + if (Diff(w[4], w[2])) + { + PIXEL00_1M + } + else + { + PIXEL00_2 + } + PIXEL01_C + PIXEL02_1M + PIXEL11 + PIXEL12_1 + if (Diff(w[8], w[4])) + { + PIXEL10_C + PIXEL20_C + PIXEL21_C + } + else + { + PIXEL10_3 + PIXEL20_4 + PIXEL21_3 + } + PIXEL22_1R + break; + } + case 242: + { + PIXEL00_1M + PIXEL01_C + if (Diff(w[2], w[6])) + { + PIXEL02_1M + } + else + { + PIXEL02_2 + } + PIXEL10_1 + PIXEL11 + PIXEL20_1L + if (Diff(w[6], w[8])) + { + PIXEL12_C + PIXEL21_C + PIXEL22_C + } + else + { + PIXEL12_3 + PIXEL21_3 + PIXEL22_4 + } + break; + } + case 59: + { + if (Diff(w[4], w[2])) + { + PIXEL00_C + PIXEL01_C + PIXEL10_C + } + else + { + PIXEL00_4 + PIXEL01_3 + PIXEL10_3 + } + if (Diff(w[2], w[6])) + { + PIXEL02_1M + } + else + { + PIXEL02_2 + } + PIXEL11 + PIXEL12_C + PIXEL20_1D + PIXEL21_1 + PIXEL22_1M + break; + } + case 121: + { + PIXEL00_1U + PIXEL01_1 + PIXEL02_1M + PIXEL11 + PIXEL12_C + if (Diff(w[8], w[4])) + { + PIXEL10_C + PIXEL20_C + PIXEL21_C + } + else + { + PIXEL10_3 + PIXEL20_4 + PIXEL21_3 + } + if (Diff(w[6], w[8])) + { + PIXEL22_1M + } + else + { + PIXEL22_2 + } + break; + } + case 87: + { + PIXEL00_1L + if (Diff(w[2], w[6])) + { + PIXEL01_C + PIXEL02_C + PIXEL12_C + } + else + { + PIXEL01_3 + PIXEL02_4 + PIXEL12_3 + } + PIXEL10_1 + PIXEL11 + PIXEL20_1M + PIXEL21_C + if (Diff(w[6], w[8])) + { + PIXEL22_1M + } + else + { + PIXEL22_2 + } + break; + } + case 79: + { + if (Diff(w[4], w[2])) + { + PIXEL00_C + PIXEL01_C + PIXEL10_C + } + else + { + PIXEL00_4 + PIXEL01_3 + PIXEL10_3 + } + PIXEL02_1R + PIXEL11 + PIXEL12_1 + if (Diff(w[8], w[4])) + { + PIXEL20_1M + } + else + { + PIXEL20_2 + } + PIXEL21_C + PIXEL22_1M + break; + } + case 122: + { + if (Diff(w[4], w[2])) + { + PIXEL00_1M + } + else + { + PIXEL00_2 + } + PIXEL01_C + if (Diff(w[2], w[6])) + { + PIXEL02_1M + } + else + { + PIXEL02_2 + } + PIXEL11 + PIXEL12_C + if (Diff(w[8], w[4])) + { + PIXEL10_C + PIXEL20_C + PIXEL21_C + } + else + { + PIXEL10_3 + PIXEL20_4 + PIXEL21_3 + } + if (Diff(w[6], w[8])) + { + PIXEL22_1M + } + else + { + PIXEL22_2 + } + break; + } + case 94: + { + if (Diff(w[4], w[2])) + { + PIXEL00_1M + } + else + { + PIXEL00_2 + } + if (Diff(w[2], w[6])) + { + PIXEL01_C + PIXEL02_C + PIXEL12_C + } + else + { + PIXEL01_3 + PIXEL02_4 + PIXEL12_3 + } + PIXEL10_C + PIXEL11 + if (Diff(w[8], w[4])) + { + PIXEL20_1M + } + else + { + PIXEL20_2 + } + PIXEL21_C + if (Diff(w[6], w[8])) + { + PIXEL22_1M + } + else + { + PIXEL22_2 + } + break; + } + case 218: + { + if (Diff(w[4], w[2])) + { + PIXEL00_1M + } + else + { + PIXEL00_2 + } + PIXEL01_C + if (Diff(w[2], w[6])) + { + PIXEL02_1M + } + else + { + PIXEL02_2 + } + PIXEL10_C + PIXEL11 + if (Diff(w[8], w[4])) + { + PIXEL20_1M + } + else + { + PIXEL20_2 + } + if (Diff(w[6], w[8])) + { + PIXEL12_C + PIXEL21_C + PIXEL22_C + } + else + { + PIXEL12_3 + PIXEL21_3 + PIXEL22_4 + } + break; + } + case 91: + { + if (Diff(w[4], w[2])) + { + PIXEL00_C + PIXEL01_C + PIXEL10_C + } + else + { + PIXEL00_4 + PIXEL01_3 + PIXEL10_3 + } + if (Diff(w[2], w[6])) + { + PIXEL02_1M + } + else + { + PIXEL02_2 + } + PIXEL11 + PIXEL12_C + if (Diff(w[8], w[4])) + { + PIXEL20_1M + } + else + { + PIXEL20_2 + } + PIXEL21_C + if (Diff(w[6], w[8])) + { + PIXEL22_1M + } + else + { + PIXEL22_2 + } + break; + } + case 229: + { + PIXEL00_2 + PIXEL01_1 + PIXEL02_2 + PIXEL10_1 + PIXEL11 + PIXEL12_1 + PIXEL20_1L + PIXEL21_C + PIXEL22_1R + break; + } + case 167: + { + PIXEL00_1L + PIXEL01_C + PIXEL02_1R + PIXEL10_1 + PIXEL11 + PIXEL12_1 + PIXEL20_2 + PIXEL21_1 + PIXEL22_2 + break; + } + case 173: + { + PIXEL00_1U + PIXEL01_1 + PIXEL02_2 + PIXEL10_C + PIXEL11 + PIXEL12_1 + PIXEL20_1D + PIXEL21_1 + PIXEL22_2 + break; + } + case 181: + { + PIXEL00_2 + PIXEL01_1 + PIXEL02_1U + PIXEL10_1 + PIXEL11 + PIXEL12_C + PIXEL20_2 + PIXEL21_1 + PIXEL22_1D + break; + } + case 186: + { + if (Diff(w[4], w[2])) + { + PIXEL00_1M + } + else + { + PIXEL00_2 + } + PIXEL01_C + if (Diff(w[2], w[6])) + { + PIXEL02_1M + } + else + { + PIXEL02_2 + } + PIXEL10_C + PIXEL11 + PIXEL12_C + PIXEL20_1D + PIXEL21_1 + PIXEL22_1D + break; + } + case 115: + { + PIXEL00_1L + PIXEL01_C + if (Diff(w[2], w[6])) + { + PIXEL02_1M + } + else + { + PIXEL02_2 + } + PIXEL10_1 + PIXEL11 + PIXEL12_C + PIXEL20_1L + PIXEL21_C + if (Diff(w[6], w[8])) + { + PIXEL22_1M + } + else + { + PIXEL22_2 + } + break; + } + case 93: + { + PIXEL00_1U + PIXEL01_1 + PIXEL02_1U + PIXEL10_C + PIXEL11 + PIXEL12_C + if (Diff(w[8], w[4])) + { + PIXEL20_1M + } + else + { + PIXEL20_2 + } + PIXEL21_C + if (Diff(w[6], w[8])) + { + PIXEL22_1M + } + else + { + PIXEL22_2 + } + break; + } + case 206: + { + if (Diff(w[4], w[2])) + { + PIXEL00_1M + } + else + { + PIXEL00_2 + } + PIXEL01_C + PIXEL02_1R + PIXEL10_C + PIXEL11 + PIXEL12_1 + if (Diff(w[8], w[4])) + { + PIXEL20_1M + } + else + { + PIXEL20_2 + } + PIXEL21_C + PIXEL22_1R + break; + } + case 205: + case 201: + { + PIXEL00_1U + PIXEL01_1 + PIXEL02_2 + PIXEL10_C + PIXEL11 + PIXEL12_1 + if (Diff(w[8], w[4])) + { + PIXEL20_1M + } + else + { + PIXEL20_2 + } + PIXEL21_C + PIXEL22_1R + break; + } + case 174: + case 46: + { + if (Diff(w[4], w[2])) + { + PIXEL00_1M + } + else + { + PIXEL00_2 + } + PIXEL01_C + PIXEL02_1R + PIXEL10_C + PIXEL11 + PIXEL12_1 + PIXEL20_1D + PIXEL21_1 + PIXEL22_2 + break; + } + case 179: + case 147: + { + PIXEL00_1L + PIXEL01_C + if (Diff(w[2], w[6])) + { + PIXEL02_1M + } + else + { + PIXEL02_2 + } + PIXEL10_1 + PIXEL11 + PIXEL12_C + PIXEL20_2 + PIXEL21_1 + PIXEL22_1D + break; + } + case 117: + case 116: + { + PIXEL00_2 + PIXEL01_1 + PIXEL02_1U + PIXEL10_1 + PIXEL11 + PIXEL12_C + PIXEL20_1L + PIXEL21_C + if (Diff(w[6], w[8])) + { + PIXEL22_1M + } + else + { + PIXEL22_2 + } + break; + } + case 189: + { + PIXEL00_1U + PIXEL01_1 + PIXEL02_1U + PIXEL10_C + PIXEL11 + PIXEL12_C + PIXEL20_1D + PIXEL21_1 + PIXEL22_1D + break; + } + case 231: + { + PIXEL00_1L + PIXEL01_C + PIXEL02_1R + PIXEL10_1 + PIXEL11 + PIXEL12_1 + PIXEL20_1L + PIXEL21_C + PIXEL22_1R + break; + } + case 126: + { + PIXEL00_1M + if (Diff(w[2], w[6])) + { + PIXEL01_C + PIXEL02_C + PIXEL12_C + } + else + { + PIXEL01_3 + PIXEL02_4 + PIXEL12_3 + } + PIXEL11 + if (Diff(w[8], w[4])) + { + PIXEL10_C + PIXEL20_C + PIXEL21_C + } + else + { + PIXEL10_3 + PIXEL20_4 + PIXEL21_3 + } + PIXEL22_1M + break; + } + case 219: + { + if (Diff(w[4], w[2])) + { + PIXEL00_C + PIXEL01_C + PIXEL10_C + } + else + { + PIXEL00_4 + PIXEL01_3 + PIXEL10_3 + } + PIXEL02_1M + PIXEL11 + PIXEL20_1M + if (Diff(w[6], w[8])) + { + PIXEL12_C + PIXEL21_C + PIXEL22_C + } + else + { + PIXEL12_3 + PIXEL21_3 + PIXEL22_4 + } + break; + } + case 125: + { + if (Diff(w[8], w[4])) + { + PIXEL00_1U + PIXEL10_C + PIXEL20_C + PIXEL21_C + } + else + { + PIXEL00_2 + PIXEL10_6 + PIXEL20_5 + PIXEL21_1 + } + PIXEL01_1 + PIXEL02_1U + PIXEL11 + PIXEL12_C + PIXEL22_1M + break; + } + case 221: + { + if (Diff(w[6], w[8])) + { + PIXEL02_1U + PIXEL12_C + PIXEL21_C + PIXEL22_C + } + else + { + PIXEL02_2 + PIXEL12_6 + PIXEL21_1 + PIXEL22_5 + } + PIXEL00_1U + PIXEL01_1 + PIXEL10_C + PIXEL11 + PIXEL20_1M + break; + } + case 207: + { + if (Diff(w[4], w[2])) + { + PIXEL00_C + PIXEL01_C + PIXEL02_1R + PIXEL10_C + } + else + { + PIXEL00_5 + PIXEL01_6 + PIXEL02_2 + PIXEL10_1 + } + PIXEL11 + PIXEL12_1 + PIXEL20_1M + PIXEL21_C + PIXEL22_1R + break; + } + case 238: + { + if (Diff(w[8], w[4])) + { + PIXEL10_C + PIXEL20_C + PIXEL21_C + PIXEL22_1R + } + else + { + PIXEL10_1 + PIXEL20_5 + PIXEL21_6 + PIXEL22_2 + } + PIXEL00_1M + PIXEL01_C + PIXEL02_1R + PIXEL11 + PIXEL12_1 + break; + } + case 190: + { + if (Diff(w[2], w[6])) + { + PIXEL01_C + PIXEL02_C + PIXEL12_C + PIXEL22_1D + } + else + { + PIXEL01_1 + PIXEL02_5 + PIXEL12_6 + PIXEL22_2 + } + PIXEL00_1M + PIXEL10_C + PIXEL11 + PIXEL20_1D + PIXEL21_1 + break; + } + case 187: + { + if (Diff(w[4], w[2])) + { + PIXEL00_C + PIXEL01_C + PIXEL10_C + PIXEL20_1D + } + else + { + PIXEL00_5 + PIXEL01_1 + PIXEL10_6 + PIXEL20_2 + } + PIXEL02_1M + PIXEL11 + PIXEL12_C + PIXEL21_1 + PIXEL22_1D + break; + } + case 243: + { + if (Diff(w[6], w[8])) + { + PIXEL12_C + PIXEL20_1L + PIXEL21_C + PIXEL22_C + } + else + { + PIXEL12_1 + PIXEL20_2 + PIXEL21_6 + PIXEL22_5 + } + PIXEL00_1L + PIXEL01_C + PIXEL02_1M + PIXEL10_1 + PIXEL11 + break; + } + case 119: + { + if (Diff(w[2], w[6])) + { + PIXEL00_1L + PIXEL01_C + PIXEL02_C + PIXEL12_C + } + else + { + PIXEL00_2 + PIXEL01_6 + PIXEL02_5 + PIXEL12_1 + } + PIXEL10_1 + PIXEL11 + PIXEL20_1L + PIXEL21_C + PIXEL22_1M + break; + } + case 237: + case 233: + { + PIXEL00_1U + PIXEL01_1 + PIXEL02_2 + PIXEL10_C + PIXEL11 + PIXEL12_1 + if (Diff(w[8], w[4])) + { + PIXEL20_C + } + else + { + PIXEL20_2 + } + PIXEL21_C + PIXEL22_1R + break; + } + case 175: + case 47: + { + if (Diff(w[4], w[2])) + { + PIXEL00_C + } + else + { + PIXEL00_2 + } + PIXEL01_C + PIXEL02_1R + PIXEL10_C + PIXEL11 + PIXEL12_1 + PIXEL20_1D + PIXEL21_1 + PIXEL22_2 + break; + } + case 183: + case 151: + { + PIXEL00_1L + PIXEL01_C + if (Diff(w[2], w[6])) + { + PIXEL02_C + } + else + { + PIXEL02_2 + } + PIXEL10_1 + PIXEL11 + PIXEL12_C + PIXEL20_2 + PIXEL21_1 + PIXEL22_1D + break; + } + case 245: + case 244: + { + PIXEL00_2 + PIXEL01_1 + PIXEL02_1U + PIXEL10_1 + PIXEL11 + PIXEL12_C + PIXEL20_1L + PIXEL21_C + if (Diff(w[6], w[8])) + { + PIXEL22_C + } + else + { + PIXEL22_2 + } + break; + } + case 250: + { + PIXEL00_1M + PIXEL01_C + PIXEL02_1M + PIXEL11 + if (Diff(w[8], w[4])) + { + PIXEL10_C + PIXEL20_C + } + else + { + PIXEL10_3 + PIXEL20_4 + } + PIXEL21_C + if (Diff(w[6], w[8])) + { + PIXEL12_C + PIXEL22_C + } + else + { + PIXEL12_3 + PIXEL22_4 + } + break; + } + case 123: + { + if (Diff(w[4], w[2])) + { + PIXEL00_C + PIXEL01_C + } + else + { + PIXEL00_4 + PIXEL01_3 + } + PIXEL02_1M + PIXEL10_C + PIXEL11 + PIXEL12_C + if (Diff(w[8], w[4])) + { + PIXEL20_C + PIXEL21_C + } + else + { + PIXEL20_4 + PIXEL21_3 + } + PIXEL22_1M + break; + } + case 95: + { + if (Diff(w[4], w[2])) + { + PIXEL00_C + PIXEL10_C + } + else + { + PIXEL00_4 + PIXEL10_3 + } + PIXEL01_C + if (Diff(w[2], w[6])) + { + PIXEL02_C + PIXEL12_C + } + else + { + PIXEL02_4 + PIXEL12_3 + } + PIXEL11 + PIXEL20_1M + PIXEL21_C + PIXEL22_1M + break; + } + case 222: + { + PIXEL00_1M + if (Diff(w[2], w[6])) + { + PIXEL01_C + PIXEL02_C + } + else + { + PIXEL01_3 + PIXEL02_4 + } + PIXEL10_C + PIXEL11 + PIXEL12_C + PIXEL20_1M + if (Diff(w[6], w[8])) + { + PIXEL21_C + PIXEL22_C + } + else + { + PIXEL21_3 + PIXEL22_4 + } + break; + } + case 252: + { + PIXEL00_1M + PIXEL01_1 + PIXEL02_1U + PIXEL11 + PIXEL12_C + if (Diff(w[8], w[4])) + { + PIXEL10_C + PIXEL20_C + } + else + { + PIXEL10_3 + PIXEL20_4 + } + PIXEL21_C + if (Diff(w[6], w[8])) + { + PIXEL22_C + } + else + { + PIXEL22_2 + } + break; + } + case 249: + { + PIXEL00_1U + PIXEL01_1 + PIXEL02_1M + PIXEL10_C + PIXEL11 + if (Diff(w[8], w[4])) + { + PIXEL20_C + } + else + { + PIXEL20_2 + } + PIXEL21_C + if (Diff(w[6], w[8])) + { + PIXEL12_C + PIXEL22_C + } + else + { + PIXEL12_3 + PIXEL22_4 + } + break; + } + case 235: + { + if (Diff(w[4], w[2])) + { + PIXEL00_C + PIXEL01_C + } + else + { + PIXEL00_4 + PIXEL01_3 + } + PIXEL02_1M + PIXEL10_C + PIXEL11 + PIXEL12_1 + if (Diff(w[8], w[4])) + { + PIXEL20_C + } + else + { + PIXEL20_2 + } + PIXEL21_C + PIXEL22_1R + break; + } + case 111: + { + if (Diff(w[4], w[2])) + { + PIXEL00_C + } + else + { + PIXEL00_2 + } + PIXEL01_C + PIXEL02_1R + PIXEL10_C + PIXEL11 + PIXEL12_1 + if (Diff(w[8], w[4])) + { + PIXEL20_C + PIXEL21_C + } + else + { + PIXEL20_4 + PIXEL21_3 + } + PIXEL22_1M + break; + } + case 63: + { + if (Diff(w[4], w[2])) + { + PIXEL00_C + } + else + { + PIXEL00_2 + } + PIXEL01_C + if (Diff(w[2], w[6])) + { + PIXEL02_C + PIXEL12_C + } + else + { + PIXEL02_4 + PIXEL12_3 + } + PIXEL10_C + PIXEL11 + PIXEL20_1D + PIXEL21_1 + PIXEL22_1M + break; + } + case 159: + { + if (Diff(w[4], w[2])) + { + PIXEL00_C + PIXEL10_C + } + else + { + PIXEL00_4 + PIXEL10_3 + } + PIXEL01_C + if (Diff(w[2], w[6])) + { + PIXEL02_C + } + else + { + PIXEL02_2 + } + PIXEL11 + PIXEL12_C + PIXEL20_1M + PIXEL21_1 + PIXEL22_1D + break; + } + case 215: + { + PIXEL00_1L + PIXEL01_C + if (Diff(w[2], w[6])) + { + PIXEL02_C + } + else + { + PIXEL02_2 + } + PIXEL10_1 + PIXEL11 + PIXEL12_C + PIXEL20_1M + if (Diff(w[6], w[8])) + { + PIXEL21_C + PIXEL22_C + } + else + { + PIXEL21_3 + PIXEL22_4 + } + break; + } + case 246: + { + PIXEL00_1M + if (Diff(w[2], w[6])) + { + PIXEL01_C + PIXEL02_C + } + else + { + PIXEL01_3 + PIXEL02_4 + } + PIXEL10_1 + PIXEL11 + PIXEL12_C + PIXEL20_1L + PIXEL21_C + if (Diff(w[6], w[8])) + { + PIXEL22_C + } + else + { + PIXEL22_2 + } + break; + } + case 254: + { + PIXEL00_1M + if (Diff(w[2], w[6])) + { + PIXEL01_C + PIXEL02_C + } + else + { + PIXEL01_3 + PIXEL02_4 + } + PIXEL11 + if (Diff(w[8], w[4])) + { + PIXEL10_C + PIXEL20_C + } + else + { + PIXEL10_3 + PIXEL20_4 + } + if (Diff(w[6], w[8])) + { + PIXEL12_C + PIXEL21_C + PIXEL22_C + } + else + { + PIXEL12_3 + PIXEL21_3 + PIXEL22_2 + } + break; + } + case 253: + { + PIXEL00_1U + PIXEL01_1 + PIXEL02_1U + PIXEL10_C + PIXEL11 + PIXEL12_C + if (Diff(w[8], w[4])) + { + PIXEL20_C + } + else + { + PIXEL20_2 + } + PIXEL21_C + if (Diff(w[6], w[8])) + { + PIXEL22_C + } + else + { + PIXEL22_2 + } + break; + } + case 251: + { + if (Diff(w[4], w[2])) + { + PIXEL00_C + PIXEL01_C + } + else + { + PIXEL00_4 + PIXEL01_3 + } + PIXEL02_1M + PIXEL11 + if (Diff(w[8], w[4])) + { + PIXEL10_C + PIXEL20_C + PIXEL21_C + } + else + { + PIXEL10_3 + PIXEL20_2 + PIXEL21_3 + } + if (Diff(w[6], w[8])) + { + PIXEL12_C + PIXEL22_C + } + else + { + PIXEL12_3 + PIXEL22_4 + } + break; + } + case 239: + { + if (Diff(w[4], w[2])) + { + PIXEL00_C + } + else + { + PIXEL00_2 + } + PIXEL01_C + PIXEL02_1R + PIXEL10_C + PIXEL11 + PIXEL12_1 + if (Diff(w[8], w[4])) + { + PIXEL20_C + } + else + { + PIXEL20_2 + } + PIXEL21_C + PIXEL22_1R + break; + } + case 127: + { + if (Diff(w[4], w[2])) + { + PIXEL00_C + PIXEL01_C + PIXEL10_C + } + else + { + PIXEL00_2 + PIXEL01_3 + PIXEL10_3 + } + if (Diff(w[2], w[6])) + { + PIXEL02_C + PIXEL12_C + } + else + { + PIXEL02_4 + PIXEL12_3 + } + PIXEL11 + if (Diff(w[8], w[4])) + { + PIXEL20_C + PIXEL21_C + } + else + { + PIXEL20_4 + PIXEL21_3 + } + PIXEL22_1M + break; + } + case 191: + { + if (Diff(w[4], w[2])) + { + PIXEL00_C + } + else + { + PIXEL00_2 + } + PIXEL01_C + if (Diff(w[2], w[6])) + { + PIXEL02_C + } + else + { + PIXEL02_2 + } + PIXEL10_C + PIXEL11 + PIXEL12_C + PIXEL20_1D + PIXEL21_1 + PIXEL22_1D + break; + } + case 223: + { + if (Diff(w[4], w[2])) + { + PIXEL00_C + PIXEL10_C + } + else + { + PIXEL00_4 + PIXEL10_3 + } + if (Diff(w[2], w[6])) + { + PIXEL01_C + PIXEL02_C + PIXEL12_C + } + else + { + PIXEL01_3 + PIXEL02_2 + PIXEL12_3 + } + PIXEL11 + PIXEL20_1M + if (Diff(w[6], w[8])) + { + PIXEL21_C + PIXEL22_C + } + else + { + PIXEL21_3 + PIXEL22_4 + } + break; + } + case 247: + { + PIXEL00_1L + PIXEL01_C + if (Diff(w[2], w[6])) + { + PIXEL02_C + } + else + { + PIXEL02_2 + } + PIXEL10_1 + PIXEL11 + PIXEL12_C + PIXEL20_1L + PIXEL21_C + if (Diff(w[6], w[8])) + { + PIXEL22_C + } + else + { + PIXEL22_2 + } + break; + } + case 255: + { + if (Diff(w[4], w[2])) + { + PIXEL00_C + } + else + { + PIXEL00_2 + } + PIXEL01_C + if (Diff(w[2], w[6])) + { + PIXEL02_C + } + else + { + PIXEL02_2 + } + PIXEL10_C + PIXEL11 + PIXEL12_C + if (Diff(w[8], w[4])) + { + PIXEL20_C + } + else + { + PIXEL20_2 + } + PIXEL21_C + if (Diff(w[6], w[8])) + { + PIXEL22_C + } + else + { + PIXEL22_2 + } + break; + } + } + ++pIn; + pOut += 3; + } + pOut += dstPitch * 3 - Xres * 3; + } +} + +MaxSt_Hq3x::MaxSt_Hq3x() { + buffer = NULL; +} + +MaxSt_Hq3x::~MaxSt_Hq3x() { + outit(); +} + +void MaxSt_Hq3x::init() { + delete []buffer; + buffer = new Gambatte::uint_least32_t[144 * 160]; +} + +void MaxSt_Hq3x::outit() { + delete []buffer; + buffer = NULL; +} + +const Gambatte::FilterInfo& MaxSt_Hq3x::info() { + static const Gambatte::FilterInfo fInfo = { "MaxSt's Hq3x", 160 * 3, 144 * 3 }; + return fInfo; +} + +Gambatte::uint_least32_t* MaxSt_Hq3x::inBuffer() { + return buffer; +} + +unsigned MaxSt_Hq3x::inPitch() { + return 160; +} + +void MaxSt_Hq3x::filter(Gambatte::uint_least32_t *const dbuffer, const unsigned pitch) { + ::filter(dbuffer, pitch, buffer, 160, 144); +} diff --git a/supergameboy/libgambatte/src/video/filters/maxsthq3x.h b/supergameboy/libgambatte/src/video/filters/maxsthq3x.h new file mode 100644 index 00000000..9e1f51d6 --- /dev/null +++ b/supergameboy/libgambatte/src/video/filters/maxsthq3x.h @@ -0,0 +1,40 @@ +/*************************************************************************** + * Copyright (C) 2007 by Sindre AamÃ¥s * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#ifndef MAXSTHQ3X_H +#define MAXSTHQ3X_H + +#include "filter.h" + +struct FilterInfo; + +class MaxSt_Hq3x : public Filter { + Gambatte::uint_least32_t *buffer; + +public: + MaxSt_Hq3x(); + ~MaxSt_Hq3x(); + void init(); + void outit(); + const Gambatte::FilterInfo& info(); + void filter(Gambatte::uint_least32_t *dbuffer, unsigned pitch); + Gambatte::uint_least32_t* inBuffer(); + unsigned inPitch(); +}; + +#endif diff --git a/supergameboy/libgambatte/src/video/irq_event.cpp b/supergameboy/libgambatte/src/video/irq_event.cpp new file mode 100644 index 00000000..358f1daf --- /dev/null +++ b/supergameboy/libgambatte/src/video/irq_event.cpp @@ -0,0 +1,36 @@ +/*************************************************************************** + * Copyright (C) 2007 by Sindre Aamås * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#include "irq_event.h" + +IrqEvent::IrqEvent(event_queue &irqEventQueue_in) : + VideoEvent(11), + irqEventQueue(irqEventQueue_in) +{ +} + +void IrqEvent::doEvent() { + irqEventQueue.top()->doEvent(); + + if (irqEventQueue.top()->time() == DISABLED_TIME) + irqEventQueue.pop(); + else + irqEventQueue.modify_root(irqEventQueue.top()); + + setTime(schedule(irqEventQueue)); +} diff --git a/supergameboy/libgambatte/src/video/irq_event.h b/supergameboy/libgambatte/src/video/irq_event.h new file mode 100644 index 00000000..c8a5b991 --- /dev/null +++ b/supergameboy/libgambatte/src/video/irq_event.h @@ -0,0 +1,52 @@ +/*************************************************************************** + * Copyright (C) 2007 by Sindre Aamås * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ +#ifndef VIDEO_IRQ_EVENT_H +#define VIDEO_IRQ_EVENT_H + +#include "../event_queue.h" +#include "video_event.h" +#include "video_event_comparer.h" +#include "basic_add_event.h" + +class IrqEvent : public VideoEvent { + event_queue &irqEventQueue; + +public: + IrqEvent(event_queue &irqEventQueue_in); + + void doEvent(); + + static unsigned long schedule(const event_queue &irqEventQueue) { + return irqEventQueue.top()->time(); + } + + void schedule() { + setTime(irqEventQueue.top()->time()); + } +}; + +static inline void addEvent(event_queue &q, IrqEvent *const e, const unsigned long newTime) { + addUnconditionalEvent(q, e, newTime); +} + +static inline void addFixedtimeEvent(event_queue &q, IrqEvent *const e, const unsigned long newTime) { + addUnconditionalFixedtimeEvent(q, e, newTime); +} + +#endif diff --git a/supergameboy/libgambatte/src/video/ly_counter.cpp b/supergameboy/libgambatte/src/video/ly_counter.cpp new file mode 100644 index 00000000..5d5b6d98 --- /dev/null +++ b/supergameboy/libgambatte/src/video/ly_counter.cpp @@ -0,0 +1,62 @@ +/*************************************************************************** + * Copyright (C) 2007 by Sindre Aam�s * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#include "ly_counter.h" +#include "../savestate.h" + +LyCounter::LyCounter() : VideoEvent(0) { + setDoubleSpeed(false); + reset(0, 0); +} + +void LyCounter::doEvent() { + ++ly_; + + if (ly_ == 154) + ly_ = 0; + + setTime(time() + lineTime_); +} + +unsigned long LyCounter::nextLineCycle(const unsigned lineCycle, const unsigned long cycleCounter) const { + unsigned long tmp = time() + (lineCycle << ds); + + if (tmp - cycleCounter > lineTime_) + tmp -= lineTime_; + + return tmp; +} + +unsigned long LyCounter::nextFrameCycle(const unsigned long frameCycle, const unsigned long cycleCounter) const { + unsigned long tmp = time() + (((153U - ly()) * 456U + frameCycle) << ds); + + if (tmp - cycleCounter > 70224U << ds) + tmp -= 70224U << ds; + + return tmp; +} + +void LyCounter::reset(const unsigned long videoCycles, const unsigned long lastUpdate) { + ly_ = videoCycles / 456; + setTime(lastUpdate + ((456 - (videoCycles - ly_ * 456ul)) << isDoubleSpeed())); +} + +void LyCounter::setDoubleSpeed(const bool ds_in) { + ds = ds_in; + lineTime_ = 456U << ds_in; +} diff --git a/supergameboy/libgambatte/src/video/ly_counter.h b/supergameboy/libgambatte/src/video/ly_counter.h new file mode 100644 index 00000000..2b795fb8 --- /dev/null +++ b/supergameboy/libgambatte/src/video/ly_counter.h @@ -0,0 +1,69 @@ +/*************************************************************************** + * Copyright (C) 2007 by Sindre Aamås * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#ifndef LY_COUNTER_H +#define LY_COUNTER_H + +class SaveState; + +#include "video_event.h" +#include "basic_add_event.h" + +class LyCounter : public VideoEvent { + unsigned short lineTime_; + unsigned char ly_; + bool ds; + +public: + LyCounter(); + + void doEvent(); + + bool isDoubleSpeed() const { + return ds; + } + + unsigned lineCycles(const unsigned long cc) const { + return 456u - ((time() - cc) >> isDoubleSpeed()); + } + + unsigned lineTime() const { + return lineTime_; + } + + unsigned ly() const { + return ly_; + } + + unsigned long nextLineCycle(unsigned lineCycle, unsigned long cycleCounter) const; + unsigned long nextFrameCycle(unsigned long frameCycle, unsigned long cycleCounter) const; + + void reset(unsigned long videoCycles, unsigned long lastUpdate); + + void setDoubleSpeed(bool ds_in); +}; + +static inline void addEvent(event_queue &q, LyCounter *const e, const unsigned long newTime) { + addUnconditionalEvent(q, e, newTime); +} + +static inline void addFixedtimeEvent(event_queue &q, LyCounter *const e, const unsigned long newTime) { + addUnconditionalFixedtimeEvent(q, e, newTime); +} + +#endif diff --git a/supergameboy/libgambatte/src/video/lyc_irq.cpp b/supergameboy/libgambatte/src/video/lyc_irq.cpp new file mode 100644 index 00000000..eb81d41b --- /dev/null +++ b/supergameboy/libgambatte/src/video/lyc_irq.cpp @@ -0,0 +1,42 @@ +/*************************************************************************** + * Copyright (C) 2007 by Sindre Aamås * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#include "lyc_irq.h" + +LycIrq::LycIrq(unsigned char &ifReg_in) : + VideoEvent(1), + ifReg(ifReg_in) +{ + setDoubleSpeed(false); + setM2IrqEnabled(false); + setLycReg(0); + setSkip(false); +} + +void LycIrq::doEvent() { + if (!skip && (!m2IrqEnabled || lycReg_ > 143 || lycReg_ == 0)) + ifReg |= 0x2; + + skip = false; + + setTime(time() + frameTime); +} + +unsigned long LycIrq::schedule(const unsigned statReg, const unsigned lycReg, const LyCounter &lyCounter, const unsigned long cycleCounter) { + return ((statReg & 0x40) && lycReg < 154) ? lyCounter.nextFrameCycle(lycReg ? lycReg * 456 : 153 * 456 + 8, cycleCounter) : static_cast(DISABLED_TIME); +} diff --git a/supergameboy/libgambatte/src/video/lyc_irq.h b/supergameboy/libgambatte/src/video/lyc_irq.h new file mode 100644 index 00000000..ed93fdda --- /dev/null +++ b/supergameboy/libgambatte/src/video/lyc_irq.h @@ -0,0 +1,67 @@ +/*************************************************************************** + * Copyright (C) 2007 by Sindre Aamås * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#ifndef VIDEO_LYC_IRQ_H +#define VIDEO_LYC_IRQ_H + +#include "ly_counter.h" + +class LycIrq : public VideoEvent { + unsigned char &ifReg; + unsigned long frameTime; + unsigned char lycReg_; + bool m2IrqEnabled; + bool skip; + +public: + LycIrq(unsigned char &ifReg_in); + + void doEvent(); + + unsigned lycReg() const { + return lycReg_; + } + + static unsigned long schedule(unsigned statReg, unsigned lycReg, const LyCounter &lyCounter, unsigned long cycleCounter); + + void setDoubleSpeed(const bool ds) { + frameTime = 70224 << ds; + } + + void setLycReg(const unsigned lycReg_in) { + lycReg_ = lycReg_in; + } + + void setM2IrqEnabled(const bool enabled) { + m2IrqEnabled = enabled; + } + + void setSkip(const bool skip) { + this->skip = skip; + } + + bool skips() const { + return skip; + } + + bool isSkipPeriod(const unsigned long cycleCounter, const bool doubleSpeed) const { + return lycReg_ > 0 && time() - cycleCounter > 4U >> doubleSpeed && time() - cycleCounter < 9; + } +}; + +#endif diff --git a/supergameboy/libgambatte/src/video/m3_extra_cycles.cpp b/supergameboy/libgambatte/src/video/m3_extra_cycles.cpp new file mode 100644 index 00000000..de4eadb7 --- /dev/null +++ b/supergameboy/libgambatte/src/video/m3_extra_cycles.cpp @@ -0,0 +1,101 @@ +/*************************************************************************** + * Copyright (C) 2007 by Sindre Aamås * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#include "m3_extra_cycles.h" +#include "scx_reader.h" +#include "window.h" +#include "sprite_mapper.h" +#include "../insertion_sort.h" + +M3ExtraCycles::M3ExtraCycles(const SpriteMapper &spriteMapper, + const ScxReader &scxReader, + const Window &win) : + spriteMapper(spriteMapper), + scxReader(scxReader), + win(win) +{ + invalidateCache(); +} + +static const unsigned char* addLineCycles(const unsigned char *const start, const unsigned char *const end, + const unsigned maxSpx, const unsigned scwxAnd7, const unsigned char *const posbuf_plus1, unsigned char *cycles_out) { + unsigned sum = 0; + + const unsigned char *a = start; + + for (; a < end; ++a) { + const unsigned spx = posbuf_plus1[*a]; + + if (spx > maxSpx) + break; + + unsigned cycles = 6; + const unsigned posAnd7 = (scwxAnd7 + spx) & 7; + + if (posAnd7 < 5) { + cycles = 11 - posAnd7; + + for (const unsigned char *b = a; b > start;) { + const unsigned bSpx = posbuf_plus1[*--b]; + + if (spx - bSpx > 4U) + break; + + if (((scwxAnd7 + bSpx) & 7) < 4 || spx == bSpx) { + cycles = 6; + break; + } + } + } + + sum += cycles; + } + + *cycles_out += sum; + + return a; +} + +void M3ExtraCycles::updateLine(const unsigned ly) const { + const bool windowEnabled = win.enabled(ly); + + cycles[ly] = windowEnabled ? scxReader.scxAnd7() + 6 : scxReader.scxAnd7(); + + const unsigned numSprites = spriteMapper.numSprites(ly); + + if (numSprites == 0) + return; + + unsigned char sortBuf[10]; + const unsigned char *tmp = spriteMapper.sprites(ly); + + if (spriteMapper.isCgb()) { + std::memcpy(sortBuf, tmp, sizeof(sortBuf)); + insertionSort(sortBuf, sortBuf + numSprites, SpriteMapper::SpxLess(spriteMapper.posbuf())); + tmp = sortBuf; + } + + const unsigned char *const tmpend = tmp + numSprites; + const unsigned char *const posbuf_plus1 = spriteMapper.posbuf() + 1; + + if (windowEnabled) { + addLineCycles(addLineCycles(tmp, tmpend, win.wxReader.wx(), scxReader.scxAnd7(), posbuf_plus1, cycles + ly), + tmpend, 167, 7 - win.wxReader.wx(), posbuf_plus1, cycles + ly); + } else + addLineCycles(tmp, tmpend, 167, scxReader.scxAnd7(), posbuf_plus1, cycles + ly); +} diff --git a/supergameboy/libgambatte/src/video/m3_extra_cycles.h b/supergameboy/libgambatte/src/video/m3_extra_cycles.h new file mode 100644 index 00000000..8a7f1470 --- /dev/null +++ b/supergameboy/libgambatte/src/video/m3_extra_cycles.h @@ -0,0 +1,56 @@ +/*************************************************************************** + * Copyright (C) 2007 by Sindre Aamås * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#ifndef VIDEO_M3_EXTRA_CYCLES_H +#define VIDEO_M3_EXTRA_CYCLES_H + +class ScxReader; +class Window; +class SpriteMapper; + +#include + +class M3ExtraCycles { + enum { CYCLES_INVALID = 0xFF }; + + mutable unsigned char cycles[144]; + + const SpriteMapper &spriteMapper; + const ScxReader &scxReader; + const Window &win; + + void updateLine(unsigned ly) const; + +public: + M3ExtraCycles(const SpriteMapper &spriteMapper, + const ScxReader &scxReader_in, + const Window &win); + + void invalidateCache() { + std::memset(cycles, CYCLES_INVALID, sizeof(cycles)); + } + + unsigned operator()(const unsigned ly) const { + if (cycles[ly] == CYCLES_INVALID) + updateLine(ly); + + return cycles[ly]; + } +}; + +#endif diff --git a/supergameboy/libgambatte/src/video/mode0_irq.cpp b/supergameboy/libgambatte/src/video/mode0_irq.cpp new file mode 100644 index 00000000..041d3db1 --- /dev/null +++ b/supergameboy/libgambatte/src/video/mode0_irq.cpp @@ -0,0 +1,95 @@ +/*************************************************************************** + * Copyright (C) 2007 by Sindre Aam�s * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * +***************************************************************************/ +#include "mode0_irq.h" + +#include "ly_counter.h" +#include "lyc_irq.h" +#include "m3_extra_cycles.h" + +Mode0Irq::Mode0Irq(const LyCounter &lyCounter_in, const LycIrq &lycIrq_in, + const M3ExtraCycles &m3ExtraCycles_in, unsigned char &ifReg_in) : + VideoEvent(0), + lyCounter(lyCounter_in), + lycIrq(lycIrq_in), + m3ExtraCycles(m3ExtraCycles_in), + ifReg(ifReg_in) +{ +} + +static unsigned baseCycle(const bool ds) { + return 80 + 169 + ds * 3 + 1 - ds; +} + +void Mode0Irq::doEvent() { + if (lycIrq.time() == DISABLED_TIME || lyCounter.ly() != lycIrq.lycReg()) + ifReg |= 2; + + unsigned long nextTime = lyCounter.time(); + unsigned nextLy = lyCounter.ly() + 1; + + if (nextLy == 144) { + nextLy = 0; + nextTime += lyCounter.lineTime() * 10; + } + + nextTime += (baseCycle(lyCounter.isDoubleSpeed()) + m3ExtraCycles(nextLy)) << lyCounter.isDoubleSpeed(); + + setTime(nextTime); +} + +void Mode0Irq::mode3CyclesChange() { + unsigned long nextTime = lyCounter.time() - lyCounter.lineTime(); + unsigned nextLy = lyCounter.ly(); + + if (time() > lyCounter.time()) { + nextTime += lyCounter.lineTime(); + ++nextLy; + + if (nextLy > 143) { + nextTime += lyCounter.lineTime() * (154 - nextLy); + nextLy = 0; + } + } + + nextTime += (baseCycle(lyCounter.isDoubleSpeed()) + m3ExtraCycles(nextLy)) << lyCounter.isDoubleSpeed(); + + setTime(nextTime); +} + +unsigned long Mode0Irq::schedule(const unsigned statReg, const M3ExtraCycles &m3ExtraCycles, const LyCounter &lyCounter, const unsigned long cycleCounter) { + if (!(statReg & 0x08)) + return DISABLED_TIME; + + unsigned line = lyCounter.ly(); + int next = static_cast(baseCycle(lyCounter.isDoubleSpeed())) - static_cast(lyCounter.lineCycles(cycleCounter)); + + if (line < 144 && next + static_cast(m3ExtraCycles(line)) <= 0) { + next += 456; + ++line; + } + + if (line > 143) { + next += (154 - line) * 456; + line = 0; + } + + next += m3ExtraCycles(line); + + return cycleCounter + (static_cast(next) << lyCounter.isDoubleSpeed()); +} diff --git a/supergameboy/libgambatte/src/video/mode0_irq.h b/supergameboy/libgambatte/src/video/mode0_irq.h new file mode 100644 index 00000000..bc5f1540 --- /dev/null +++ b/supergameboy/libgambatte/src/video/mode0_irq.h @@ -0,0 +1,42 @@ +/*************************************************************************** + * Copyright (C) 2007 by Sindre Aamås * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#ifndef VIDEO_MODE0_IRQ_H +#define VIDEO_MODE0_IRQ_H + +class LycIrq; +class M3ExtraCycles; + +#include "ly_counter.h" + +class Mode0Irq : public VideoEvent { + const LyCounter &lyCounter; + const LycIrq &lycIrq; + const M3ExtraCycles &m3ExtraCycles; + unsigned char &ifReg; + +public: + Mode0Irq(const LyCounter &lyCounter_in, const LycIrq &lycIrq_in, + const M3ExtraCycles &m3ExtraCycles_in, unsigned char &ifReg_in); + + void doEvent(); + void mode3CyclesChange(); + static unsigned long schedule(unsigned statReg, const M3ExtraCycles &m3ExtraCycles, const LyCounter &lyCounter, unsigned long cycleCounter); +}; + +#endif diff --git a/supergameboy/libgambatte/src/video/mode1_irq.cpp b/supergameboy/libgambatte/src/video/mode1_irq.cpp new file mode 100644 index 00000000..ddafe25c --- /dev/null +++ b/supergameboy/libgambatte/src/video/mode1_irq.cpp @@ -0,0 +1,33 @@ +/*************************************************************************** + * Copyright (C) 2007 by Sindre Aamås * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#include "mode1_irq.h" + +Mode1Irq::Mode1Irq(unsigned char &ifReg_in) : + VideoEvent(0), + ifReg(ifReg_in) +{ + setDoubleSpeed(false); + setM1StatIrqEnabled(false); +} + +void Mode1Irq::doEvent() { + ifReg |= flags; + + setTime(time() + frameTime); +} diff --git a/supergameboy/libgambatte/src/video/mode1_irq.h b/supergameboy/libgambatte/src/video/mode1_irq.h new file mode 100644 index 00000000..f4e6270f --- /dev/null +++ b/supergameboy/libgambatte/src/video/mode1_irq.h @@ -0,0 +1,56 @@ +/*************************************************************************** + * Copyright (C) 2007 by Sindre Aamås * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#ifndef VIDEO_MODE1_IRQ_H +#define VIDEO_MODE1_IRQ_H + +#include "ly_counter.h" +#include "basic_add_event.h" + +class Mode1Irq : public VideoEvent { + unsigned char &ifReg; + unsigned long frameTime; + unsigned char flags; + +public: + Mode1Irq(unsigned char &ifReg_in); + + void doEvent(); + + static unsigned long schedule(const LyCounter &lyCounter, unsigned long cycleCounter) { + return lyCounter.nextFrameCycle(144 * 456, cycleCounter); + } + + void setDoubleSpeed(const bool ds) { + frameTime = 70224 << ds; + } + + void setM1StatIrqEnabled(const bool enabled) { + flags = (enabled * 2) | 1; + } +}; + +static inline void addEvent(event_queue &q, Mode1Irq *const e, const unsigned long newTime) { + addUnconditionalEvent(q, e, newTime); +} + +static inline void addFixedtimeEvent(event_queue &q, Mode1Irq *const e, const unsigned long newTime) { + addUnconditionalFixedtimeEvent(q, e, newTime); +} + +#endif diff --git a/supergameboy/libgambatte/src/video/mode2_irq.cpp b/supergameboy/libgambatte/src/video/mode2_irq.cpp new file mode 100644 index 00000000..b1a419ea --- /dev/null +++ b/supergameboy/libgambatte/src/video/mode2_irq.cpp @@ -0,0 +1,63 @@ +/*************************************************************************** + * Copyright (C) 2007 by Sindre Aamås * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#include "mode2_irq.h" + +#include "ly_counter.h" +#include "lyc_irq.h" + +Mode2Irq::Mode2Irq(const LyCounter &lyCounter_in, const LycIrq &lycIrq_in, + unsigned char &ifReg_in) : + VideoEvent(0), + lyCounter(lyCounter_in), + lycIrq(lycIrq_in), + ifReg(ifReg_in) +{ +} + +void Mode2Irq::doEvent() { + const unsigned ly = lyCounter.time() - time() < 8 ? (lyCounter.ly() == 153 ? 0 : lyCounter.ly() + 1) : lyCounter.ly(); + + if (lycIrq.time() == DISABLED_TIME || (lycIrq.lycReg() != 0 && ly != (lycIrq.lycReg() + 1U)) || (lycIrq.lycReg() == 0 && ly > 1)) + ifReg |= 2; + + setTime(time() + lyCounter.lineTime()); + + if (ly == 0) + setTime(time() - 4); + else if (ly == 143) + setTime(time() + lyCounter.lineTime() * 10 + 4); +} + +unsigned long Mode2Irq::schedule(const unsigned statReg, const LyCounter &lyCounter, const unsigned long cycleCounter) { + if ((statReg & 0x28) != 0x20) + return DISABLED_TIME; + + unsigned next = lyCounter.time() - cycleCounter; + + if (lyCounter.ly() >= 143 || (lyCounter.ly() == 142 && next <= 4)) { + next += (153u - lyCounter.ly()) * lyCounter.lineTime(); + } else { + if (next <= 4) + next += lyCounter.lineTime(); + + next -= 4; + } + + return cycleCounter + next; +} diff --git a/supergameboy/libgambatte/src/video/mode2_irq.h b/supergameboy/libgambatte/src/video/mode2_irq.h new file mode 100644 index 00000000..2ea86055 --- /dev/null +++ b/supergameboy/libgambatte/src/video/mode2_irq.h @@ -0,0 +1,40 @@ +/*************************************************************************** + * Copyright (C) 2007 by Sindre Aamås * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#ifndef VIDEO_MODE2_IRQ_H +#define VIDEO_MODE2_IRQ_H + +class LycIrq; + +#include "ly_counter.h" +#include "basic_add_event.h" + +class Mode2Irq : public VideoEvent { + const LyCounter &lyCounter; + const LycIrq &lycIrq; + unsigned char &ifReg; + +public: + Mode2Irq(const LyCounter &lyCounter_in, const LycIrq &lycIrq_in, + unsigned char &ifReg_in); + + void doEvent(); + static unsigned long schedule(unsigned statReg, const LyCounter &lyCounter, unsigned long cycleCounter); +}; + +#endif diff --git a/supergameboy/libgambatte/src/video/mode3_event.cpp b/supergameboy/libgambatte/src/video/mode3_event.cpp new file mode 100644 index 00000000..84502315 --- /dev/null +++ b/supergameboy/libgambatte/src/video/mode3_event.cpp @@ -0,0 +1,62 @@ +/*************************************************************************** + * Copyright (C) 2007 by Sindre Aamås * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#include "mode3_event.h" +#include "mode0_irq.h" +#include "irq_event.h" + +Mode3Event::Mode3Event(event_queue &m3EventQueue_in, + event_queue &vEventQueue_in, + Mode0Irq &mode0Irq_in, IrqEvent &irqEvent_in) : + VideoEvent(1), + m3EventQueue(m3EventQueue_in), + vEventQueue(vEventQueue_in), + mode0Irq(mode0Irq_in), + irqEvent(irqEvent_in) +{ +} + +void Mode3Event::doEvent() { + m3EventQueue.top()->doEvent(); + + if (m3EventQueue.top()->time() == DISABLED_TIME) + m3EventQueue.pop(); + else + m3EventQueue.modify_root(m3EventQueue.top()); + + if (mode0Irq.time() != DISABLED_TIME) { + const unsigned long oldTime = mode0Irq.time(); + mode0Irq.mode3CyclesChange(); + + if (mode0Irq.time() != oldTime) { + // position in irqEventQueue should remain the same. + // The same may be possible for vEventQueue, with some precautions. + if (irqEvent.time() == oldTime) { + irqEvent.schedule(); + + if (mode0Irq.time() > oldTime) + vEventQueue.inc(&irqEvent, &irqEvent); + else + vEventQueue.dec(&irqEvent, &irqEvent); + } + + } + } + + setTime(schedule(m3EventQueue)); +} diff --git a/supergameboy/libgambatte/src/video/mode3_event.h b/supergameboy/libgambatte/src/video/mode3_event.h new file mode 100644 index 00000000..7f9aedc6 --- /dev/null +++ b/supergameboy/libgambatte/src/video/mode3_event.h @@ -0,0 +1,47 @@ +/*************************************************************************** + * Copyright (C) 2007 by Sindre Aamås * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#ifndef MODE3_EVENT_H +#define MODE3_EVENT_H + +class Mode0Irq; +class IrqEvent; + +#include "video_event.h" +#include "video_event_comparer.h" +#include "../event_queue.h" + +class Mode3Event : public VideoEvent { + event_queue &m3EventQueue; + event_queue &vEventQueue; + Mode0Irq &mode0Irq; + IrqEvent &irqEvent; + +public: + Mode3Event(event_queue &m3EventQueue_in, + event_queue &vEventQueue_in, + Mode0Irq &mode0Irq_in, IrqEvent &irqEvent_in); + + void doEvent(); + + static unsigned long schedule(const event_queue &m3EventQueue) { + return m3EventQueue.empty() ? static_cast(DISABLED_TIME) : m3EventQueue.top()->time(); + } +}; + +#endif diff --git a/supergameboy/libgambatte/src/video/sc_reader.cpp b/supergameboy/libgambatte/src/video/sc_reader.cpp new file mode 100644 index 00000000..fff2f66c --- /dev/null +++ b/supergameboy/libgambatte/src/video/sc_reader.cpp @@ -0,0 +1,62 @@ +/*************************************************************************** + * Copyright (C) 2007 by Sindre Aamås * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#include "sc_reader.h" + +#include "../event_queue.h" +#include "../savestate.h" + +ScReader::ScReader() : VideoEvent(2) { + setDoubleSpeed(false); + setScxSource(0); + setScySource(0); + scx_[1] = scx_[0] = scxSrc; + scy_[1] = scy_[0] = scySrc; +} + +void ScReader::doEvent() { + scy_[0] = scy_[1]; + scy_[1] = scySrc; + scx_[0] = scx_[1]; + scx_[1] = scxSrc; + + if ((scy_[0] ^ scy_[1]) | (scx_[0] ^ scx_[1])) + setTime(time() + incCycles); + else + setTime(DISABLED_TIME); + +} + +void ScReader::saveState(SaveState &state) const { + state.ppu.scx[0] = scx_[0]; + state.ppu.scx[1] = scx_[1]; + state.ppu.scy[0] = scy_[0]; + state.ppu.scy[1] = scy_[1]; +} + +void ScReader::loadState(const SaveState &state) { + scx_[0] = state.ppu.scx[0]; + scx_[1] = state.ppu.scx[1]; + scy_[0] = state.ppu.scy[0]; + scy_[1] = state.ppu.scy[1]; +} + +void ScReader::setDoubleSpeed(const bool dS_in) { + dS = dS_in; + incCycles = 8u << dS_in; +} diff --git a/supergameboy/libgambatte/src/video/sc_reader.h b/supergameboy/libgambatte/src/video/sc_reader.h new file mode 100644 index 00000000..0d7ef7d1 --- /dev/null +++ b/supergameboy/libgambatte/src/video/sc_reader.h @@ -0,0 +1,77 @@ +/*************************************************************************** + * Copyright (C) 2007 by Sindre Aam�s * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#ifndef SC_READER_H +#define SC_READER_H + +class SaveState; +template class event_queue; + +#include "video_event.h" +#include "video_event_comparer.h" +#include "basic_add_event.h" + +class ScReader : public VideoEvent { + unsigned char scx_[2]; + unsigned char scy_[2]; + + unsigned char scxSrc; + unsigned char scySrc; + unsigned char incCycles; + bool dS; + +public: + ScReader(); + + void doEvent(); + + unsigned scx() const { + return /*(*/scx_[0]/* & ~0x7) | (scxSrc & 0x7)*/; + } + + unsigned scy() const { + return scy_[0]; + } + + static unsigned long schedule(const unsigned long lastUpdate, const unsigned long videoCycles, const unsigned scReadOffset, const bool dS) { + return lastUpdate + ((8u - ((videoCycles - scReadOffset) & 7)) << dS); + } + + void setDoubleSpeed(bool dS_in); + + void setScxSource(const unsigned scxSrc_in) { + scxSrc = scxSrc_in; + } + + void setScySource(const unsigned scySrc_in) { + scySrc = scySrc_in; + } + + void saveState(SaveState &state) const; + void loadState(const SaveState &state); +}; + +static inline void addEvent(event_queue &q, ScReader *const e, const unsigned long newTime) { + addUnconditionalEvent(q, e, newTime); +} + +static inline void addFixedtimeEvent(event_queue &q, ScReader *const e, const unsigned long newTime) { + addUnconditionalFixedtimeEvent(q, e, newTime); +} + +#endif diff --git a/supergameboy/libgambatte/src/video/scx_reader.cpp b/supergameboy/libgambatte/src/video/scx_reader.cpp new file mode 100644 index 00000000..6baa97f9 --- /dev/null +++ b/supergameboy/libgambatte/src/video/scx_reader.cpp @@ -0,0 +1,71 @@ +/*************************************************************************** + * Copyright (C) 2007 by Sindre Aam�s * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#include "scx_reader.h" + +#include "../event_queue.h" +#include "m3_extra_cycles.h" +#include "../savestate.h" + +ScxReader::ScxReader(event_queue &m3EventQueue_in, +// VideoEvent &wyReader3_in, + VideoEvent &wxReader_in, + VideoEvent &weEnableChecker_in, + VideoEvent &weDisableChecker_in, + M3ExtraCycles &m3ExtraCycles) : + VideoEvent(1), + m3EventQueue(m3EventQueue_in), +// wyReader3(wyReader3_in), + wxReader(wxReader_in), + weEnableChecker(weEnableChecker_in), + weDisableChecker(weDisableChecker_in), + m3ExtraCycles(m3ExtraCycles) +{ + setDoubleSpeed(false); + setSource(0); + scxAnd7_ = src; +} + +static void rescheduleEvent(event_queue &m3EventQueue, VideoEvent& event, const unsigned long diff) { + if (event.time() != VideoEvent::DISABLED_TIME) { + event.setTime(event.time() + diff); + (diff & 0x10) ? m3EventQueue.dec(&event, &event) : m3EventQueue.inc(&event, &event); + } +} + +void ScxReader::doEvent() { + const unsigned long diff = (static_cast(src) - static_cast(scxAnd7_)) << dS; + scxAnd7_ = src; + +// rescheduleEvent(m3EventQueue, wyReader3, diff); + rescheduleEvent(m3EventQueue, wxReader, diff); + rescheduleEvent(m3EventQueue, weEnableChecker, diff); + rescheduleEvent(m3EventQueue, weDisableChecker, diff); + + m3ExtraCycles.invalidateCache(); + + setTime(DISABLED_TIME); +} + +void ScxReader::saveState(SaveState &state) const { + state.ppu.scxAnd7 = scxAnd7_; +} + +void ScxReader::loadState(const SaveState &state) { + scxAnd7_ = state.ppu.scxAnd7; +} diff --git a/supergameboy/libgambatte/src/video/scx_reader.h b/supergameboy/libgambatte/src/video/scx_reader.h new file mode 100644 index 00000000..f92f8b2b --- /dev/null +++ b/supergameboy/libgambatte/src/video/scx_reader.h @@ -0,0 +1,85 @@ +/*************************************************************************** + * Copyright (C) 2007 by Sindre Aamås * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#ifndef SCX_READER_H +#define SCX_READER_H + +template class event_queue; +class M3ExtraCycles; +class SaveState; + +#include "video_event.h" +#include "video_event_comparer.h" +#include "ly_counter.h" +#include "basic_add_event.h" + +class ScxReader : public VideoEvent { + event_queue &m3EventQueue; +// VideoEvent &wyReader3; + VideoEvent &wxReader; + VideoEvent &weEnableChecker; + VideoEvent &weDisableChecker; + M3ExtraCycles &m3ExtraCycles; + + unsigned char scxAnd7_; + unsigned char src; + bool dS; + +public: + ScxReader(event_queue &m3EventQueue_in, +// VideoEvent &wyReader3_in, + VideoEvent &wxReader_in, + VideoEvent &weEnableChecker_in, + VideoEvent &weDisableChecker_in, + M3ExtraCycles &m3ExtraCycles); + + void doEvent(); + + unsigned getSource() const { + return src; + } + + static unsigned long schedule(const LyCounter &lyCounter, const unsigned long cycleCounter) { + return lyCounter.nextLineCycle(82 + lyCounter.isDoubleSpeed() * 3, cycleCounter); + } + + unsigned scxAnd7() const { + return scxAnd7_; + } + + void setDoubleSpeed(const bool dS_in) { + dS = dS_in; + } + + void setSource(const unsigned scxSrc) { + src = scxSrc & 7; + } + + void saveState(SaveState &state) const; + void loadState(const SaveState &state); +}; + +static inline void addEvent(event_queue &q, ScxReader *const e, const unsigned long newTime) { + addUnconditionalEvent(q, e, newTime); +} + +static inline void addFixedtimeEvent(event_queue &q, ScxReader *const e, const unsigned long newTime) { + addUnconditionalFixedtimeEvent(q, e, newTime); +} + +#endif diff --git a/supergameboy/libgambatte/src/video/sprite_mapper.cpp b/supergameboy/libgambatte/src/video/sprite_mapper.cpp new file mode 100644 index 00000000..f1e9cd97 --- /dev/null +++ b/supergameboy/libgambatte/src/video/sprite_mapper.cpp @@ -0,0 +1,187 @@ +/*************************************************************************** + * Copyright (C) 2007 by Sindre AamÃ¥s * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#include "sprite_mapper.h" +#include "m3_extra_cycles.h" +#include "../insertion_sort.h" +#include + +#include + +SpriteMapper::OamReader::OamReader(const LyCounter &lyCounter, const unsigned char *oamram) +: lyCounter(lyCounter), oamram(oamram) { + reset(oamram); +} + +void SpriteMapper::OamReader::reset(const unsigned char *const oamram) { + this->oamram = oamram; + setLargeSpritesSrc(false); + lu = 0; + lastChange = 0xFF; + std::fill_n(szbuf, 40, largeSpritesSrc); + + unsigned pos = 0; + unsigned distance = 80; + + while (distance--) { + buf[pos] = oamram[((pos * 2) & ~3) | (pos & 1)]; + ++pos; + } +} + +static unsigned toPosCycles(const unsigned long cc, const LyCounter &lyCounter) { + unsigned lc = lyCounter.lineCycles(cc) + 4 - lyCounter.isDoubleSpeed() * 3u; + + if (lc >= 456) + lc -= 456; + + return lc >> 1; +} + +void SpriteMapper::OamReader::update(const unsigned long cc) { + if (cc > lu) { + if (changed()) { + const unsigned lulc = toPosCycles(lu, lyCounter); + + unsigned pos = std::min(lulc, 40u); + unsigned distance = 40; + + if ((cc - lu) >> lyCounter.isDoubleSpeed() < 456) { + const unsigned cclc = toPosCycles(cc, lyCounter); + + distance = std::min(cclc, 40u) - pos + (cclc < lulc ? 40 : 0); + } + + { + const unsigned targetDistance = lastChange - pos + (lastChange <= pos ? 40 : 0); + + if (targetDistance <= distance) { + distance = targetDistance; + lastChange = 0xFF; + } + } + + while (distance--) { + if (pos >= 40) + pos = 0; + + szbuf[pos] = largeSpritesSrc; + buf[pos * 2] = oamram[pos * 4]; + buf[pos * 2 + 1] = oamram[pos * 4 + 1]; + + ++pos; + } + } + + lu = cc; + } +} + +void SpriteMapper::OamReader::change(const unsigned long cc) { + update(cc); + lastChange = std::min(toPosCycles(lu, lyCounter), 40u); +} + +void SpriteMapper::OamReader::setStatePtrs(SaveState &state) { + state.ppu.oamReaderBuf.set(buf, sizeof buf); + state.ppu.oamReaderSzbuf.set(szbuf, sizeof(szbuf) / sizeof(bool)); +} + +void SpriteMapper::OamReader::enableDisplay(const unsigned long cc) { + std::memset(buf, 0x00, sizeof(buf)); + std::fill(szbuf, szbuf + 40, false); + lu = cc + 160; + lastChange = 40; +} + +bool SpriteMapper::OamReader::oamAccessible(const unsigned long cycleCounter, const M3ExtraCycles &m3ExtraCycles) const { + unsigned ly = lyCounter.ly(); + unsigned lc = lyCounter.lineCycles(cycleCounter) + 4 - lyCounter.isDoubleSpeed() * 3u; + + if (lc >= 456) { + lc -= 456; + ++ly; + } + + return cycleCounter < lu || ly >= 144 || lc >= 80 + 173 + m3ExtraCycles(ly); +} + +SpriteMapper::SpriteMapper(M3ExtraCycles &m3ExtraCycles, + const LyCounter &lyCounter, + const unsigned char *const oamram) : + VideoEvent(2), + m3ExtraCycles(m3ExtraCycles), + oamReader(lyCounter, oamram), + cgb(false) +{ + clearMap(); +} + +void SpriteMapper::reset(const unsigned char *const oamram, const bool cgb_in) { + oamReader.reset(oamram); + cgb = cgb_in; + clearMap(); +} + +void SpriteMapper::clearMap() { + std::memset(num, cgb ? 0 : NEED_SORTING_MASK, sizeof(num)); +} + +void SpriteMapper::mapSprites() { + clearMap(); + + for (unsigned i = 0x00; i < 0x50; i += 2) { + const unsigned spriteHeight = 8u << largeSprites(i >> 1); + const unsigned bottom_pos = posbuf()[i] - (17u - spriteHeight); + + if (bottom_pos >= 143 + spriteHeight) + continue; + + unsigned char *map = spritemap; + unsigned char *n = num; + + if (bottom_pos >= spriteHeight) { + const unsigned startly = bottom_pos + 1 - spriteHeight; + n += startly; + map += startly * 10; + } + + unsigned char *const end = num + (bottom_pos >= 143 ? 143 : bottom_pos); + + do { + if ((*n & ~NEED_SORTING_MASK) < 10) + map[(*n)++ & ~NEED_SORTING_MASK] = i; + + map += 10; + ++n; + } while (n <= end); + } + + m3ExtraCycles.invalidateCache(); +} + +void SpriteMapper::sortLine(const unsigned ly) const { + num[ly] &= ~NEED_SORTING_MASK; + insertionSort(spritemap + ly * 10, spritemap + ly * 10 + num[ly], SpxLess(posbuf())); +} + +void SpriteMapper::doEvent() { + oamReader.update(time()); + mapSprites(); + setTime(oamReader.changed() ? time() + oamReader.lyCounter.lineTime() : static_cast(DISABLED_TIME)); +} diff --git a/supergameboy/libgambatte/src/video/sprite_mapper.h b/supergameboy/libgambatte/src/video/sprite_mapper.h new file mode 100644 index 00000000..25b8090b --- /dev/null +++ b/supergameboy/libgambatte/src/video/sprite_mapper.h @@ -0,0 +1,148 @@ +/*************************************************************************** + * Copyright (C) 2007 by Sindre Aam�s * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#ifndef SPRITE_MAPPER_H +#define SPRITE_MAPPER_H + +#include "video_event.h" +//#include "video_event_comparer.h" +#include "ly_counter.h" +#include "basic_add_event.h" +#include "../savestate.h" + +class M3ExtraCycles; +class SaveState; + +class SpriteMapper : public VideoEvent { + class OamReader { + unsigned char buf[80]; + bool szbuf[40]; + + public: + const LyCounter &lyCounter; + + private: + const unsigned char *oamram; + unsigned long lu; + unsigned char lastChange; + bool largeSpritesSrc; + + public: + OamReader(const LyCounter &lyCounter, const unsigned char *oamram); + void reset(const unsigned char *oamram); + void change(unsigned long cc); + void change(const unsigned char *oamram, unsigned long cc) { change(cc); this->oamram = oamram; } + bool changed() const { return lastChange != 0xFF; } + bool largeSprites(unsigned spNr) const { return szbuf[spNr]; } + const unsigned char *oam() const { return oamram; } + void resetCycleCounter(const unsigned long oldCc, const unsigned long newCc) { lu = lu + newCc - oldCc; } + void setLargeSpritesSrc(const bool src) { largeSpritesSrc = src; } + void update(unsigned long cc); + const unsigned char *spritePosBuf() const { return buf; } + void setStatePtrs(SaveState &state); + void enableDisplay(unsigned long cc); + void saveState(SaveState &state) const { state.ppu.enableDisplayM0Time = lu; } + void loadState(const SaveState &state) { lu = state.ppu.enableDisplayM0Time; } + void resetVideoState() { change(lu); } + bool oamAccessible(unsigned long cycleCounter, const M3ExtraCycles &m3ExtraCycles) const; + bool inactivePeriodAfterDisplayEnable(const unsigned long cc) const { return cc < lu; } + }; + + enum { NEED_SORTING_MASK = 0x80 }; + +public: + class SpxLess { + const unsigned char *const posbuf_plus1; + + public: + SpxLess(const unsigned char *const posbuf) : posbuf_plus1(posbuf + 1) {} + + bool operator()(const unsigned char l, const unsigned char r) const { + return posbuf_plus1[l] < posbuf_plus1[r]; + } + }; + +private: + mutable unsigned char spritemap[144*10]; + mutable unsigned char num[144]; + + M3ExtraCycles &m3ExtraCycles; + OamReader oamReader; + + bool cgb; + + void clearMap(); + void mapSprites(); + void sortLine(unsigned ly) const; + +public: + SpriteMapper(M3ExtraCycles &m3ExtraCycles, + const LyCounter &lyCounter, + const unsigned char *oamram_in); + void reset(const unsigned char *oamram, bool cgb_in); + void doEvent(); + bool isCgb() const { return cgb; } + bool largeSprites(unsigned spNr) const { return oamReader.largeSprites(spNr); } + unsigned numSprites(const unsigned ly) const { return num[ly] & ~NEED_SORTING_MASK; } + void oamChange(const unsigned long cc) { oamReader.change(cc); } + void oamChange(const unsigned char *oamram, const unsigned long cc) { oamReader.change(oamram, cc); } + const unsigned char *oamram() const { return oamReader.oam(); } + const unsigned char *posbuf() const { return oamReader.spritePosBuf(); } + void preCounterChange(const unsigned long cc) { oamReader.update(cc); } + + void resetCycleCounter(const unsigned long oldCc, const unsigned long newCc) { + oamReader.resetCycleCounter(oldCc, newCc); + } + + static unsigned long schedule(const LyCounter &lyCounter, const unsigned long cycleCounter) { + return lyCounter.nextLineCycle(80, cycleCounter); + } + + void setLargeSpritesSource(const bool src) { oamReader.setLargeSpritesSrc(src); } + + const unsigned char* sprites(const unsigned ly) const { + if (num[ly] & NEED_SORTING_MASK) + sortLine(ly); + + return spritemap + ly * 10; + } + + void setStatePtrs(SaveState &state) { oamReader.setStatePtrs(state); } + void enableDisplay(unsigned long cc) { oamReader.enableDisplay(cc); } + void saveState(SaveState &state) const { oamReader.saveState(state); } + void loadState(const SaveState &state) { oamReader.loadState(state); } + void resetVideoState() { oamReader.resetVideoState(); } + + bool oamAccessible(unsigned long cycleCounter) const { + return oamReader.oamAccessible(cycleCounter, m3ExtraCycles); + } + + bool inactivePeriodAfterDisplayEnable(const unsigned long cc) const { + return oamReader.inactivePeriodAfterDisplayEnable(cc); + } +}; + +static inline void addEvent(event_queue &q, SpriteMapper *const e, const unsigned long newTime) { + addUnconditionalEvent(q, e, newTime); +} + +static inline void addFixedtimeEvent(event_queue &q, SpriteMapper *const e, const unsigned long newTime) { + addUnconditionalFixedtimeEvent(q, e, newTime); +} + +#endif diff --git a/supergameboy/libgambatte/src/video/video_event.h b/supergameboy/libgambatte/src/video/video_event.h new file mode 100644 index 00000000..fb64d5b2 --- /dev/null +++ b/supergameboy/libgambatte/src/video/video_event.h @@ -0,0 +1,50 @@ +/*************************************************************************** + * Copyright (C) 2007 by Sindre Aamås * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#ifndef VIDEO_EVENT_H +#define VIDEO_EVENT_H + +class VideoEvent { + unsigned long time_; + const unsigned char priority_; + +public: + enum { DISABLED_TIME = 0xFFFFFFFFu }; + + VideoEvent(const unsigned priority_in) : + time_(DISABLED_TIME), + priority_(priority_in) + {} + + virtual ~VideoEvent() {} + virtual void doEvent() = 0; + + unsigned priority() const { + return priority_; + } + + unsigned long time() const { + return time_; + } + + void setTime(const unsigned long time_in) { + time_ = time_in; + } +}; + +#endif diff --git a/supergameboy/libgambatte/src/video/video_event_comparer.h b/supergameboy/libgambatte/src/video/video_event_comparer.h new file mode 100644 index 00000000..4eb25969 --- /dev/null +++ b/supergameboy/libgambatte/src/video/video_event_comparer.h @@ -0,0 +1,31 @@ +/*************************************************************************** + * Copyright (C) 2007 by Sindre Aamås * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#ifndef VIDEO_EVENT_COMPARER_H +#define VIDEO_EVENT_COMPARER_H + +#include "video_event.h" + +class VideoEventComparer { +public: + bool less(const VideoEvent *const a, const VideoEvent *const b) const { + return a->time() < b->time() || (a->time() == b->time() && a->priority() < b->priority()); + } +}; + +#endif diff --git a/supergameboy/libgambatte/src/video/we.cpp b/supergameboy/libgambatte/src/video/we.cpp new file mode 100644 index 00000000..d5e66c47 --- /dev/null +++ b/supergameboy/libgambatte/src/video/we.cpp @@ -0,0 +1,59 @@ +/*************************************************************************** + * Copyright (C) 2007 by Sindre Aamås * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#include "we.h" +#include "../savestate.h" + +We::WeEnableChecker::WeEnableChecker(We &we) : + VideoEvent(8), + we(we) +{} + +void We::WeEnableChecker::doEvent() { + we.set(we.src_); + + setTime(DISABLED_TIME); +} + +We::WeDisableChecker::WeDisableChecker(We &we) : + VideoEvent(9), + we(we) +{} + +void We::WeDisableChecker::doEvent() { + we.set(we.we_ & we.src_); + + setTime(DISABLED_TIME); +} + +We::We(M3ExtraCycles &m3ExtraCycles) : + m3ExtraCycles_(m3ExtraCycles), + enableChecker_(*this), + disableChecker_(*this) +{ + setSource(false); + we_ = src_; +} + +void We::saveState(SaveState &state) const { + state.ppu.lcdc = (state.ppu.lcdc & ~0x20) | we_ << 5; +} + +void We::loadState(const SaveState &state) { + we_ = state.ppu.lcdc >> 5 & 1; +} diff --git a/supergameboy/libgambatte/src/video/we.h b/supergameboy/libgambatte/src/video/we.h new file mode 100644 index 00000000..d800ca11 --- /dev/null +++ b/supergameboy/libgambatte/src/video/we.h @@ -0,0 +1,118 @@ +/*************************************************************************** + * Copyright (C) 2007 by Sindre Aamås * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#ifndef WE_H +#define WE_H + +class SaveState; + +#include "video_event.h" +#include "ly_counter.h" +#include "m3_extra_cycles.h" +#include "basic_add_event.h" + +class We { +public: + class WeEnableChecker : public VideoEvent { + We &we; + + public: + WeEnableChecker(We &we); + + void doEvent(); + + static unsigned long schedule(const unsigned scxAnd7, const unsigned wx, const LyCounter &lyCounter, const unsigned long cycleCounter) { + return lyCounter.nextLineCycle(scxAnd7 + 82 + wx + lyCounter.isDoubleSpeed() * 3, cycleCounter); + } + }; + + class WeDisableChecker : public VideoEvent { + We &we; + + public: + WeDisableChecker(We &we); + + void doEvent(); + + static unsigned long schedule(const unsigned scxAnd7, const unsigned wx, const LyCounter &lyCounter, const unsigned long cycleCounter) { + return lyCounter.nextLineCycle(scxAnd7 + 88 + wx + lyCounter.isDoubleSpeed() * 3, cycleCounter); + } + }; + + friend class WeEnableChecker; + friend class WeDisableChecker; + +private: + M3ExtraCycles &m3ExtraCycles_; + WeEnableChecker enableChecker_; + WeDisableChecker disableChecker_; + + bool we_; + bool src_; + + void set(const bool value) { + if (we_ != value) + m3ExtraCycles_.invalidateCache(); + + we_ = value; + } + +public: + We(M3ExtraCycles &m3ExtraCycles); + + WeDisableChecker& disableChecker() { + return disableChecker_; + } + + WeEnableChecker& enableChecker() { + return enableChecker_; + } + + bool getSource() const { + return src_; + } + + void setSource(const bool src) { + src_ = src; + } + + bool value() const { + return we_; + } + + void saveState(SaveState &state) const; + void loadState(const SaveState &state); +}; + +static inline void addEvent(event_queue &q, We::WeEnableChecker *const e, const unsigned long newTime) { + addUnconditionalEvent(q, e, newTime); +} + +static inline void addFixedtimeEvent(event_queue &q, We::WeEnableChecker *const e, const unsigned long newTime) { + addUnconditionalFixedtimeEvent(q, e, newTime); +} + +static inline void addEvent(event_queue &q, We::WeDisableChecker *const e, const unsigned long newTime) { + addUnconditionalEvent(q, e, newTime); +} + +static inline void addFixedtimeEvent(event_queue &q, We::WeDisableChecker *const e, const unsigned long newTime) { + addUnconditionalFixedtimeEvent(q, e, newTime); +} + +#endif diff --git a/supergameboy/libgambatte/src/video/we_master_checker.cpp b/supergameboy/libgambatte/src/video/we_master_checker.cpp new file mode 100644 index 00000000..bff81585 --- /dev/null +++ b/supergameboy/libgambatte/src/video/we_master_checker.cpp @@ -0,0 +1,58 @@ +/*************************************************************************** + * Copyright (C) 2007 by Sindre Aamås * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#include "we_master_checker.h" + +#include "event_queue.h" +#include "wy.h" +#include "basic_add_event.h" +#include "../savestate.h" + +WeMasterChecker::WeMasterChecker(event_queue &m3EventQueue_in, + Wy &wy_in, + const LyCounter &lyCounter_in, + M3ExtraCycles &m3ExtraCycles) : + VideoEvent(10), + m3EventQueue(m3EventQueue_in), + wy(wy_in), + lyCounter(lyCounter_in), + m3ExtraCycles(m3ExtraCycles) +{ + weMaster_ = false; +} + +void WeMasterChecker::doEvent() { +// if (wy.value() >= lyCounter.ly()) { + if (!weMaster_ /*&& src */&& wy.value() == lyCounter.ly()) { + wy.weirdAssWeMasterEnableOnWyLineCase(); + addEvent(m3EventQueue, &wy.reader4(), Wy::WyReader4::schedule(lyCounter, time())); + } + + set(true); +// } + + setTime(time() + (70224U << lyCounter.isDoubleSpeed())); +} + +void WeMasterChecker::saveState(SaveState &state) const { + state.ppu.weMaster = weMaster_; +} + +void WeMasterChecker::loadState(const SaveState &state) { + weMaster_ = state.ppu.weMaster; +} diff --git a/supergameboy/libgambatte/src/video/we_master_checker.h b/supergameboy/libgambatte/src/video/we_master_checker.h new file mode 100644 index 00000000..cf1f1209 --- /dev/null +++ b/supergameboy/libgambatte/src/video/we_master_checker.h @@ -0,0 +1,73 @@ +/*************************************************************************** + * Copyright (C) 2007 by Sindre Aamås * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#ifndef WE_MASTER_CHECKER_H +#define WE_MASTER_CHECKER_H + +template class event_queue; +class Wy; +class SaveState; + +#include "video_event.h" +#include "video_event_comparer.h" +#include "ly_counter.h" +#include "m3_extra_cycles.h" + +class WeMasterChecker : public VideoEvent { + event_queue &m3EventQueue; + Wy &wy; + const LyCounter &lyCounter; + M3ExtraCycles &m3ExtraCycles; + + bool weMaster_; + + void set(const bool value) { + if (weMaster_ != value) + m3ExtraCycles.invalidateCache(); + + weMaster_ = value; + } + +public: + WeMasterChecker(event_queue &m3EventQueue_in, + Wy &wy_in, + const LyCounter &lyCounter_in, + M3ExtraCycles &m3ExtraCycles); + + void doEvent(); + + static unsigned long schedule(const unsigned wySrc, const bool weSrc, const LyCounter &lyCounter, const unsigned long cycleCounter) { + if (weSrc && wySrc < 143) + return lyCounter.nextFrameCycle(wySrc * 456ul + 448 + lyCounter.isDoubleSpeed() * 4, cycleCounter); + else + return DISABLED_TIME; + } + + void unset() { + set(false); + } + + bool weMaster() const { + return weMaster_; + } + + void saveState(SaveState &state) const; + void loadState(const SaveState &state); +}; + +#endif diff --git a/supergameboy/libgambatte/src/video/window.h b/supergameboy/libgambatte/src/video/window.h new file mode 100644 index 00000000..790d612c --- /dev/null +++ b/supergameboy/libgambatte/src/video/window.h @@ -0,0 +1,47 @@ +/*************************************************************************** + * Copyright (C) 2007 by Sindre Aamås * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#ifndef WINDOW_H +#define WINDOW_H + +#include "we.h" +#include "we_master_checker.h" +#include "wy.h" +#include "wx_reader.h" + +struct Window { + We we; + WeMasterChecker weMasterChecker; + Wy wyReg; + WxReader wxReader; + + Window(event_queue &m3EventQueue, + const LyCounter &lyCounter, + M3ExtraCycles &m3ExtraCycles) : + we(m3ExtraCycles), + weMasterChecker(m3EventQueue, wyReg, lyCounter, m3ExtraCycles), + wyReg(lyCounter, weMasterChecker, m3ExtraCycles), + wxReader(m3EventQueue, we.enableChecker(), we.disableChecker(), m3ExtraCycles) + {} + + bool enabled(const unsigned ly) const { + return we.value() && wxReader.wx() < 0xA7 && ly >= wyReg.value() && (weMasterChecker.weMaster() || ly == wyReg.value()); + } +}; + +#endif /*WINDOW_H*/ diff --git a/supergameboy/libgambatte/src/video/wx_reader.cpp b/supergameboy/libgambatte/src/video/wx_reader.cpp new file mode 100644 index 00000000..80a6b640 --- /dev/null +++ b/supergameboy/libgambatte/src/video/wx_reader.cpp @@ -0,0 +1,65 @@ +/*************************************************************************** + * Copyright (C) 2007 by Sindre Aam�s * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#include "wx_reader.h" + +#include "../event_queue.h" +#include "m3_extra_cycles.h" +#include "../savestate.h" + +WxReader::WxReader(event_queue &m3EventQueue, + VideoEvent &weEnableChecker, + VideoEvent &weDisableChecker, + M3ExtraCycles &m3ExtraCycles) : +VideoEvent(7), +m3EventQueue(m3EventQueue), +weEnableChecker(weEnableChecker), +weDisableChecker(weDisableChecker), +m3ExtraCycles(m3ExtraCycles) +{ + setDoubleSpeed(false); + setSource(0); + wx_ = src_; +} + +static void rescheduleEvent(event_queue &m3EventQueue, VideoEvent& event, const unsigned long diff) { + if (event.time() != VideoEvent::DISABLED_TIME) { + event.setTime(event.time() + diff); + (diff & 0x200) ? m3EventQueue.dec(&event, &event) : m3EventQueue.inc(&event, &event); + } +} + +void WxReader::doEvent() { + const unsigned long diff = (static_cast(src_) - static_cast(wx_)) << dS; + wx_ = src_; + + rescheduleEvent(m3EventQueue, weEnableChecker, diff); + rescheduleEvent(m3EventQueue, weDisableChecker, diff); + + m3ExtraCycles.invalidateCache(); + + setTime(DISABLED_TIME); +} + +void WxReader::saveState(SaveState &state) const { + state.ppu.wx = wx_; +} + +void WxReader::loadState(const SaveState &state) { + wx_ = state.ppu.wx; +} diff --git a/supergameboy/libgambatte/src/video/wx_reader.h b/supergameboy/libgambatte/src/video/wx_reader.h new file mode 100644 index 00000000..1681f8a4 --- /dev/null +++ b/supergameboy/libgambatte/src/video/wx_reader.h @@ -0,0 +1,83 @@ +/*************************************************************************** + * Copyright (C) 2007 by Sindre Aamås * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#ifndef WX_READER_H +#define WX_READER_H + +template class event_queue; +class M3ExtraCycles; +class SaveState; + +#include "video_event.h" +#include "video_event_comparer.h" +#include "ly_counter.h" +#include "basic_add_event.h" +#include + +class WxReader : public VideoEvent { + event_queue &m3EventQueue; + VideoEvent &weEnableChecker; + VideoEvent &weDisableChecker; + M3ExtraCycles &m3ExtraCycles; + + unsigned char wx_; + unsigned char src_; + bool dS; + +public: + WxReader(event_queue &m3EventQueue_in, + VideoEvent &weEnableChecker_in, + VideoEvent &weDisableChecker_in, + M3ExtraCycles &m3ExtraCycles); + + void doEvent(); + + unsigned getSource() const { + return src_; + } + + unsigned wx() const { + return wx_; + } + + void setDoubleSpeed(const bool dS_in) { + dS = dS_in; + } + + void setSource(const unsigned src) { + src_ = src; + } + + static unsigned long schedule(const unsigned scxAnd7, const LyCounter &lyCounter, const WxReader &wxReader, const unsigned long cycleCounter) { + return lyCounter.nextLineCycle(scxAnd7 + 82 + lyCounter.isDoubleSpeed() * 3 + std::min(wxReader.getSource(), wxReader.wx()), cycleCounter); + //setTime(lyCounter.nextLineCycle(scxAnd7 + 89 + lyCounter.isDoubleSpeed() * 3, cycleCounter)); + } + + void saveState(SaveState &state) const; + void loadState(const SaveState &state); +}; + +static inline void addEvent(event_queue &q, WxReader *const e, const unsigned long newTime) { + addUnconditionalEvent(q, e, newTime); +} + +static inline void addFixedtimeEvent(event_queue &q, WxReader *const e, const unsigned long newTime) { + addUnconditionalFixedtimeEvent(q, e, newTime); +} + +#endif diff --git a/supergameboy/libgambatte/src/video/wy.cpp b/supergameboy/libgambatte/src/video/wy.cpp new file mode 100644 index 00000000..64a5f725 --- /dev/null +++ b/supergameboy/libgambatte/src/video/wy.cpp @@ -0,0 +1,105 @@ +/*************************************************************************** + * Copyright (C) 2007 by Sindre Aamås * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#include "wy.h" + +#include "we_master_checker.h" +#include "scx_reader.h" +#include "../event_queue.h" +#include "../savestate.h" + +Wy::WyReader1::WyReader1(Wy &wy, const WeMasterChecker &weMasterChecker) : + VideoEvent(3), + wy(wy), + weMasterChecker(weMasterChecker) +{} + +void Wy::WyReader1::doEvent() { + if (wy.src_ >= wy.lyCounter.ly() && /*wy >= lyCounter.ly()*/ !weMasterChecker.weMaster()) + wy.set(wy.src_); + + setTime(DISABLED_TIME); +} + +Wy::WyReader2::WyReader2(Wy &wy) : + VideoEvent(4), + wy(wy) +{} + +void Wy::WyReader2::doEvent() { + if (wy.wy_ == wy.lyCounter.ly() + 1 - wy.lyCounter.isDoubleSpeed() && wy.src_ > wy.wy_) + wy.set(wy.src_); + + setTime(DISABLED_TIME); +} + +Wy::WyReader3::WyReader3(Wy &wy) : + VideoEvent(5), + wy(wy) +{} + +void Wy::WyReader3::doEvent() { + if (wy.src_ == wy.lyCounter.ly() && wy.wy_ > wy.lyCounter.ly()) + wy.set(wy.src_); + + setTime(DISABLED_TIME); +} + +unsigned long Wy::WyReader3::schedule(const unsigned wxSrc, const ScxReader &scxReader, const LyCounter &lyCounter, const unsigned long cycleCounter) { + const unsigned curLineCycle = 456 - ((lyCounter.time() - cycleCounter) >> lyCounter.isDoubleSpeed()); + const unsigned baseTime = 78 + lyCounter.isDoubleSpeed() * 6 + wxSrc; + + if (curLineCycle >= 82U + lyCounter.isDoubleSpeed() * 3) { + if (baseTime + scxReader.scxAnd7() > curLineCycle) + return lyCounter.time() + ((baseTime + scxReader.scxAnd7()) << lyCounter.isDoubleSpeed()) - lyCounter.lineTime(); + else + return lyCounter.time() + ((baseTime + scxReader.getSource()) << lyCounter.isDoubleSpeed()); + } else + return lyCounter.nextLineCycle(baseTime + scxReader.getSource(), cycleCounter); +} + +Wy::WyReader4::WyReader4(Wy &wy) : + VideoEvent(6), + wy(wy) +{} + +void Wy::WyReader4::doEvent() { + wy.set(wy.src_); + + setTime(DISABLED_TIME); +} + +Wy::Wy(const LyCounter &lyCounter, const WeMasterChecker &weMasterChecker, M3ExtraCycles &m3ExtraCycles) : + lyCounter(lyCounter), + m3ExtraCycles(m3ExtraCycles), + reader1_(*this, weMasterChecker), + reader2_(*this), + reader3_(*this), + reader4_(*this) +{ + setSource(0); + wy_ = src_; +} + +void Wy::saveState(SaveState &state) const { + state.ppu.wy = wy_; +} + +void Wy::loadState(const SaveState &state) { + wy_ = state.ppu.wy; +} diff --git a/supergameboy/libgambatte/src/video/wy.h b/supergameboy/libgambatte/src/video/wy.h new file mode 100644 index 00000000..2a1033f9 --- /dev/null +++ b/supergameboy/libgambatte/src/video/wy.h @@ -0,0 +1,187 @@ +/*************************************************************************** + * Copyright (C) 2007 by Sindre Aamås * + * aamas@stud.ntnu.no * + * * + * 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. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#ifndef WY_H +#define WY_H + +class WeMasterChecker; +class ScxReader; +template class event_queue; +class SaveState; + +#include "video_event.h" +#include "video_event_comparer.h" +#include "ly_counter.h" +#include "m3_extra_cycles.h" +#include "basic_add_event.h" + +class Wy { +public: + class WyReader1 : public VideoEvent { + Wy &wy; + const WeMasterChecker &weMasterChecker; + + public: + WyReader1(Wy &wy, const WeMasterChecker &weMasterChecker); + + void doEvent(); + + static unsigned long schedule(const LyCounter &lyCounter, const unsigned long cycleCounter) { + return lyCounter.nextLineCycle(448 + lyCounter.isDoubleSpeed() * 4, cycleCounter); + } + }; + + class WyReader2 : public VideoEvent { + Wy &wy; + + public: + WyReader2(Wy &wy); + + void doEvent(); + + static unsigned long schedule(const LyCounter &lyCounter, const unsigned long cycleCounter) { + return lyCounter.isDoubleSpeed() ? lyCounter.time() : lyCounter.nextLineCycle(452, cycleCounter); + } + }; + + class WyReader3 : public VideoEvent { + Wy &wy; + + public: + WyReader3(Wy &wy); + + void doEvent(); + static unsigned long schedule(unsigned wxSrc, const ScxReader &scxReader, const LyCounter &lyCounter, unsigned long cycleCounter); + + //void schedule(const unsigned scxAnd7, const LyCounter &lyCounter, const unsigned cycleCounter) { + // setTime(lyCounter.nextLineCycle(scxAnd7 + 85 + lyCounter.isDoubleSpeed() * 6, cycleCounter)); + //} + }; + + class WyReader4 : public VideoEvent { + Wy &wy; + + public: + WyReader4(Wy &wy); + + void doEvent(); + + static unsigned long schedule(const LyCounter &lyCounter, const unsigned long cycleCounter) { + return lyCounter.nextFrameCycle(lyCounter.isDoubleSpeed() * 4, cycleCounter); + } + }; + + friend class WyReader1; + friend class WyReader2; + friend class WyReader3; + friend class WyReader4; + +private: + const LyCounter &lyCounter; + M3ExtraCycles &m3ExtraCycles; + WyReader1 reader1_; + WyReader2 reader2_; + WyReader3 reader3_; + WyReader4 reader4_; + + unsigned char wy_; + unsigned char src_; + + void set(const unsigned char value) { + if (wy_ != value) + m3ExtraCycles.invalidateCache(); + + wy_ = value; + } + +public: + Wy(const LyCounter &lyCounter, const WeMasterChecker &weMasterChecker, M3ExtraCycles &m3ExtraCycles); + + WyReader1& reader1() { + return reader1_; + } + + WyReader2& reader2() { + return reader2_; + } + + WyReader3& reader3() { + return reader3_; + } + + WyReader4& reader4() { + return reader4_; + } + + unsigned getSource() const { + return src_; + } + + void setSource(const unsigned src) { + src_ = src; + } + + //void setValue(const unsigned val) { + // wy_ = val; + //} + + unsigned value() const { + return wy_; + } + + void weirdAssWeMasterEnableOnWyLineCase() { + set(wy_ + 1); + } + + void saveState(SaveState &state) const; + void loadState(const SaveState &state); +}; + +static inline void addEvent(event_queue &q, Wy::WyReader1 *const e, const unsigned long newTime) { + addUnconditionalEvent(q, e, newTime); +} + +static inline void addFixedtimeEvent(event_queue &q, Wy::WyReader1 *const e, const unsigned long newTime) { + addUnconditionalFixedtimeEvent(q, e, newTime); +} + +static inline void addEvent(event_queue &q, Wy::WyReader2 *const e, const unsigned long newTime) { + addUnconditionalEvent(q, e, newTime); +} + +static inline void addFixedtimeEvent(event_queue &q, Wy::WyReader2 *const e, const unsigned long newTime) { + addUnconditionalFixedtimeEvent(q, e, newTime); +} + +static inline void addEvent(event_queue &q, Wy::WyReader3 *const e, const unsigned long newTime) { + addUnconditionalEvent(q, e, newTime); +} + +static inline void addFixedtimeEvent(event_queue &q, Wy::WyReader3 *const e, const unsigned long newTime) { + addUnconditionalFixedtimeEvent(q, e, newTime); +} + +static inline void addEvent(event_queue &q, Wy::WyReader4 *const e, const unsigned long newTime) { + addUnconditionalEvent(q, e, newTime); +} + +static inline void addFixedtimeEvent(event_queue &q, Wy::WyReader4 *const e, const unsigned long newTime) { + addUnconditionalFixedtimeEvent(q, e, newTime); +} + +#endif diff --git a/supergameboy/nall/Makefile b/supergameboy/nall/Makefile new file mode 100644 index 00000000..8149bf15 --- /dev/null +++ b/supergameboy/nall/Makefile @@ -0,0 +1,107 @@ +# Makefile +# author: byuu +# license: public domain + +[A-Z] = A B C D E F G H I J K L M N O P Q R S T U V W X Y Z +[a-z] = a b c d e f g h i j k l m n o p q r s t u v w x y z +[0-9] = 0 1 2 3 4 5 6 7 8 9 +[markup] = ` ~ ! @ \# $$ % ^ & * ( ) - _ = + [ { ] } \ | ; : ' " , < . > / ? +[all] = $([A-Z]) $([a-z]) $([0-9]) $([markup]) +[space] := +[space] += + +##### +# platform detection +##### + +ifeq ($(platform),) + uname := $(shell uname -a) + ifeq ($(uname),) + platform := win + delete = del $(subst /,\,$1) + else ifneq ($(findstring Darwin,$(uname)),) + platform := osx + delete = rm -f $1 + else + platform := x + delete = rm -f $1 + endif +endif + +ifeq ($(compiler),) + ifeq ($(platform),osx) + compiler := gcc-4.2 + else + compiler := gcc + endif +endif + +ifeq ($(prefix),) + prefix := /usr/local +endif + +##### +# function rwildcard(directory, pattern) +##### +rwildcard = \ + $(strip \ + $(filter $(if $2,$2,%), \ + $(foreach f, \ + $(wildcard $1*), \ + $(eval t = $(call rwildcard,$f/)) \ + $(if $t,$t,$f) \ + ) \ + ) \ + ) + +##### +# function strtr(source, from, to) +##### +strtr = \ + $(eval __temp := $1) \ + $(strip \ + $(foreach c, \ + $(join $(addsuffix :,$2),$3), \ + $(eval __temp := \ + $(subst $(word 1,$(subst :, ,$c)),$(word 2,$(subst :, ,$c)),$(__temp)) \ + ) \ + ) \ + $(__temp) \ + ) + +##### +# function strupper(source) +##### +strupper = $(call strtr,$1,$([a-z]),$([A-Z])) + +##### +# function strlower(source) +##### +strlower = $(call strtr,$1,$([A-Z]),$([a-z])) + +##### +# function strlen(source) +##### +strlen = \ + $(eval __temp := $(subst $([space]),_,$1)) \ + $(words \ + $(strip \ + $(foreach c, \ + $([all]), \ + $(eval __temp := \ + $(subst $c,$c ,$(__temp)) \ + ) \ + ) \ + $(__temp) \ + ) \ + ) + +##### +# function streq(source) +##### +streq = $(if $(filter-out xx,x$(subst $1,,$2)$(subst $2,,$1)x),,1) + +##### +# function strne(source) +##### +strne = $(if $(filter-out xx,x$(subst $1,,$2)$(subst $2,,$1)x),1,) diff --git a/supergameboy/nall/algorithm.hpp b/supergameboy/nall/algorithm.hpp new file mode 100644 index 00000000..cdc48dcf --- /dev/null +++ b/supergameboy/nall/algorithm.hpp @@ -0,0 +1,23 @@ +#ifndef NALL_ALGORITHM_HPP +#define NALL_ALGORITHM_HPP + +#undef min +#undef max + +namespace nall { + template T min(const T &t, const U &u) { + return t < u ? t : u; + } + + template T max(const T &t, const U &u) { + return t > u ? t : u; + } + + //pseudo-random number generator + inline unsigned prng() { + static unsigned n = 0; + return n = (n >> 1) ^ (((n & 1) - 1) & 0xedb88320); + } +} + +#endif diff --git a/supergameboy/nall/any.hpp b/supergameboy/nall/any.hpp new file mode 100644 index 00000000..b31cff3c --- /dev/null +++ b/supergameboy/nall/any.hpp @@ -0,0 +1,74 @@ +#ifndef NALL_ANY_HPP +#define NALL_ANY_HPP + +#include +#include +#include + +namespace nall { + class any { + public: + bool empty() const { return container; } + const std::type_info& type() const { return container ? container->type() : typeid(void); } + + template any& operator=(const T& value_) { + typedef typename static_if< + std::is_array::value, + typename std::remove_extent::type>::type*, + T + >::type auto_t; + + if(type() == typeid(auto_t)) { + static_cast*>(container)->value = (auto_t)value_; + } else { + if(container) delete container; + container = new holder((auto_t)value_); + } + + return *this; + } + + any() : container(0) {} + template any(const T& value_) : container(0) { operator=(value_); } + + private: + struct placeholder { + virtual const std::type_info& type() const = 0; + } *container; + + template struct holder : placeholder { + T value; + const std::type_info& type() const { return typeid(T); } + holder(const T& value_) : value(value_) {} + }; + + template friend T any_cast(any&); + template friend T any_cast(const any&); + template friend T* any_cast(any*); + template friend const T* any_cast(const any*); + }; + + template T any_cast(any &value) { + typedef typename std::remove_reference::type nonref; + if(value.type() != typeid(nonref)) throw; + return static_cast*>(value.container)->value; + } + + template T any_cast(const any &value) { + typedef const typename std::remove_reference::type nonref; + if(value.type() != typeid(nonref)) throw; + return static_cast*>(value.container)->value; + } + + template T* any_cast(any *value) { + if(!value || value->type() != typeid(T)) return 0; + return &static_cast*>(value->container)->value; + } + + template const T* any_cast(const any *value) { + if(!value || value->type() != typeid(T)) return 0; + return &static_cast*>(value->container)->value; + } +} + +#endif diff --git a/supergameboy/nall/array.hpp b/supergameboy/nall/array.hpp new file mode 100644 index 00000000..c1d33fd1 --- /dev/null +++ b/supergameboy/nall/array.hpp @@ -0,0 +1,120 @@ +#ifndef NALL_ARRAY_HPP +#define NALL_ARRAY_HPP + +#include +#include +#include +#include +#include +#include +#include + +namespace nall { + //dynamic vector array + //neither constructor nor destructor is ever invoked; + //thus, this should only be used for POD objects. + template class array { + protected: + T *pool; + unsigned poolsize, buffersize; + + public: + unsigned size() const { return buffersize; } + unsigned capacity() const { return poolsize; } + + void reset() { + if(pool) free(pool); + pool = 0; + poolsize = 0; + buffersize = 0; + } + + void reserve(unsigned newsize) { + if(newsize == poolsize) return; + + pool = (T*)realloc(pool, newsize * sizeof(T)); + poolsize = newsize; + buffersize = min(buffersize, newsize); + } + + void resize(unsigned newsize) { + if(newsize > poolsize) reserve(bit::round(newsize)); //round reserve size up to power of 2 + buffersize = newsize; + } + + T* get(unsigned minsize = 0) { + if(minsize > buffersize) resize(minsize); + if(minsize > buffersize) throw "array[] out of bounds"; + return pool; + } + + void add(const T data) { + operator[](buffersize) = data; + } + + signed find(const T data) { + for(unsigned i = 0; i < size(); i++) if(pool[i] == data) return i; + return -1; //not found + } + + void clear() { + memset(pool, 0, buffersize * sizeof(T)); + } + + array() : pool(0), poolsize(0), buffersize(0) { + } + + array(std::initializer_list list) : pool(0), poolsize(0), buffersize(0) { + for(const T *p = list.begin(); p != list.end(); ++p) add(*p); + } + + ~array() { + reset(); + } + + //copy + array& operator=(const array &source) { + if(pool) free(pool); + buffersize = source.buffersize; + poolsize = source.poolsize; + pool = (T*)malloc(sizeof(T) * poolsize); //allocate entire pool size, + memcpy(pool, source.pool, sizeof(T) * buffersize); //... but only copy used pool objects + return *this; + } + + array(const array &source) : pool(0), poolsize(0), buffersize(0) { + operator=(source); + } + + //move + array& operator=(array &&source) { + if(pool) free(pool); + pool = source.pool; + poolsize = source.poolsize; + buffersize = source.buffersize; + source.pool = 0; + source.reset(); + return *this; + } + + array(array &&source) : pool(0), poolsize(0), buffersize(0) { + operator=(std::move(source)); + } + + //index + inline T& operator[](unsigned index) { + if(index >= buffersize) resize(index + 1); + if(index >= buffersize) throw "array[] out of bounds"; + return pool[index]; + } + + inline const T& operator[](unsigned index) const { + if(index >= buffersize) throw "array[] out of bounds"; + return pool[index]; + } + }; + + template struct has_size> { enum { value = true }; }; +} + +#endif diff --git a/supergameboy/nall/base64.hpp b/supergameboy/nall/base64.hpp new file mode 100644 index 00000000..e41c87b7 --- /dev/null +++ b/supergameboy/nall/base64.hpp @@ -0,0 +1,90 @@ +#ifndef NALL_BASE64_HPP +#define NALL_BASE64_HPP + +#include +#include + +namespace nall { + class base64 { + public: + static bool encode(char *&output, const uint8_t* input, unsigned inlength) { + output = new char[inlength * 8 / 6 + 6](); + + unsigned i = 0, o = 0; + while(i < inlength) { + switch(i % 3) { + case 0: { + output[o++] = enc(input[i] >> 2); + output[o] = enc((input[i] & 3) << 4); + } break; + + case 1: { + uint8_t prev = dec(output[o]); + output[o++] = enc(prev + (input[i] >> 4)); + output[o] = enc((input[i] & 15) << 2); + } break; + + case 2: { + uint8_t prev = dec(output[o]); + output[o++] = enc(prev + (input[i] >> 6)); + output[o++] = enc(input[i] & 63); + } break; + } + + i++; + } + + return true; + } + + static bool decode(uint8_t *&output, unsigned &outlength, const char *input) { + unsigned inlength = strlen(input), infix = 0; + output = new uint8_t[inlength](); + + unsigned i = 0, o = 0; + while(i < inlength) { + uint8_t x = dec(input[i]); + + switch(i++ & 3) { + case 0: { + output[o] = x << 2; + } break; + + case 1: { + output[o++] |= x >> 4; + output[o] = (x & 15) << 4; + } break; + + case 2: { + output[o++] |= x >> 2; + output[o] = (x & 3) << 6; + } break; + + case 3: { + output[o++] |= x; + } break; + } + } + + outlength = o; + return true; + } + + private: + static char enc(uint8_t n) { + static char lookup_table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; + return lookup_table[n & 63]; + } + + static uint8_t dec(char n) { + if(n >= 'A' && n <= 'Z') return n - 'A'; + if(n >= 'a' && n <= 'z') return n - 'a' + 26; + if(n >= '0' && n <= '9') return n - '0' + 52; + if(n == '-') return 62; + if(n == '_') return 63; + return 0; + } + }; +} + +#endif diff --git a/supergameboy/nall/bit.hpp b/supergameboy/nall/bit.hpp new file mode 100644 index 00000000..169fc144 --- /dev/null +++ b/supergameboy/nall/bit.hpp @@ -0,0 +1,51 @@ +#ifndef NALL_BIT_HPP +#define NALL_BIT_HPP + +namespace nall { + template inline unsigned uclamp(const unsigned x) { + enum { y = (1U << bits) - 1 }; + return y + ((x - y) & -(x < y)); //min(x, y); + } + + template inline unsigned uclip(const unsigned x) { + enum { m = (1U << bits) - 1 }; + return (x & m); + } + + template inline signed sclamp(const signed x) { + enum { b = 1U << (bits - 1), m = (1U << (bits - 1)) - 1 }; + return (x > m) ? m : (x < -b) ? -b : x; + } + + template inline signed sclip(const signed x) { + enum { b = 1U << (bits - 1), m = (1U << bits) - 1 }; + return ((x & m) ^ b) - b; + } + + namespace bit { + //lowest(0b1110) == 0b0010 + template inline T lowest(const T x) { + return x & -x; + } + + //clear_lowest(0b1110) == 0b1100 + template inline T clear_lowest(const T x) { + return x & (x - 1); + } + + //set_lowest(0b0101) == 0b0111 + template inline T set_lowest(const T x) { + return x | (x + 1); + } + + //round up to next highest single bit: + //round(15) == 16, round(16) == 16, round(17) == 32 + inline unsigned round(unsigned x) { + if((x & (x - 1)) == 0) return x; + while(x & (x - 1)) x &= x - 1; + return x << 1; + } + } +} + +#endif diff --git a/supergameboy/nall/concept.hpp b/supergameboy/nall/concept.hpp new file mode 100644 index 00000000..2949cd5e --- /dev/null +++ b/supergameboy/nall/concept.hpp @@ -0,0 +1,15 @@ +#ifndef NALL_CONCEPT_HPP +#define NALL_CONCEPT_HPP + +namespace nall { + //unsigned count() const; + template struct has_count { enum { value = false }; }; + + //unsigned length() const; + template struct has_length { enum { value = false }; }; + + //unsigned size() const; + template struct has_size { enum { value = false }; }; +} + +#endif diff --git a/supergameboy/nall/config.hpp b/supergameboy/nall/config.hpp new file mode 100644 index 00000000..c713d0b0 --- /dev/null +++ b/supergameboy/nall/config.hpp @@ -0,0 +1,123 @@ +#ifndef NALL_CONFIG_HPP +#define NALL_CONFIG_HPP + +#include +#include +#include + +namespace nall { + namespace configuration_traits { + template struct is_boolean { enum { value = false }; }; + template<> struct is_boolean { enum { value = true }; }; + + template struct is_signed { enum { value = false }; }; + template<> struct is_signed { enum { value = true }; }; + + template struct is_unsigned { enum { value = false }; }; + template<> struct is_unsigned { enum { value = true }; }; + + template struct is_double { enum { value = false }; }; + template<> struct is_double { enum { value = true }; }; + + template struct is_string { enum { value = false }; }; + template<> struct is_string { enum { value = true }; }; + } + + class configuration { + public: + enum type_t { boolean_t, signed_t, unsigned_t, double_t, string_t, unknown_t }; + struct item_t { + uintptr_t data; + string name; + string desc; + type_t type; + + string get() const { + switch(type) { + case boolean_t: return string() << *(bool*)data; + case signed_t: return string() << *(signed*)data; + case unsigned_t: return string() << *(unsigned*)data; + case double_t: return string() << *(double*)data; + case string_t: return string() << "\"" << *(string*)data << "\""; + } + return "???"; + } + + void set(string s) { + switch(type) { + case boolean_t: *(bool*)data = (s == "true"); break; + case signed_t: *(signed*)data = strsigned(s); break; + case unsigned_t: *(unsigned*)data = strunsigned(s); break; + case double_t: *(double*)data = strdouble(s); break; + case string_t: trim(s, "\""); *(string*)data = s; break; + } + } + }; + linear_vector list; + + template + void attach(T &data, const char *name, const char *desc = "") { + unsigned n = list.size(); + list[n].data = (uintptr_t)&data; + list[n].name = name; + list[n].desc = desc; + + if(configuration_traits::is_boolean::value) list[n].type = boolean_t; + else if(configuration_traits::is_signed::value) list[n].type = signed_t; + else if(configuration_traits::is_unsigned::value) list[n].type = unsigned_t; + else if(configuration_traits::is_double::value) list[n].type = double_t; + else if(configuration_traits::is_string::value) list[n].type = string_t; + else list[n].type = unknown_t; + } + + virtual bool load(const char *filename) { + string data; + if(data.readfile(filename) == true) { + data.replace("\r", ""); + lstring line; + line.split("\n", data); + + for(unsigned i = 0; i < line.size(); i++) { + if(auto position = qstrpos(line[i], "#")) line[i][position()] = 0; + if(!qstrpos(line[i], " = ")) continue; + + lstring part; + part.qsplit(" = ", line[i]); + trim(part[0]); + trim(part[1]); + + for(unsigned n = 0; n < list.size(); n++) { + if(part[0] == list[n].name) { + list[n].set(part[1]); + break; + } + } + } + + return true; + } else { + return false; + } + } + + virtual bool save(const char *filename) const { + file fp; + if(fp.open(filename, file::mode_write)) { + for(unsigned i = 0; i < list.size(); i++) { + string output; + output << list[i].name << " = " << list[i].get(); + if(list[i].desc != "") output << " # " << list[i].desc; + output << "\r\n"; + fp.print(output); + } + + fp.close(); + return true; + } else { + return false; + } + } + }; +} + +#endif diff --git a/supergameboy/nall/crc32.hpp b/supergameboy/nall/crc32.hpp new file mode 100644 index 00000000..ad36fbf6 --- /dev/null +++ b/supergameboy/nall/crc32.hpp @@ -0,0 +1,66 @@ +#ifndef NALL_CRC32_HPP +#define NALL_CRC32_HPP + +#include + +namespace nall { + const uint32_t crc32_table[256] = { + 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, + 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, + 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2, + 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, + 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, + 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, + 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c, + 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, + 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, + 0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, + 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106, + 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, + 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, + 0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, + 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, + 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, + 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, + 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, + 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, + 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, + 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, + 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, + 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84, + 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, + 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, + 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, + 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e, + 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, + 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, + 0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, + 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28, + 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, + 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, + 0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, + 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242, + 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, + 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, + 0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, + 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, + 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, + 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, + 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, + 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d + }; + + inline uint32_t crc32_adjust(uint32_t crc32, uint8_t input) { + return ((crc32 >> 8) & 0x00ffffff) ^ crc32_table[(crc32 ^ input) & 0xff]; + } + + inline uint32_t crc32_calculate(const uint8_t *data, unsigned length) { + uint32_t crc32 = ~0; + for(unsigned i = 0; i < length; i++) { + crc32 = crc32_adjust(crc32, data[i]); + } + return ~crc32; + } +} + +#endif diff --git a/supergameboy/nall/detect.hpp b/supergameboy/nall/detect.hpp new file mode 100644 index 00000000..b4991aaf --- /dev/null +++ b/supergameboy/nall/detect.hpp @@ -0,0 +1,30 @@ +#ifndef NALL_DETECT_HPP +#define NALL_DETECT_HPP + +/* Compiler detection */ + +#if defined(__GNUC__) + #define COMPILER_GCC +#elif defined(_MSC_VER) + #define COMPILER_VISUALC +#endif + +/* Platform detection */ + +#if defined(_WIN32) + #define PLATFORM_WIN +#elif defined(__APPLE__) + #define PLATFORM_OSX +#elif defined(linux) || defined(__sun__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) + #define PLATFORM_X +#endif + +/* Endian detection */ + +#if defined(__i386__) || defined(__amd64__) || defined(_M_IX86) || defined(_M_AMD64) + #define ARCH_LSB +#elif defined(__powerpc__) || defined(_M_PPC) || defined(__BIG_ENDIAN__) + #define ARCH_MSB +#endif + +#endif diff --git a/supergameboy/nall/dictionary.hpp b/supergameboy/nall/dictionary.hpp new file mode 100644 index 00000000..9e0a1620 --- /dev/null +++ b/supergameboy/nall/dictionary.hpp @@ -0,0 +1,75 @@ +#ifndef NALL_DICTIONARY_HPP +#define NALL_DICTIONARY_HPP + +#include +#include +#include + +namespace nall { + class dictionary { + public: + string operator[](const char *input) { + for(unsigned i = 0; i < index_input.size(); i++) { + if(index_input[i] == input) return index_output[i]; + } + + //no match, use input; remove input identifier, if one exists + if(strbegin(input, "{{")) { + if(auto pos = strpos(input, "}}")) { + string temp = substr(input, pos() + 2); + return temp; + } + } + + return input; + } + + bool import(const char *filename) { + string data; + if(data.readfile(filename) == false) return false; + ltrim_once(data, "\xef\xbb\xbf"); //remove UTF-8 marker, if it exists + data.replace("\r", ""); + + lstring line; + line.split("\n", data); + for(unsigned i = 0; i < line.size(); i++) { + lstring part; + //format: "Input" = "Output" + part.qsplit("=", line[i]); + if(part.size() != 2) continue; + + //remove whitespace + trim(part[0]); + trim(part[1]); + + //remove quotes + trim_once(part[0], "\""); + trim_once(part[1], "\""); + + unsigned n = index_input.size(); + index_input[n] = part[0]; + index_output[n] = part[1]; + } + + return true; + } + + void reset() { + index_input.reset(); + index_output.reset(); + } + + ~dictionary() { + reset(); + } + + dictionary& operator=(const dictionary&) = delete; + dictionary(const dictionary&) = delete; + + protected: + lstring index_input; + lstring index_output; + }; +} + +#endif diff --git a/supergameboy/nall/dl.hpp b/supergameboy/nall/dl.hpp new file mode 100644 index 00000000..22acf51f --- /dev/null +++ b/supergameboy/nall/dl.hpp @@ -0,0 +1,119 @@ +#ifndef NALL_DL_HPP +#define NALL_DL_HPP + +//dynamic linking support + +#include +#include +#include +#include + +#if defined(PLATFORM_X) || defined(PLATFORM_OSX) + #include +#elif defined(PLATFORM_WIN) + #include + #include +#endif + +namespace nall { + struct library { + bool opened() const { return handle; } + bool open(const char*); + void* sym(const char*); + void close(); + + library() : handle(0) {} + ~library() { close(); } + + library& operator=(const library&) = delete; + library(const library&) = delete; + + private: + uintptr_t handle; + }; + + #if defined(PLATFORM_X) + inline bool library::open(const char *name) { + if(handle) close(); + char *t = new char[strlen(name) + 256]; + strcpy(t, "lib"); + strcat(t, name); + strcat(t, ".so"); + handle = (uintptr_t)dlopen(t, RTLD_LAZY); + if(!handle) { + strcpy(t, "/usr/local/lib/lib"); + strcat(t, name); + strcat(t, ".so"); + handle = (uintptr_t)dlopen(t, RTLD_LAZY); + } + delete[] t; + return handle; + } + + inline void* library::sym(const char *name) { + if(!handle) return 0; + return dlsym((void*)handle, name); + } + + inline void library::close() { + if(!handle) return; + dlclose((void*)handle); + handle = 0; + } + #elif defined(PLATFORM_OSX) + inline bool library::open(const char *name) { + if(handle) close(); + char *t = new char[strlen(name) + 256]; + strcpy(t, "lib"); + strcat(t, name); + strcat(t, ".dylib"); + handle = (uintptr_t)dlopen(t, RTLD_LAZY); + if(!handle) { + strcpy(t, "/usr/local/lib/lib"); + strcat(t, name); + strcat(t, ".dylib"); + handle = (uintptr_t)dlopen(t, RTLD_LAZY); + } + delete[] t; + return handle; + } + + inline void* library::sym(const char *name) { + if(!handle) return 0; + return dlsym((void*)handle, name); + } + + inline void library::close() { + if(!handle) return; + dlclose((void*)handle); + handle = 0; + } + #elif defined(PLATFORM_WIN) + inline bool library::open(const char *name) { + if(handle) close(); + char *t = new char[strlen(name) + 8]; + strcpy(t, name); + strcat(t, ".dll"); + handle = (uintptr_t)LoadLibraryW(utf16_t(t)); + delete[] t; + return handle; + } + + inline void* library::sym(const char *name) { + if(!handle) return 0; + return (void*)GetProcAddress((HMODULE)handle, name); + } + + inline void library::close() { + if(!handle) return; + FreeLibrary((HMODULE)handle); + handle = 0; + } + #else + inline bool library::open(const char*) { return false; } + inline void* library::sym(const char*) { return 0; } + inline void library::close() {} + #endif +}; + +#endif diff --git a/supergameboy/nall/endian.hpp b/supergameboy/nall/endian.hpp new file mode 100644 index 00000000..40d15633 --- /dev/null +++ b/supergameboy/nall/endian.hpp @@ -0,0 +1,38 @@ +#ifndef NALL_ENDIAN_HPP +#define NALL_ENDIAN_HPP + +#if !defined(ARCH_MSB) + //little-endian: uint8_t[] { 0x01, 0x02, 0x03, 0x04 } == 0x04030201 + #define order_lsb2(a,b) a,b + #define order_lsb3(a,b,c) a,b,c + #define order_lsb4(a,b,c,d) a,b,c,d + #define order_lsb5(a,b,c,d,e) a,b,c,d,e + #define order_lsb6(a,b,c,d,e,f) a,b,c,d,e,f + #define order_lsb7(a,b,c,d,e,f,g) a,b,c,d,e,f,g + #define order_lsb8(a,b,c,d,e,f,g,h) a,b,c,d,e,f,g,h + #define order_msb2(a,b) b,a + #define order_msb3(a,b,c) c,b,a + #define order_msb4(a,b,c,d) d,c,b,a + #define order_msb5(a,b,c,d,e) e,d,c,b,a + #define order_msb6(a,b,c,d,e,f) f,e,d,c,b,a + #define order_msb7(a,b,c,d,e,f,g) g,f,e,d,c,b,a + #define order_msb8(a,b,c,d,e,f,g,h) h,g,f,e,d,c,b,a +#else + //big-endian: uint8_t[] { 0x01, 0x02, 0x03, 0x04 } == 0x01020304 + #define order_lsb2(a,b) b,a + #define order_lsb3(a,b,c) c,b,a + #define order_lsb4(a,b,c,d) d,c,b,a + #define order_lsb5(a,b,c,d,e) e,d,c,b,a + #define order_lsb6(a,b,c,d,e,f) f,e,d,c,b,a + #define order_lsb7(a,b,c,d,e,f,g) g,f,e,d,c,b,a + #define order_lsb8(a,b,c,d,e,f,g,h) h,g,f,e,d,c,b,a + #define order_msb2(a,b) a,b + #define order_msb3(a,b,c) a,b,c + #define order_msb4(a,b,c,d) a,b,c,d + #define order_msb5(a,b,c,d,e) a,b,c,d,e + #define order_msb6(a,b,c,d,e,f) a,b,c,d,e,f + #define order_msb7(a,b,c,d,e,f,g) a,b,c,d,e,f,g + #define order_msb8(a,b,c,d,e,f,g,h) a,b,c,d,e,f,g,h +#endif + +#endif diff --git a/supergameboy/nall/file.hpp b/supergameboy/nall/file.hpp new file mode 100644 index 00000000..4c8ca8ee --- /dev/null +++ b/supergameboy/nall/file.hpp @@ -0,0 +1,259 @@ +#ifndef NALL_FILE_HPP +#define NALL_FILE_HPP + +#include +#include + +#if !defined(_WIN32) + #include +#else + #include +#endif + +#include +#include +#include + +namespace nall { + inline FILE* fopen_utf8(const char *utf8_filename, const char *mode) { + #if !defined(_WIN32) + return fopen(utf8_filename, mode); + #else + return _wfopen(utf16_t(utf8_filename), utf16_t(mode)); + #endif + } + + class file { + public: + enum FileMode { mode_read, mode_write, mode_readwrite, mode_writeread }; + enum SeekMode { seek_absolute, seek_relative }; + + uint8_t read() { + if(!fp) return 0xff; //file not open + if(file_mode == mode_write) return 0xff; //reads not permitted + if(file_offset >= file_size) return 0xff; //cannot read past end of file + buffer_sync(); + return buffer[(file_offset++) & buffer_mask]; + } + + uintmax_t readl(unsigned length = 1) { + uintmax_t data = 0; + for(int i = 0; i < length; i++) { + data |= (uintmax_t)read() << (i << 3); + } + return data; + } + + uintmax_t readm(unsigned length = 1) { + uintmax_t data = 0; + while(length--) { + data <<= 8; + data |= read(); + } + return data; + } + + void read(uint8_t *buffer, unsigned length) { + while(length--) *buffer++ = read(); + } + + void write(uint8_t data) { + if(!fp) return; //file not open + if(file_mode == mode_read) return; //writes not permitted + buffer_sync(); + buffer[(file_offset++) & buffer_mask] = data; + buffer_dirty = true; + if(file_offset > file_size) file_size = file_offset; + } + + void writel(uintmax_t data, unsigned length = 1) { + while(length--) { + write(data); + data >>= 8; + } + } + + void writem(uintmax_t data, unsigned length = 1) { + for(int i = length - 1; i >= 0; i--) { + write(data >> (i << 3)); + } + } + + void write(const uint8_t *buffer, unsigned length) { + while(length--) write(*buffer++); + } + + void print(const char *string) { + if(!string) return; + while(*string) write(*string++); + } + + void flush() { + buffer_flush(); + fflush(fp); + } + + void seek(int offset, SeekMode mode = seek_absolute) { + if(!fp) return; //file not open + buffer_flush(); + + uintmax_t req_offset = file_offset; + switch(mode) { + case seek_absolute: req_offset = offset; break; + case seek_relative: req_offset += offset; break; + } + + if(req_offset < 0) req_offset = 0; //cannot seek before start of file + if(req_offset > file_size) { + if(file_mode == mode_read) { //cannot seek past end of file + req_offset = file_size; + } else { //pad file to requested location + file_offset = file_size; + while(file_size < req_offset) write(0x00); + } + } + + file_offset = req_offset; + } + + int offset() { + if(!fp) return -1; //file not open + return file_offset; + } + + int size() { + if(!fp) return -1; //file not open + return file_size; + } + + bool truncate(unsigned size) { + if(!fp) return false; //file not open + #if !defined(_WIN32) + return ftruncate(fileno(fp), size) == 0; + #else + return _chsize(fileno(fp), size) == 0; + #endif + } + + bool end() { + if(!fp) return true; //file not open + return file_offset >= file_size; + } + + static bool exists(const char *fn) { + #if !defined(_WIN32) + FILE *fp = fopen(fn, "rb"); + #else + FILE *fp = _wfopen(utf16_t(fn), L"rb"); + #endif + if(fp) { + fclose(fp); + return true; + } + return false; + } + + static unsigned size(const char *fn) { + #if !defined(_WIN32) + FILE *fp = fopen(fn, "rb"); + #else + FILE *fp = _wfopen(utf16_t(fn), L"rb"); + #endif + unsigned filesize = 0; + if(fp) { + fseek(fp, 0, SEEK_END); + filesize = ftell(fp); + fclose(fp); + } + return filesize; + } + + bool open() { + return fp; + } + + bool open(const char *fn, FileMode mode) { + if(fp) return false; + + switch(file_mode = mode) { + #if !defined(_WIN32) + case mode_read: fp = fopen(fn, "rb"); break; + case mode_write: fp = fopen(fn, "wb+"); break; //need read permission for buffering + case mode_readwrite: fp = fopen(fn, "rb+"); break; + case mode_writeread: fp = fopen(fn, "wb+"); break; + #else + case mode_read: fp = _wfopen(utf16_t(fn), L"rb"); break; + case mode_write: fp = _wfopen(utf16_t(fn), L"wb+"); break; + case mode_readwrite: fp = _wfopen(utf16_t(fn), L"rb+"); break; + case mode_writeread: fp = _wfopen(utf16_t(fn), L"wb+"); break; + #endif + } + if(!fp) return false; + buffer_offset = -1; //invalidate buffer + file_offset = 0; + fseek(fp, 0, SEEK_END); + file_size = ftell(fp); + fseek(fp, 0, SEEK_SET); + return true; + } + + void close() { + if(!fp) return; + buffer_flush(); + fclose(fp); + fp = 0; + } + + file() { + memset(buffer, 0, sizeof buffer); + buffer_offset = -1; + buffer_dirty = false; + fp = 0; + file_offset = 0; + file_size = 0; + file_mode = mode_read; + } + + ~file() { + close(); + } + + file& operator=(const file&) = delete; + file(const file&) = delete; + + private: + enum { buffer_size = 1 << 12, buffer_mask = buffer_size - 1 }; + char buffer[buffer_size]; + int buffer_offset; + bool buffer_dirty; + FILE *fp; + unsigned file_offset; + unsigned file_size; + FileMode file_mode; + + void buffer_sync() { + if(!fp) return; //file not open + if(buffer_offset != (file_offset & ~buffer_mask)) { + buffer_flush(); + buffer_offset = file_offset & ~buffer_mask; + fseek(fp, buffer_offset, SEEK_SET); + unsigned length = (buffer_offset + buffer_size) <= file_size ? buffer_size : (file_size & buffer_mask); + if(length) unsigned unused = fread(buffer, 1, length, fp); + } + } + + void buffer_flush() { + if(!fp) return; //file not open + if(file_mode == mode_read) return; //buffer cannot be written to + if(buffer_offset < 0) return; //buffer unused + if(buffer_dirty == false) return; //buffer unmodified since read + fseek(fp, buffer_offset, SEEK_SET); + unsigned length = (buffer_offset + buffer_size) <= file_size ? buffer_size : (file_size & buffer_mask); + if(length) unsigned unused = fwrite(buffer, 1, length, fp); + buffer_offset = -1; //invalidate buffer + buffer_dirty = false; + } + }; +} + +#endif diff --git a/supergameboy/nall/filemap.hpp b/supergameboy/nall/filemap.hpp new file mode 100644 index 00000000..a05f0eb7 --- /dev/null +++ b/supergameboy/nall/filemap.hpp @@ -0,0 +1,190 @@ +#ifndef NALL_FILEMAP_HPP +#define NALL_FILEMAP_HPP + +#include +#include + +#include +#include +#if defined(_WIN32) + #include +#else + #include + #include + #include + #include + #include +#endif + +namespace nall { + class filemap { + public: + enum filemode { mode_read, mode_write, mode_readwrite, mode_writeread }; + + bool open(const char *filename, filemode mode) { return p_open(filename, mode); } + void close() { return p_close(); } + unsigned size() const { return p_size; } + uint8_t* handle() { return p_handle; } + const uint8_t* handle() const { return p_handle; } + filemap() : p_size(0), p_handle(0) { p_ctor(); } + ~filemap() { p_dtor(); } + + private: + unsigned p_size; + uint8_t *p_handle; + + #if defined(_WIN32) + //============= + //MapViewOfFile + //============= + + HANDLE p_filehandle, p_maphandle; + + bool p_open(const char *filename, filemode mode) { + int desired_access, creation_disposition, flprotect, map_access; + + switch(mode) { + default: return false; + case mode_read: + desired_access = GENERIC_READ; + creation_disposition = OPEN_EXISTING; + flprotect = PAGE_READONLY; + map_access = FILE_MAP_READ; + break; + case mode_write: + //write access requires read access + desired_access = GENERIC_WRITE; + creation_disposition = CREATE_ALWAYS; + flprotect = PAGE_READWRITE; + map_access = FILE_MAP_ALL_ACCESS; + break; + case mode_readwrite: + desired_access = GENERIC_READ | GENERIC_WRITE; + creation_disposition = OPEN_EXISTING; + flprotect = PAGE_READWRITE; + map_access = FILE_MAP_ALL_ACCESS; + break; + case mode_writeread: + desired_access = GENERIC_READ | GENERIC_WRITE; + creation_disposition = CREATE_NEW; + flprotect = PAGE_READWRITE; + map_access = FILE_MAP_ALL_ACCESS; + break; + } + + p_filehandle = CreateFileW(utf16_t(filename), desired_access, FILE_SHARE_READ, NULL, + creation_disposition, FILE_ATTRIBUTE_NORMAL, NULL); + if(p_filehandle == INVALID_HANDLE_VALUE) return false; + + p_size = GetFileSize(p_filehandle, NULL); + + p_maphandle = CreateFileMapping(p_filehandle, NULL, flprotect, 0, p_size, NULL); + if(p_maphandle == INVALID_HANDLE_VALUE) { + CloseHandle(p_filehandle); + p_filehandle = INVALID_HANDLE_VALUE; + return false; + } + + p_handle = (uint8_t*)MapViewOfFile(p_maphandle, map_access, 0, 0, p_size); + return p_handle; + } + + void p_close() { + if(p_handle) { + UnmapViewOfFile(p_handle); + p_handle = 0; + } + + if(p_maphandle != INVALID_HANDLE_VALUE) { + CloseHandle(p_maphandle); + p_maphandle = INVALID_HANDLE_VALUE; + } + + if(p_filehandle != INVALID_HANDLE_VALUE) { + CloseHandle(p_filehandle); + p_filehandle = INVALID_HANDLE_VALUE; + } + } + + void p_ctor() { + p_filehandle = INVALID_HANDLE_VALUE; + p_maphandle = INVALID_HANDLE_VALUE; + } + + void p_dtor() { + close(); + } + + #else + //==== + //mmap + //==== + + int p_fd; + + bool p_open(const char *filename, filemode mode) { + int open_flags, mmap_flags; + + switch(mode) { + default: return false; + case mode_read: + open_flags = O_RDONLY; + mmap_flags = PROT_READ; + break; + case mode_write: + open_flags = O_RDWR | O_CREAT; //mmap() requires read access + mmap_flags = PROT_WRITE; + break; + case mode_readwrite: + open_flags = O_RDWR; + mmap_flags = PROT_READ | PROT_WRITE; + break; + case mode_writeread: + open_flags = O_RDWR | O_CREAT; + mmap_flags = PROT_READ | PROT_WRITE; + break; + } + + p_fd = ::open(filename, open_flags); + if(p_fd < 0) return false; + + struct stat p_stat; + fstat(p_fd, &p_stat); + p_size = p_stat.st_size; + + p_handle = (uint8_t*)mmap(0, p_size, mmap_flags, MAP_SHARED, p_fd, 0); + if(p_handle == MAP_FAILED) { + p_handle = 0; + ::close(p_fd); + p_fd = -1; + return false; + } + + return p_handle; + } + + void p_close() { + if(p_handle) { + munmap(p_handle, p_size); + p_handle = 0; + } + + if(p_fd >= 0) { + ::close(p_fd); + p_fd = -1; + } + } + + void p_ctor() { + p_fd = -1; + } + + void p_dtor() { + p_close(); + } + + #endif + }; +} + +#endif diff --git a/supergameboy/nall/foreach.hpp b/supergameboy/nall/foreach.hpp new file mode 100644 index 00000000..ea975b84 --- /dev/null +++ b/supergameboy/nall/foreach.hpp @@ -0,0 +1,31 @@ +#ifndef NALL_FOREACH_HPP +#define NALL_FOREACH_HPP + +#undef foreach +#define foreach(iter, object) \ + for(unsigned foreach_counter = 0, foreach_limit = foreach_size(object), foreach_once = 0, foreach_broken = 0; foreach_counter < foreach_limit && foreach_broken == 0; foreach_counter++, foreach_once = 0) \ + for(auto &iter = object[foreach_counter]; foreach_once == 0 && (foreach_broken = 1); foreach_once++, foreach_broken = 0) + +#include +#include +#include + +namespace nall { + template unsigned foreach_size(const T& object, typename mp_enable_if>::type = 0) { + return object.count(); + } + + template unsigned foreach_size(const T& object, typename mp_enable_if>::type = 0) { + return object.length(); + } + + template unsigned foreach_size(const T& object, typename mp_enable_if>::type = 0) { + return object.size(); + } + + template unsigned foreach_size(const T& object, typename mp_enable_if>::type = 0) { + return sizeof(T) / sizeof(typename std::remove_extent::type); + } +} + +#endif diff --git a/supergameboy/nall/function.hpp b/supergameboy/nall/function.hpp new file mode 100644 index 00000000..3f0f704e --- /dev/null +++ b/supergameboy/nall/function.hpp @@ -0,0 +1,102 @@ +#ifndef NALL_FUNCTION_HPP +#define NALL_FUNCTION_HPP + +#include +#include + +namespace nall { + template class function; + + template + class function { + private: + struct base1 { virtual void func1(P...) {} }; + struct base2 { virtual void func2(P...) {} }; + struct derived : base1, virtual base2 {}; + + struct data_t { + R (*callback)(const data_t&, P...); + union { + R (*callback_global)(P...); + struct { + R (derived::*callback_member)(P...); + void *object; + }; + }; + } data; + + static R callback_global(const data_t &data, P... p) { + return data.callback_global(p...); + } + + template + static R callback_member(const data_t &data, P... p) { + return (((C*)data.object)->*((R (C::*&)(P...))data.callback_member))(p...); + } + + public: + R operator()(P... p) const { return data.callback(data, p...); } + operator bool() const { return data.callback; } + void reset() { data.callback = 0; } + + function& operator=(const function &source) { memcpy(&data, &source.data, sizeof(data_t)); return *this; } + function(const function &source) { operator=(source); } + + //no pointer + function() { + data.callback = 0; + } + + //symbolic link pointer (nall/dl.hpp::sym, etc) + function(void *callback) { + data.callback = callback ? &callback_global : 0; + data.callback_global = (R (*)(P...))callback; + } + + //global function pointer + function(R (*callback)(P...)) { + data.callback = &callback_global; + data.callback_global = callback; + } + + //member function pointer + template + function(R (C::*callback)(P...), C *object) { + static_assert(sizeof data.callback_member >= sizeof callback, "callback_member is too small"); + data.callback = &callback_member; + (R (C::*&)(P...))data.callback_member = callback; + data.object = object; + } + + //const member function pointer + template + function(R (C::*callback)(P...) const, C *object) { + static_assert(sizeof data.callback_member >= sizeof callback, "callback_member is too small"); + data.callback = &callback_member; + (R (C::*&)(P...))data.callback_member = (R (C::*&)(P...))callback; + data.object = object; + } + + //lambda function pointer + template + function(T callback) { + static_assert(std::is_same::type>::value, "lambda mismatch"); + data.callback = &callback_global; + data.callback_global = (R (*)(P...))callback; + } + }; + + //bind functions to ease construction and assignment of function() with more than one argument + + template + function bind(R (C::*callback)(P...), C *object) { + return function(callback, object); + } + + template + function bind(R (C::*callback)(P...) const, C *object) { + return function(callback, object); + } +} + +#endif diff --git a/supergameboy/nall/input.hpp b/supergameboy/nall/input.hpp new file mode 100644 index 00000000..83c4a484 --- /dev/null +++ b/supergameboy/nall/input.hpp @@ -0,0 +1,386 @@ +#ifndef NALL_INPUT_HPP +#define NALL_INPUT_HPP + +#include +#include +#include + +#include +#include + +namespace nall { + +struct Keyboard; +Keyboard& keyboard(unsigned = 0); + +static const char KeyboardScancodeName[][64] = { + "Escape", "F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12", + "PrintScreen", "ScrollLock", "Pause", "Tilde", + "Num1", "Num2", "Num3", "Num4", "Num5", "Num6", "Num7", "Num8", "Num9", "Num0", + "Dash", "Equal", "Backspace", + "Insert", "Delete", "Home", "End", "PageUp", "PageDown", + "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", + "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", + "LeftBracket", "RightBracket", "Backslash", "Semicolon", "Apostrophe", "Comma", "Period", "Slash", + "Keypad1", "Keypad2", "Keypad3", "Keypad4", "Keypad5", "Keypad6", "Keypad7", "Keypad8", "Keypad9", "Keypad0", + "Point", "Enter", "Add", "Subtract", "Multiply", "Divide", + "NumLock", "CapsLock", + "Up", "Down", "Left", "Right", + "Tab", "Return", "Spacebar", "Menu", + "Shift", "Control", "Alt", "Super", +}; + +struct Keyboard { + const unsigned ID; + enum { Base = 1 }; + enum { Count = 8, Size = 128 }; + + enum Scancode { + Escape, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, + PrintScreen, ScrollLock, Pause, Tilde, + Num1, Num2, Num3, Num4, Num5, Num6, Num7, Num8, Num9, Num0, + Dash, Equal, Backspace, + Insert, Delete, Home, End, PageUp, PageDown, + A, B, C, D, E, F, G, H, I, J, K, L, M, + N, O, P, Q, R, S, T, U, V, W, X, Y, Z, + LeftBracket, RightBracket, Backslash, Semicolon, Apostrophe, Comma, Period, Slash, + Keypad1, Keypad2, Keypad3, Keypad4, Keypad5, Keypad6, Keypad7, Keypad8, Keypad9, Keypad0, + Point, Enter, Add, Subtract, Multiply, Divide, + NumLock, CapsLock, + Up, Down, Left, Right, + Tab, Return, Spacebar, Menu, + Shift, Control, Alt, Super, + Limit, + }; + + static signed numberDecode(uint16_t scancode) { + for(unsigned i = 0; i < Count; i++) { + if(keyboard(i).belongsTo(scancode)) return i; + } + return -1; + } + + static signed keyDecode(uint16_t scancode) { + for(unsigned i = 0; i < Count; i++) { + if(keyboard(i).isKey(scancode)) return scancode - keyboard(i).key(Escape); + } + return -1; + } + + static signed modifierDecode(uint16_t scancode) { + for(unsigned i = 0; i < Count; i++) { + if(keyboard(i).isModifier(scancode)) return scancode - keyboard(i).key(Shift); + } + return -1; + } + + static bool isAnyKey(uint16_t scancode) { + for(unsigned i = 0; i < Count; i++) { + if(keyboard(i).isKey(scancode)) return true; + } + return false; + } + + static bool isAnyModifier(uint16_t scancode) { + for(unsigned i = 0; i < Count; i++) { + if(keyboard(i).isModifier(scancode)) return true; + } + return false; + } + + static uint16_t decode(const char *name) { + string s(name); + if(!strbegin(name, "KB")) return 0; + ltrim(s, "KB"); + unsigned id = strunsigned(s); + auto pos = strpos(s, "::"); + if(!pos) return 0; + s = substr(s, pos() + 2); + for(unsigned i = 0; i < Limit; i++) { + if(s == KeyboardScancodeName[i]) return Base + Size * id + i; + } + return 0; + } + + string encode(uint16_t code) const { + unsigned index = 0; + for(unsigned i = 0; i < Count; i++) { + if(code >= Base + Size * i && code < Base + Size * (i + 1)) { + index = code - (Base + Size * i); + break; + } + } + return string() << "KB" << ID << "::" << KeyboardScancodeName[index]; + } + + uint16_t operator[](Scancode code) const { return Base + ID * Size + code; } + uint16_t key(unsigned id) const { return Base + Size * ID + id; } + bool isKey(unsigned id) const { return id >= key(Escape) && id <= key(Menu); } + bool isModifier(unsigned id) const { return id >= key(Shift) && id <= key(Super); } + bool belongsTo(uint16_t scancode) const { return isKey(scancode) || isModifier(scancode); } + + Keyboard(unsigned ID_) : ID(ID_) {} +}; + +inline Keyboard& keyboard(unsigned id) { + static Keyboard kb0(0), kb1(1), kb2(2), kb3(3), kb4(4), kb5(5), kb6(6), kb7(7); + switch(id) { default: + case 0: return kb0; case 1: return kb1; case 2: return kb2; case 3: return kb3; + case 4: return kb4; case 5: return kb5; case 6: return kb6; case 7: return kb7; + } +} + +static const char MouseScancodeName[][64] = { + "Xaxis", "Yaxis", "Zaxis", + "Button0", "Button1", "Button2", "Button3", "Button4", "Button5", "Button6", "Button7", +}; + +struct Mouse; +Mouse& mouse(unsigned = 0); + +struct Mouse { + const unsigned ID; + enum { Base = Keyboard::Base + Keyboard::Size * Keyboard::Count }; + enum { Count = 8, Size = 16 }; + enum { Axes = 3, Buttons = 8 }; + + enum Scancode { + Xaxis, Yaxis, Zaxis, + Button0, Button1, Button2, Button3, Button4, Button5, Button6, Button7, + Limit, + }; + + static signed numberDecode(uint16_t scancode) { + for(unsigned i = 0; i < Count; i++) { + if(mouse(i).belongsTo(scancode)) return i; + } + return -1; + } + + static signed axisDecode(uint16_t scancode) { + for(unsigned i = 0; i < Count; i++) { + if(mouse(i).isAxis(scancode)) return scancode - mouse(i).axis(0); + } + return -1; + } + + static signed buttonDecode(uint16_t scancode) { + for(unsigned i = 0; i < Count; i++) { + if(mouse(i).isButton(scancode)) return scancode - mouse(i).button(0); + } + return -1; + } + + static bool isAnyAxis(uint16_t scancode) { + for(unsigned i = 0; i < Count; i++) { + if(mouse(i).isAxis(scancode)) return true; + } + return false; + } + + static bool isAnyButton(uint16_t scancode) { + for(unsigned i = 0; i < Count; i++) { + if(mouse(i).isButton(scancode)) return true; + } + return false; + } + + static uint16_t decode(const char *name) { + string s(name); + if(!strbegin(name, "MS")) return 0; + ltrim(s, "MS"); + unsigned id = strunsigned(s); + auto pos = strpos(s, "::"); + if(!pos) return 0; + s = substr(s, pos() + 2); + for(unsigned i = 0; i < Limit; i++) { + if(s == MouseScancodeName[i]) return Base + Size * id + i; + } + return 0; + } + + string encode(uint16_t code) const { + unsigned index = 0; + for(unsigned i = 0; i < Count; i++) { + if(code >= Base + Size * i && code < Base + Size * (i + 1)) { + index = code - (Base + Size * i); + break; + } + } + return string() << "MS" << ID << "::" << MouseScancodeName[index]; + } + + uint16_t operator[](Scancode code) const { return Base + ID * Size + code; } + uint16_t axis(unsigned id) const { return Base + Size * ID + Xaxis + id; } + uint16_t button(unsigned id) const { return Base + Size * ID + Button0 + id; } + bool isAxis(unsigned id) const { return id >= axis(0) && id <= axis(2); } + bool isButton(unsigned id) const { return id >= button(0) && id <= button(7); } + bool belongsTo(uint16_t scancode) const { return isAxis(scancode) || isButton(scancode); } + + Mouse(unsigned ID_) : ID(ID_) {} +}; + +inline Mouse& mouse(unsigned id) { + static Mouse ms0(0), ms1(1), ms2(2), ms3(3), ms4(4), ms5(5), ms6(6), ms7(7); + switch(id) { default: + case 0: return ms0; case 1: return ms1; case 2: return ms2; case 3: return ms3; + case 4: return ms4; case 5: return ms5; case 6: return ms6; case 7: return ms7; + } +} + +static const char JoypadScancodeName[][64] = { + "Hat0", "Hat1", "Hat2", "Hat3", "Hat4", "Hat5", "Hat6", "Hat7", + "Axis0", "Axis1", "Axis2", "Axis3", "Axis4", "Axis5", "Axis6", "Axis7", + "Axis8", "Axis9", "Axis10", "Axis11", "Axis12", "Axis13", "Axis14", "Axis15", + "Button0", "Button1", "Button2", "Button3", "Button4", "Button5", "Button6", "Button7", + "Button8", "Button9", "Button10", "Button11", "Button12", "Button13", "Button14", "Button15", + "Button16", "Button17", "Button18", "Button19", "Button20", "Button21", "Button22", "Button23", + "Button24", "Button25", "Button26", "Button27", "Button28", "Button29", "Button30", "Button31", +}; + +struct Joypad; +Joypad& joypad(unsigned = 0); + +struct Joypad { + const unsigned ID; + enum { Base = Mouse::Base + Mouse::Size * Mouse::Count }; + enum { Count = 8, Size = 64 }; + enum { Hats = 8, Axes = 16, Buttons = 32 }; + + enum Scancode { + Hat0, Hat1, Hat2, Hat3, Hat4, Hat5, Hat6, Hat7, + Axis0, Axis1, Axis2, Axis3, Axis4, Axis5, Axis6, Axis7, + Axis8, Axis9, Axis10, Axis11, Axis12, Axis13, Axis14, Axis15, + Button0, Button1, Button2, Button3, Button4, Button5, Button6, Button7, + Button8, Button9, Button10, Button11, Button12, Button13, Button14, Button15, + Button16, Button17, Button18, Button19, Button20, Button21, Button22, Button23, + Button24, Button25, Button26, Button27, Button28, Button29, Button30, Button31, + Limit, + }; + + enum Hat { HatCenter = 0, HatUp = 1, HatRight = 2, HatDown = 4, HatLeft = 8 }; + + static signed numberDecode(uint16_t scancode) { + for(unsigned i = 0; i < Count; i++) { + if(joypad(i).belongsTo(scancode)) return i; + } + return -1; + } + + static signed hatDecode(uint16_t scancode) { + for(unsigned i = 0; i < Count; i++) { + if(joypad(i).isHat(scancode)) return scancode - joypad(i).hat(0); + } + return -1; + } + + static signed axisDecode(uint16_t scancode) { + for(unsigned i = 0; i < Count; i++) { + if(joypad(i).isAxis(scancode)) return scancode - joypad(i).axis(0); + } + return -1; + } + + static signed buttonDecode(uint16_t scancode) { + for(unsigned i = 0; i < Count; i++) { + if(joypad(i).isButton(scancode)) return scancode - joypad(i).button(0); + } + return -1; + } + + static bool isAnyHat(uint16_t scancode) { + for(unsigned i = 0; i < Count; i++) { + if(joypad(i).isHat(scancode)) return true; + } + return false; + } + + static bool isAnyAxis(uint16_t scancode) { + for(unsigned i = 0; i < Count; i++) { + if(joypad(i).isAxis(scancode)) return true; + } + return false; + } + + static bool isAnyButton(uint16_t scancode) { + for(unsigned i = 0; i < Count; i++) { + if(joypad(i).isButton(scancode)) return true; + } + return false; + } + + static uint16_t decode(const char *name) { + string s(name); + if(!strbegin(name, "JP")) return 0; + ltrim(s, "JP"); + unsigned id = strunsigned(s); + auto pos = strpos(s, "::"); + if(!pos) return 0; + s = substr(s, pos() + 2); + for(unsigned i = 0; i < Limit; i++) { + if(s == JoypadScancodeName[i]) return Base + Size * id + i; + } + return 0; + } + + string encode(uint16_t code) const { + unsigned index = 0; + for(unsigned i = 0; i < Count; i++) { + if(code >= Base + Size * i && code < Base + Size * (i + 1)) { + index = code - (Base + Size * i); + } + } + return string() << "JP" << ID << "::" << JoypadScancodeName[index]; + } + + uint16_t operator[](Scancode code) const { return Base + ID * Size + code; } + uint16_t hat(unsigned id) const { return Base + Size * ID + Hat0 + id; } + uint16_t axis(unsigned id) const { return Base + Size * ID + Axis0 + id; } + uint16_t button(unsigned id) const { return Base + Size * ID + Button0 + id; } + bool isHat(unsigned id) const { return id >= hat(0) && id <= hat(7); } + bool isAxis(unsigned id) const { return id >= axis(0) && id <= axis(15); } + bool isButton(unsigned id) const { return id >= button(0) && id <= button(31); } + bool belongsTo(uint16_t scancode) const { return isHat(scancode) || isAxis(scancode) || isButton(scancode); } + + Joypad(unsigned ID_) : ID(ID_) {} +}; + +inline Joypad& joypad(unsigned id) { + static Joypad jp0(0), jp1(1), jp2(2), jp3(3), jp4(4), jp5(5), jp6(6), jp7(7); + switch(id) { default: + case 0: return jp0; case 1: return jp1; case 2: return jp2; case 3: return jp3; + case 4: return jp4; case 5: return jp5; case 6: return jp6; case 7: return jp7; + } +} + +struct Scancode { + enum { None = 0, Limit = Joypad::Base + Joypad::Size * Joypad::Count }; + + static uint16_t decode(const char *name) { + uint16_t code; + code = Keyboard::decode(name); + if(code) return code; + code = Mouse::decode(name); + if(code) return code; + code = Joypad::decode(name); + if(code) return code; + return None; + } + + static string encode(uint16_t code) { + for(unsigned i = 0; i < Keyboard::Count; i++) { + if(keyboard(i).belongsTo(code)) return keyboard(i).encode(code); + } + for(unsigned i = 0; i < Mouse::Count; i++) { + if(mouse(i).belongsTo(code)) return mouse(i).encode(code); + } + for(unsigned i = 0; i < Joypad::Count; i++) { + if(joypad(i).belongsTo(code)) return joypad(i).encode(code); + } + return "None"; + } +}; + +} + +#endif diff --git a/supergameboy/nall/lzss.hpp b/supergameboy/nall/lzss.hpp new file mode 100644 index 00000000..202bc814 --- /dev/null +++ b/supergameboy/nall/lzss.hpp @@ -0,0 +1,81 @@ +#ifndef NALL_LZSS_HPP +#define NALL_LZSS_HPP + +#include +#include +#include + +namespace nall { + class lzss { + public: + static bool encode(uint8_t *&output, unsigned &outlength, const uint8_t *input, unsigned inlength) { + output = new(zeromemory) uint8_t[inlength * 9 / 8 + 9]; + + unsigned i = 0, o = 0; + while(i < inlength) { + unsigned flagoffset = o++; + uint8_t flag = 0x00; + + for(unsigned b = 0; b < 8 && i < inlength; b++) { + unsigned longest = 0, pointer; + for(unsigned index = 1; index < 4096; index++) { + unsigned count = 0; + while(true) { + if(count >= 15 + 3) break; //verify pattern match is not longer than max length + if(i + count >= inlength) break; //verify pattern match does not read past end of input + if(i + count < index) break; //verify read is not before start of input + if(input[i + count] != input[i + count - index]) break; //verify pattern still matches + count++; + } + + if(count > longest) { + longest = count; + pointer = index; + } + } + + if(longest < 3) output[o++] = input[i++]; + else { + flag |= 1 << b; + uint16_t x = ((longest - 3) << 12) + pointer; + output[o++] = x; + output[o++] = x >> 8; + i += longest; + } + } + + output[flagoffset] = flag; + } + + outlength = o; + return true; + } + + static bool decode(uint8_t *&output, const uint8_t *input, unsigned length) { + output = new(zeromemory) uint8_t[length]; + + unsigned i = 0, o = 0; + while(o < length) { + uint8_t flag = input[i++]; + + for(unsigned b = 0; b < 8 && o < length; b++) { + if(!(flag & (1 << b))) output[o++] = input[i++]; + else { + uint16_t offset = input[i++]; + offset += input[i++] << 8; + uint16_t lookuplength = (offset >> 12) + 3; + offset &= 4095; + for(unsigned index = 0; index < lookuplength && o + index < length; index++) { + output[o + index] = output[o + index - offset]; + } + o += lookuplength; + } + } + } + + return true; + } + }; +} + +#endif diff --git a/supergameboy/nall/moduloarray.hpp b/supergameboy/nall/moduloarray.hpp new file mode 100644 index 00000000..be549ae9 --- /dev/null +++ b/supergameboy/nall/moduloarray.hpp @@ -0,0 +1,40 @@ +#ifndef NALL_MODULO_HPP +#define NALL_MODULO_HPP + +#include + +namespace nall { + template class modulo_array { + public: + inline T operator[](int index) const { + return buffer[size + index]; + } + + inline T read(int index) const { + return buffer[size + index]; + } + + inline void write(unsigned index, const T value) { + buffer[index] = + buffer[index + size] = + buffer[index + size + size] = value; + } + + void serialize(serializer &s) { + s.array(buffer, size * 3); + } + + modulo_array() { + buffer = new T[size * 3](); + } + + ~modulo_array() { + delete[] buffer; + } + + private: + T *buffer; + }; +} + +#endif diff --git a/supergameboy/nall/platform.hpp b/supergameboy/nall/platform.hpp new file mode 100644 index 00000000..68ed37ce --- /dev/null +++ b/supergameboy/nall/platform.hpp @@ -0,0 +1,80 @@ +#ifndef NALL_PLATFORM_HPP +#define NALL_PLATFORM_HPP + +#include + +//========================= +//standard platform headers +//========================= + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(_WIN32) + #include + #include + #include + #undef interface +#else + #include + #include + #include +#endif + +//================== +//warning supression +//================== + +//Visual C++ +#if defined(_MSC_VER) + //disable libc "deprecation" warnings + #pragma warning(disable:4996) +#endif + +//================ +//POSIX compliance +//================ + +#if defined(_MSC_VER) + #define PATH_MAX _MAX_PATH + #define va_copy(dest, src) ((dest) = (src)) +#endif + +#if defined(_WIN32) + #define getcwd _getcwd + #define ftruncate _chsize + #define putenv _putenv + #define mkdir(n, m) _wmkdir(nall::utf16_t(n)) + #define rmdir _rmdir + #define vsnprintf _vsnprintf + #define usleep(n) Sleep(n / 1000) +#endif + +//================ +//inline expansion +//================ + +#if defined(__GNUC__) + #define noinline __attribute__((noinline)) + #define inline inline + #define alwaysinline inline __attribute__((always_inline)) +#elif defined(_MSC_VER) + #define noinline __declspec(noinline) + #define inline inline + #define alwaysinline inline __forceinline +#else + #define noinline + #define inline inline + #define alwaysinline inline +#endif + +#endif + diff --git a/supergameboy/nall/priorityqueue.hpp b/supergameboy/nall/priorityqueue.hpp new file mode 100644 index 00000000..7104e791 --- /dev/null +++ b/supergameboy/nall/priorityqueue.hpp @@ -0,0 +1,109 @@ +#ifndef NALL_PRIORITYQUEUE_HPP +#define NALL_PRIORITYQUEUE_HPP + +#include +#include +#include +#include + +namespace nall { + template void priority_queue_nocallback(type_t) {} + + //priority queue implementation using binary min-heap array; + //does not require normalize() function. + //O(1) find (tick) + //O(log n) insert (enqueue) + //O(log n) remove (dequeue) + template class priority_queue { + public: + inline void tick(unsigned ticks) { + basecounter += ticks; + while(heapsize && gte(basecounter, heap[0].counter)) callback(dequeue()); + } + + //counter is relative to current time (eg enqueue(64, ...) fires in 64 ticks); + //counter cannot exceed std::numeric_limits::max() >> 1. + void enqueue(unsigned counter, type_t event) { + unsigned child = heapsize++; + counter += basecounter; + + while(child) { + unsigned parent = (child - 1) >> 1; + if(gte(counter, heap[parent].counter)) break; + + heap[child].counter = heap[parent].counter; + heap[child].event = heap[parent].event; + child = parent; + } + + heap[child].counter = counter; + heap[child].event = event; + } + + type_t dequeue() { + type_t event(heap[0].event); + unsigned parent = 0; + unsigned counter = heap[--heapsize].counter; + + while(true) { + unsigned child = (parent << 1) + 1; + if(child >= heapsize) break; + if(child + 1 < heapsize && gte(heap[child].counter, heap[child + 1].counter)) child++; + if(gte(heap[child].counter, counter)) break; + + heap[parent].counter = heap[child].counter; + heap[parent].event = heap[child].event; + parent = child; + } + + heap[parent].counter = counter; + heap[parent].event = heap[heapsize].event; + return event; + } + + void reset() { + basecounter = 0; + heapsize = 0; + } + + void serialize(serializer &s) { + s.integer(basecounter); + s.integer(heapsize); + for(unsigned n = 0; n < heapcapacity; n++) { + s.integer(heap[n].counter); + s.integer(heap[n].event); + } + } + + priority_queue(unsigned size, function callback_ = &priority_queue_nocallback) + : callback(callback_) { + heap = new heap_t[size]; + heapcapacity = size; + reset(); + } + + ~priority_queue() { + delete[] heap; + } + + priority_queue& operator=(const priority_queue&) = delete; + priority_queue(const priority_queue&) = delete; + + private: + function callback; + unsigned basecounter; + unsigned heapsize; + unsigned heapcapacity; + struct heap_t { + unsigned counter; + type_t event; + } *heap; + + //return true if x is greater than or equal to y + inline bool gte(unsigned x, unsigned y) { + return x - y < (std::numeric_limits::max() >> 1); + } + }; +} + +#endif diff --git a/supergameboy/nall/property.hpp b/supergameboy/nall/property.hpp new file mode 100644 index 00000000..6fd33acd --- /dev/null +++ b/supergameboy/nall/property.hpp @@ -0,0 +1,91 @@ +#ifndef NALL_PROPERTY_HPP +#define NALL_PROPERTY_HPP + +//nall::property implements ownership semantics into container classes +//example: property::readonly implies that only owner has full +//access to type; and all other code has readonly access. +// +//this code relies on extended friend semantics from C++0x to work, as it +//declares a friend class via a template paramter. it also exploits a bug in +//G++ 4.x to work even in C++98 mode. +// +//if compiling elsewhere, simply remove the friend class and private semantics + +//property can be used either of two ways: +//struct foo { +// property::readonly x; +// property::readwrite y; +//}; +//-or- +//struct foo : property { +// readonly x; +// readwrite y; +//}; + +//return types are const T& (byref) instead fo T (byval) to avoid major speed +//penalties for objects with expensive copy constructors + +//operator-> provides access to underlying object type: +//readonly foo; +//foo->bar(); +//... will call Object::bar(); + +//operator='s reference is constant so as to avoid leaking a reference handle +//that could bypass access restrictions + +//both constant and non-constant operators are provided, though it may be +//necessary to cast first, for instance: +//struct foo : property { readonly bar; } object; +//int main() { int value = const_cast(object); } + +//writeonly is useful for objects that have non-const reads, but const writes. +//however, to avoid leaking handles, the interface is very restricted. the only +//way to write is via operator=, which requires conversion via eg copy +//constructor. example: +//struct foo { +// foo(bool value) { ... } +//}; +//writeonly bar; +//bar = true; + +namespace nall { + template struct property { + template struct traits { typedef T type; }; + + template struct readonly { + const T* operator->() const { return &value; } + const T& operator()() const { return value; } + operator const T&() const { return value; } + private: + T* operator->() { return &value; } + operator T&() { return value; } + const T& operator=(const T& value_) { return value = value_; } + T value; + friend class traits::type; + }; + + template struct writeonly { + void operator=(const T& value_) { value = value_; } + private: + const T* operator->() const { return &value; } + const T& operator()() const { return value; } + operator const T&() const { return value; } + T* operator->() { return &value; } + operator T&() { return value; } + T value; + friend class traits::type; + }; + + template struct readwrite { + const T* operator->() const { return &value; } + const T& operator()() const { return value; } + operator const T&() const { return value; } + T* operator->() { return &value; } + operator T&() { return value; } + const T& operator=(const T& value_) { return value = value_; } + T value; + }; + }; +} + +#endif diff --git a/supergameboy/nall/qt/Makefile b/supergameboy/nall/qt/Makefile new file mode 100644 index 00000000..69e84960 --- /dev/null +++ b/supergameboy/nall/qt/Makefile @@ -0,0 +1,55 @@ +# requires nall/Makefile + +# imports: +# $(qtlibs) -- list of Qt components to link against + +# exports the following symbols: +# $(moc) -- meta-object compiler +# $(rcc) -- resource compiler +# $(qtinc) -- includes for compiling +# $(qtlib) -- libraries for linking + +ifeq ($(moc),) +moc := moc +endif + +ifeq ($(rcc),) +rcc := rcc +endif + +ifeq ($(platform),x) + qtinc := `pkg-config --cflags $(qtlibs)` + qtlib := `pkg-config --libs $(qtlibs)` +else ifeq ($(platform),osx) + qtinc := $(foreach lib,$(qtlibs),-I/Library/Frameworks/$(lib).framework/Versions/4/Headers) + + qtlib := -L/Library/Frameworks + qtlib += $(foreach lib,$(qtlibs),-framework $(lib)) + qtlib += -framework Carbon + qtlib += -framework Cocoa + qtlib += -framework OpenGL + qtlib += -framework AppKit + qtlib += -framework ApplicationServices +else ifeq ($(platform),win) + ifeq ($(qtpath),) + # find Qt install directory from PATH environment variable + qtpath := $(foreach path,$(subst ;, ,$(PATH)),$(if $(wildcard $(path)/$(moc).exe),$(path))) + qtpath := $(strip $(qtpath)) + qtpath := $(subst \,/,$(qtpath)) + qtpath := $(patsubst %/bin,%,$(qtpath)) + endif + + qtinc := -I$(qtpath)/include + qtinc += $(foreach lib,$(qtlibs),-I$(qtpath)/include/$(lib)) + + qtlib := -L$(qtpath)/lib + qtlib += -L$(qtpath)/plugins/imageformats + + qtlib += $(foreach lib,$(qtlibs),-l$(lib)4) + qtlib += -lmingw32 -lqtmain -lcomdlg32 -loleaut32 -limm32 -lwinmm + qtlib += -lwinspool -lmsimg32 -lole32 -ladvapi32 -lws2_32 -luuid -lgdi32 + qtlib += $(foreach lib,$(qtlibs),-l$(lib)4) + + # optional image-file support: + # qtlib += -lqjpeg -lqmng +endif diff --git a/supergameboy/nall/qt/check-action.moc.hpp b/supergameboy/nall/qt/check-action.moc.hpp new file mode 100644 index 00000000..db378fe9 --- /dev/null +++ b/supergameboy/nall/qt/check-action.moc.hpp @@ -0,0 +1,41 @@ +#ifndef NALL_QT_CHECKACTION_HPP +#define NALL_QT_CHECKACTION_HPP + +namespace nall { + +class CheckAction : public QAction { + Q_OBJECT + +public: + bool isChecked() const; + void setChecked(bool); + void toggleChecked(); + CheckAction(const QString&, QObject*); + +protected slots: + +protected: + bool checked; +}; + +inline bool CheckAction::isChecked() const { + return checked; +} + +inline void CheckAction::setChecked(bool checked_) { + checked = checked_; + if(checked) setIcon(QIcon(":/16x16/item-check-on.png")); + else setIcon(QIcon(":/16x16/item-check-off.png")); +} + +inline void CheckAction::toggleChecked() { + setChecked(!isChecked()); +} + +inline CheckAction::CheckAction(const QString &text, QObject *parent) : QAction(text, parent) { + setChecked(false); +} + +} + +#endif diff --git a/supergameboy/nall/qt/concept.hpp b/supergameboy/nall/qt/concept.hpp new file mode 100644 index 00000000..51cacef4 --- /dev/null +++ b/supergameboy/nall/qt/concept.hpp @@ -0,0 +1,10 @@ +#ifndef NALL_QT_CONCEPT_HPP +#define NALL_QT_CONCEPT_HPP + +#include + +namespace nall { + template struct has_count> { enum { value = true }; }; +} + +#endif diff --git a/supergameboy/nall/qt/file-dialog.moc.hpp b/supergameboy/nall/qt/file-dialog.moc.hpp new file mode 100644 index 00000000..6528289b --- /dev/null +++ b/supergameboy/nall/qt/file-dialog.moc.hpp @@ -0,0 +1,392 @@ +#ifndef NALL_QT_FILEDIALOG_HPP +#define NALL_QT_FILEDIALOG_HPP + +#include +#include +#include + +namespace nall { + +class FileDialog; + +class NewFolderDialog : public Window { + Q_OBJECT + +public: + void show(); + NewFolderDialog(FileDialog*); + +protected slots: + void createFolderAction(); + +protected: + FileDialog *parent; + QVBoxLayout *layout; + QLineEdit *folderNameEdit; + QHBoxLayout *controlLayout; + QPushButton *okButton; + QPushButton *cancelButton; +}; + +class FileView : public QListView { + Q_OBJECT + +protected: + void keyPressEvent(QKeyEvent*); + +signals: + void changed(const QModelIndex&); + void browseUp(); + +protected slots: + void currentChanged(const QModelIndex&, const QModelIndex&); +}; + +class FileDialog : public Window { + Q_OBJECT + +public: + void showLoad(); + void showSave(); + void showFolder(); + + void setPath(string path); + void setNameFilters(const string &filters); + FileDialog(); + +signals: + void changed(const string&); + void activated(const string&); + void accepted(const string&); + void rejected(); + +protected slots: + void fileViewChange(const QModelIndex&); + void fileViewActivate(const QModelIndex&); + void pathBoxChanged(); + void filterBoxChanged(); + void createNewFolder(); + void browseUp(); + void acceptAction(); + void rejectAction(); + +protected: + NewFolderDialog *newFolderDialog; + QVBoxLayout *layout; + QHBoxLayout *navigationLayout; + QComboBox *pathBox; + QPushButton *newFolderButton; + QPushButton *upFolderButton; + QHBoxLayout *browseLayout; + QFileSystemModel *fileSystemModel; + FileView *fileView; + QGroupBox *previewFrame; + QLineEdit *fileNameEdit; + QHBoxLayout *controlLayout; + QComboBox *filterBox; + QPushButton *optionsButton; + QPushButton *acceptButton; + QPushButton *rejectButton; + bool lock; + void createFolderAction(const string &name); + void closeEvent(QCloseEvent*); + + friend class NewFolderDialog; +}; + +inline void NewFolderDialog::show() { + folderNameEdit->setText(""); + Window::show(); + folderNameEdit->setFocus(); +} + +inline void NewFolderDialog::createFolderAction() { + string name = folderNameEdit->text().toUtf8().constData(); + if(name == "") { + folderNameEdit->setFocus(); + } else { + parent->createFolderAction(name); + close(); + } +} + +inline NewFolderDialog::NewFolderDialog(FileDialog *fileDialog) : parent(fileDialog) { + setMinimumWidth(240); + setWindowTitle("Create New Folder"); + + layout = new QVBoxLayout; + layout->setAlignment(Qt::AlignTop); + layout->setMargin(5); + layout->setSpacing(5); + setLayout(layout); + + folderNameEdit = new QLineEdit; + layout->addWidget(folderNameEdit); + + controlLayout = new QHBoxLayout; + controlLayout->setAlignment(Qt::AlignRight); + layout->addLayout(controlLayout); + + okButton = new QPushButton("Ok"); + controlLayout->addWidget(okButton); + + cancelButton = new QPushButton("Cancel"); + controlLayout->addWidget(cancelButton); + + connect(folderNameEdit, SIGNAL(returnPressed()), this, SLOT(createFolderAction())); + connect(okButton, SIGNAL(released()), this, SLOT(createFolderAction())); + connect(cancelButton, SIGNAL(released()), this, SLOT(close())); +} + +inline void FileView::currentChanged(const QModelIndex ¤t, const QModelIndex &previous) { + QAbstractItemView::currentChanged(current, previous); + emit changed(current); +} + +inline void FileView::keyPressEvent(QKeyEvent *event) { + //enhance consistency: force OS X to act like Windows and Linux; enter = activate item + if(event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter) { + emit activated(currentIndex()); + return; + } + + //simulate popular file manager behavior; backspace = go up one directory + if(event->key() == Qt::Key_Backspace) { + emit browseUp(); + return; + } + + //fallback: unrecognized keypresses get handled by the widget itself + QListView::keyPressEvent(event); +} + +inline void FileDialog::showLoad() { + acceptButton->setText("Load"); + fileNameEdit->hide(); + filterBox->show(); + show(); +} + +inline void FileDialog::showSave() { + acceptButton->setText("Save"); + fileNameEdit->show(); + filterBox->show(); + show(); +} + +inline void FileDialog::showFolder() { + acceptButton->setText("Choose"); + fileNameEdit->hide(); + filterBox->hide(); + setNameFilters("Folders ()"); + show(); +} + +inline void FileDialog::fileViewChange(const QModelIndex &index) { + string path = fileSystemModel->filePath(index).toUtf8().constData(); + if(path == fileSystemModel->rootPath().toUtf8().constData()) path = ""; + fileNameEdit->setText(notdir(path)); + emit changed(path); +} + +inline void FileDialog::fileViewActivate(const QModelIndex &index) { + string path = fileSystemModel->filePath(index).toUtf8().constData(); + if(fileSystemModel->isDir(index)) { + emit activated(path); + setPath(path); + } else { + emit activated(path); + close(); + } +} + +inline void FileDialog::pathBoxChanged() { + if(lock) return; + setPath(pathBox->currentText().toUtf8().constData()); +} + +inline void FileDialog::filterBoxChanged() { + if(lock) return; + string filters = filterBox->currentText().toUtf8().constData(); + if(filters.length() == 0) { + fileSystemModel->setNameFilters(QStringList() << "*"); + } else { + filters = substr(filters, strpos(filters, "(")()); + ltrim(filters, "("); + rtrim(filters, ")"); + lstring part; + part.split(" ", filters); + QStringList list; + for(unsigned i = 0; i < part.size(); i++) list << part[i]; + fileSystemModel->setNameFilters(list); + } +} + +inline void FileDialog::createNewFolder() { + newFolderDialog->show(); +} + +inline void FileDialog::browseUp() { + if(pathBox->count() > 1) pathBox->setCurrentIndex(1); +} + +inline void FileDialog::setPath(string path) { + lock = true; + newFolderDialog->close(); + + if(QDir(path).exists()) { + newFolderButton->setEnabled(true); + } else { + newFolderButton->setEnabled(false); + path = ""; + } + + fileSystemModel->setRootPath(path); + fileView->setRootIndex(fileSystemModel->index(path)); + fileView->setCurrentIndex(fileView->rootIndex()); + fileView->setFocus(); + + pathBox->clear(); + if(path.length() > 0) { + QDir directory(path); + while(true) { + pathBox->addItem(directory.absolutePath()); + if(directory.isRoot()) break; + directory.cdUp(); + } + } + pathBox->addItem(""); + fileNameEdit->setText(""); + + lock = false; +} + +inline void FileDialog::setNameFilters(const string &filters) { + lock = true; + + lstring list; + list.split("\n", filters); + + filterBox->clear(); + for(unsigned i = 0; i < list.size(); i++) { + filterBox->addItem(list[i]); + } + + lock = false; + filterBoxChanged(); +} + +inline void FileDialog::acceptAction() { + string path = fileSystemModel->rootPath().toUtf8().constData(); + path << "/" << notdir(fileNameEdit->text().toUtf8().constData()); + rtrim(path, "/"); + if(QDir(path).exists()) { + emit accepted(path); + setPath(path); + } else { + emit accepted(path); + close(); + } +} + +inline void FileDialog::rejectAction() { + emit rejected(); + close(); +} + +inline void FileDialog::createFolderAction(const string &name) { + string path = fileSystemModel->rootPath().toUtf8().constData(); + path << "/" << notdir(name); + mkdir(path, 0755); +} + +inline void FileDialog::closeEvent(QCloseEvent *event) { + newFolderDialog->close(); + Window::closeEvent(event); +} + +inline FileDialog::FileDialog() { + newFolderDialog = new NewFolderDialog(this); + resize(640, 360); + + layout = new QVBoxLayout; + layout->setMargin(5); + layout->setSpacing(5); + setLayout(layout); + + navigationLayout = new QHBoxLayout; + layout->addLayout(navigationLayout); + + pathBox = new QComboBox; + pathBox->setEditable(true); + pathBox->setMinimumContentsLength(16); + pathBox->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLength); + pathBox->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); + navigationLayout->addWidget(pathBox); + + newFolderButton = new QPushButton; + newFolderButton->setIconSize(QSize(16, 16)); + newFolderButton->setIcon(QIcon(":/16x16/folder-new.png")); + navigationLayout->addWidget(newFolderButton); + + upFolderButton = new QPushButton; + upFolderButton->setIconSize(QSize(16, 16)); + upFolderButton->setIcon(QIcon(":/16x16/go-up.png")); + navigationLayout->addWidget(upFolderButton); + + browseLayout = new QHBoxLayout; + layout->addLayout(browseLayout); + + fileSystemModel = new QFileSystemModel; + fileSystemModel->setFilter(QDir::AllDirs | QDir::Files | QDir::NoDotAndDotDot); + fileSystemModel->setNameFilterDisables(false); + + fileView = new FileView; + fileView->setMinimumWidth(320); + fileView->setModel(fileSystemModel); + fileView->setIconSize(QSize(16, 16)); + browseLayout->addWidget(fileView); + + previewFrame = new QGroupBox; + previewFrame->hide(); + browseLayout->addWidget(previewFrame); + + fileNameEdit = new QLineEdit; + layout->addWidget(fileNameEdit); + + controlLayout = new QHBoxLayout; + controlLayout->setAlignment(Qt::AlignRight); + layout->addLayout(controlLayout); + + filterBox = new QComboBox; + filterBox->setMinimumContentsLength(16); + filterBox->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLength); + filterBox->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); + controlLayout->addWidget(filterBox); + + optionsButton = new QPushButton("Options"); + optionsButton->hide(); + controlLayout->addWidget(optionsButton); + + acceptButton = new QPushButton("Ok"); + controlLayout->addWidget(acceptButton); + + rejectButton = new QPushButton("Cancel"); + controlLayout->addWidget(rejectButton); + + lock = false; + connect(pathBox, SIGNAL(currentIndexChanged(int)), this, SLOT(pathBoxChanged())); + connect(newFolderButton, SIGNAL(released()), this, SLOT(createNewFolder())); + connect(upFolderButton, SIGNAL(released()), this, SLOT(browseUp())); + connect(fileView, SIGNAL(changed(const QModelIndex&)), this, SLOT(fileViewChange(const QModelIndex&))); + connect(fileView, SIGNAL(activated(const QModelIndex&)), this, SLOT(fileViewActivate(const QModelIndex&))); + connect(fileView, SIGNAL(browseUp()), this, SLOT(browseUp())); + connect(fileNameEdit, SIGNAL(returnPressed()), this, SLOT(acceptAction())); + connect(filterBox, SIGNAL(currentIndexChanged(int)), this, SLOT(filterBoxChanged())); + connect(acceptButton, SIGNAL(released()), this, SLOT(acceptAction())); + connect(rejectButton, SIGNAL(released()), this, SLOT(rejectAction())); +} + +} + +#endif diff --git a/supergameboy/nall/qt/hex-editor.moc.hpp b/supergameboy/nall/qt/hex-editor.moc.hpp new file mode 100644 index 00000000..d59f4be9 --- /dev/null +++ b/supergameboy/nall/qt/hex-editor.moc.hpp @@ -0,0 +1,173 @@ +#ifndef NALL_QT_HEXEDITOR_HPP +#define NALL_QT_HEXEDITOR_HPP + +#include +#include +#include + +namespace nall { + +class HexEditor : public QTextEdit { + Q_OBJECT + +public: + function reader; + function writer; + + void setColumns(unsigned columns); + void setRows(unsigned rows); + void setOffset(unsigned offset); + void setSize(unsigned size); + unsigned lineWidth() const; + void refresh(); + + HexEditor(); + +protected slots: + void scrolled(); + +protected: + QHBoxLayout *layout; + QScrollBar *scrollBar; + unsigned editorColumns; + unsigned editorRows; + unsigned editorOffset; + unsigned editorSize; + bool lock; + + void keyPressEvent(QKeyEvent*); +}; + +inline void HexEditor::keyPressEvent(QKeyEvent *event) { + QTextCursor cursor = textCursor(); + unsigned x = cursor.position() % lineWidth(); + unsigned y = cursor.position() / lineWidth(); + + int hexCode = -1; + switch(event->key()) { + case Qt::Key_0: hexCode = 0; break; + case Qt::Key_1: hexCode = 1; break; + case Qt::Key_2: hexCode = 2; break; + case Qt::Key_3: hexCode = 3; break; + case Qt::Key_4: hexCode = 4; break; + case Qt::Key_5: hexCode = 5; break; + case Qt::Key_6: hexCode = 6; break; + case Qt::Key_7: hexCode = 7; break; + case Qt::Key_8: hexCode = 8; break; + case Qt::Key_9: hexCode = 9; break; + case Qt::Key_A: hexCode = 10; break; + case Qt::Key_B: hexCode = 11; break; + case Qt::Key_C: hexCode = 12; break; + case Qt::Key_D: hexCode = 13; break; + case Qt::Key_E: hexCode = 14; break; + case Qt::Key_F: hexCode = 15; break; + } + + if(cursor.hasSelection() == false && hexCode != -1) { + bool cursorOffsetValid = (x >= 11 && ((x - 11) % 3) != 2); + if(cursorOffsetValid) { + bool nibble = (x - 11) % 3; //0 = top nibble, 1 = bottom nibble + unsigned cursorOffset = y * editorColumns + ((x - 11) / 3); + unsigned effectiveOffset = editorOffset + cursorOffset; + if(effectiveOffset >= editorSize) effectiveOffset %= editorSize; + + uint8_t data = reader ? reader(effectiveOffset) : 0x00; + data &= (nibble == 0 ? 0x0f : 0xf0); + data |= (nibble == 0 ? (hexCode << 4) : (hexCode << 0)); + if(writer) writer(effectiveOffset, data); + refresh(); + + cursor.setPosition(y * lineWidth() + x + 1); //advance cursor + setTextCursor(cursor); + } + } else { + //allow navigation keys to move cursor, but block text input + setTextInteractionFlags(Qt::TextSelectableByKeyboard | Qt::TextSelectableByMouse); + QTextEdit::keyPressEvent(event); + setTextInteractionFlags(Qt::TextEditorInteraction); + } +} + +inline void HexEditor::setColumns(unsigned columns) { + editorColumns = columns; +} + +inline void HexEditor::setRows(unsigned rows) { + editorRows = rows; + scrollBar->setPageStep(editorRows); +} + +inline void HexEditor::setOffset(unsigned offset) { + lock = true; + editorOffset = offset; + scrollBar->setSliderPosition(editorOffset / editorColumns); + lock = false; +} + +inline void HexEditor::setSize(unsigned size) { + editorSize = size; + bool indivisible = (editorSize % editorColumns) != 0; //add one for incomplete row + scrollBar->setRange(0, editorSize / editorColumns + indivisible - editorRows); +} + +inline unsigned HexEditor::lineWidth() const { + return 11 + 3 * editorColumns; +} + +inline void HexEditor::refresh() { + string output; + char temp[256]; + unsigned offset = editorOffset; + + for(unsigned y = 0; y < editorRows; y++) { + if(offset >= editorSize) break; + sprintf(temp, "%.4x:%.4x", (offset >> 16) & 0xffff, (offset >> 0) & 0xffff); + output << "" << temp << "  "; + + for(unsigned x = 0; x < editorColumns; x++) { + if(offset >= editorSize) break; + sprintf(temp, "%.2x", reader ? reader(offset) : 0x00); + offset++; + output << "" << temp << ""; + if(x != (editorColumns - 1)) output << " "; + } + + if(y != (editorRows - 1)) output << "
    "; + } + + setHtml(output); +} + +inline void HexEditor::scrolled() { + if(lock) return; + unsigned offset = scrollBar->sliderPosition(); + editorOffset = offset * editorColumns; + refresh(); +} + +inline HexEditor::HexEditor() { + setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + + layout = new QHBoxLayout; + layout->setAlignment(Qt::AlignRight); + layout->setMargin(0); + layout->setSpacing(0); + setLayout(layout); + + scrollBar = new QScrollBar(Qt::Vertical); + scrollBar->setSingleStep(1); + layout->addWidget(scrollBar); + + lock = false; + connect(scrollBar, SIGNAL(actionTriggered(int)), this, SLOT(scrolled())); + + setColumns(16); + setRows(16); + setSize(0); + setOffset(0); +} + +} + +#endif diff --git a/supergameboy/nall/qt/radio-action.moc.hpp b/supergameboy/nall/qt/radio-action.moc.hpp new file mode 100644 index 00000000..a2bbca48 --- /dev/null +++ b/supergameboy/nall/qt/radio-action.moc.hpp @@ -0,0 +1,41 @@ +#ifndef NALL_QT_RADIOACTION_HPP +#define NALL_QT_RADIOACTION_HPP + +namespace nall { + +class RadioAction : public QAction { + Q_OBJECT + +public: + bool isChecked() const; + void setChecked(bool); + void toggleChecked(); + RadioAction(const QString&, QObject*); + +protected slots: + +protected: + bool checked; +}; + +inline bool RadioAction::isChecked() const { + return checked; +} + +inline void RadioAction::setChecked(bool checked_) { + checked = checked_; + if(checked) setIcon(QIcon(":/16x16/item-radio-on.png")); + else setIcon(QIcon(":/16x16/item-radio-off.png")); +} + +inline void RadioAction::toggleChecked() { + setChecked(!isChecked()); +} + +inline RadioAction::RadioAction(const QString &text, QObject *parent) : QAction(text, parent) { + setChecked(false); +} + +} + +#endif diff --git a/supergameboy/nall/qt/window.moc.hpp b/supergameboy/nall/qt/window.moc.hpp new file mode 100644 index 00000000..0d3bf390 --- /dev/null +++ b/supergameboy/nall/qt/window.moc.hpp @@ -0,0 +1,105 @@ +#ifndef NALL_QT_WINDOW_HPP +#define NALL_QT_WINDOW_HPP + +#include +#include + +namespace nall { + +class Window : public QWidget { + Q_OBJECT + +public: + void setGeometryString(string *geometryString); + void setCloseOnEscape(bool); + void show(); + void hide(); + void shrink(); + + Window(); + +protected slots: + +protected: + string *geometryString; + bool closeOnEscape; + void keyReleaseEvent(QKeyEvent *event); + void closeEvent(QCloseEvent *event); +}; + +inline void Window::setGeometryString(string *geometryString_) { + geometryString = geometryString_; + if(geometryString && isVisible() == false) { + uint8_t *data; + unsigned length; + base64::decode(data, length, *geometryString); + QByteArray array((const char*)data, length); + delete[] data; + restoreGeometry(array); + } +} + +inline void Window::setCloseOnEscape(bool value) { + closeOnEscape = value; +} + +inline void Window::show() { + if(geometryString && isVisible() == false) { + uint8_t *data; + unsigned length; + base64::decode(data, length, *geometryString); + QByteArray array((const char*)data, length); + delete[] data; + restoreGeometry(array); + } + QWidget::show(); + QApplication::processEvents(); + activateWindow(); + raise(); +} + +inline void Window::hide() { + if(geometryString && isVisible() == true) { + char *data; + QByteArray geometry = saveGeometry(); + base64::encode(data, (const uint8_t*)geometry.data(), geometry.length()); + *geometryString = data; + delete[] data; + } + QWidget::hide(); +} + +inline void Window::shrink() { + if(isFullScreen()) return; + + for(unsigned i = 0; i < 2; i++) { + resize(0, 0); + usleep(2000); + QApplication::processEvents(); + } +} + +inline void Window::keyReleaseEvent(QKeyEvent *event) { + if(closeOnEscape && (event->key() == Qt::Key_Escape)) close(); + QWidget::keyReleaseEvent(event); +} + +inline void Window::closeEvent(QCloseEvent *event) { + if(geometryString) { + char *data; + QByteArray geometry = saveGeometry(); + base64::encode(data, (const uint8_t*)geometry.data(), geometry.length()); + *geometryString = data; + delete[] data; + } + QWidget::closeEvent(event); +} + +inline Window::Window() { + geometryString = 0; + closeOnEscape = true; +} + +} + +#endif diff --git a/supergameboy/nall/serial.hpp b/supergameboy/nall/serial.hpp new file mode 100644 index 00000000..6f5cf6d6 --- /dev/null +++ b/supergameboy/nall/serial.hpp @@ -0,0 +1,80 @@ +#ifndef NALL_SERIAL_HPP +#define NALL_SERIAL_HPP + +#include +#include +#include +#include + +#include + +namespace nall { + class serial { + public: + //-1 on error, otherwise return bytes read + int read(uint8_t *data, unsigned length) { + if(port_open == false) return -1; + return ::read(port, (void*)data, length); + } + + //-1 on error, otherwise return bytes written + int write(const uint8_t *data, unsigned length) { + if(port_open == false) return -1; + return ::write(port, (void*)data, length); + } + + bool open(const char *portname, unsigned rate) { + close(); + + port = ::open(portname, O_RDWR | O_NOCTTY | O_NDELAY | O_NONBLOCK); + if(port == -1) return false; + + if(ioctl(port, TIOCEXCL) == -1) { close(); return false; } + if(fcntl(port, F_SETFL, 0) == -1) { close(); return false; } + if(tcgetattr(port, &original_attr) == -1) { close(); return false; } + + termios attr = original_attr; + cfmakeraw(&attr); + cfsetspeed(&attr, rate); + + attr.c_lflag &=~ (ECHO | ECHONL | ISIG | ICANON | IEXTEN); + attr.c_iflag &=~ (BRKINT | PARMRK | INPCK | ISTRIP | INLCR | IGNCR | ICRNL | IXON | IXOFF | IXANY); + attr.c_iflag |= (IGNBRK | IGNPAR); + attr.c_oflag &=~ (OPOST); + attr.c_cflag &=~ (CSIZE | CSTOPB | PARENB); + attr.c_cflag |= (CS8 | CREAD | CLOCAL); + attr.c_cc[VTIME] = attr.c_cc[VMIN] = 0; + + if(tcsetattr(port, TCSANOW, &attr) == -1) { close(); return false; } + return port_open = true; + } + + void close() { + if(port != -1) { + tcdrain(port); + if(port_open == true) { + tcsetattr(port, TCSANOW, &original_attr); + port_open = false; + } + ::close(port); + port = -1; + } + } + + serial() { + port = -1; + port_open = false; + } + + ~serial() { + close(); + } + + private: + int port; + bool port_open; + termios original_attr; + }; +} + +#endif diff --git a/supergameboy/nall/serializer.hpp b/supergameboy/nall/serializer.hpp new file mode 100644 index 00000000..9f816dfe --- /dev/null +++ b/supergameboy/nall/serializer.hpp @@ -0,0 +1,145 @@ +#ifndef NALL_SERIALIZER_HPP +#define NALL_SERIALIZER_HPP + +#include +#include +#include +#include + +namespace nall { + //serializer: a class designed to save and restore the state of classes. + // + //benefits: + //- data() will be portable in size (it is not necessary to specify type sizes.) + //- data() will be portable in endianness (always stored internally as little-endian.) + //- one serialize function can both save and restore class states. + // + //caveats: + //- only plain-old-data can be stored. complex classes must provide serialize(serializer&); + //- floating-point usage is not portable across platforms + + class serializer { + public: + enum mode_t { Load, Save, Size }; + + mode_t mode() const { + return imode; + } + + const uint8_t* data() const { + return idata; + } + + unsigned size() const { + return isize; + } + + unsigned capacity() const { + return icapacity; + } + + template void floatingpoint(T &value) { + enum { size = sizeof(T) }; + //this is rather dangerous, and not cross-platform safe; + //but there is no standardized way to export FP-values + uint8_t *p = (uint8_t*)&value; + if(imode == Save) { + for(unsigned n = 0; n < size; n++) idata[isize++] = p[n]; + } else if(imode == Load) { + for(unsigned n = 0; n < size; n++) p[n] = idata[isize++]; + } else { + isize += size; + } + } + + template void integer(T &value) { + enum { size = std::is_same::value ? 1 : sizeof(T) }; + if(imode == Save) { + for(unsigned n = 0; n < size; n++) idata[isize++] = value >> (n << 3); + } else if(imode == Load) { + value = 0; + for(unsigned n = 0; n < size; n++) value |= idata[isize++] << (n << 3); + } else if(imode == Size) { + isize += size; + } + } + + template void array(T &array) { + enum { size = sizeof(T) / sizeof(typename std::remove_extent::type) }; + for(unsigned n = 0; n < size; n++) integer(array[n]); + } + + template void array(T array, unsigned size) { + for(unsigned n = 0; n < size; n++) integer(array[n]); + } + + //copy + serializer& operator=(const serializer &s) { + if(idata) delete[] idata; + + imode = s.imode; + idata = new uint8_t[s.icapacity]; + isize = s.isize; + icapacity = s.icapacity; + + memcpy(idata, s.idata, s.icapacity); + return *this; + } + + serializer(const serializer &s) : idata(0) { + operator=(s); + } + + //move + serializer& operator=(serializer &&s) { + if(idata) delete[] idata; + + imode = s.imode; + idata = s.idata; + isize = s.isize; + icapacity = s.icapacity; + + s.idata = 0; + return *this; + } + + serializer(serializer &&s) { + operator=(std::move(s)); + } + + //construction + serializer() { + imode = Size; + idata = 0; + isize = 0; + } + + serializer(unsigned capacity) { + imode = Save; + idata = new uint8_t[capacity](); + isize = 0; + icapacity = capacity; + } + + serializer(const uint8_t *data, unsigned capacity) { + imode = Load; + idata = new uint8_t[capacity]; + isize = 0; + icapacity = capacity; + memcpy(idata, data, capacity); + } + + ~serializer() { + if(idata) delete[] idata; + } + + private: + mode_t imode; + uint8_t *idata; + unsigned isize; + unsigned icapacity; + }; + +}; + +#endif diff --git a/supergameboy/nall/sha256.hpp b/supergameboy/nall/sha256.hpp new file mode 100644 index 00000000..7f41f04e --- /dev/null +++ b/supergameboy/nall/sha256.hpp @@ -0,0 +1,143 @@ +#ifndef NALL_SHA256_HPP +#define NALL_SHA256_HPP + +//author: vladitx + +namespace nall { + #define PTR(t, a) ((t*)(a)) + + #define SWAP32(x) ((uint32_t)( \ + (((uint32_t)(x) & 0x000000ff) << 24) | \ + (((uint32_t)(x) & 0x0000ff00) << 8) | \ + (((uint32_t)(x) & 0x00ff0000) >> 8) | \ + (((uint32_t)(x) & 0xff000000) >> 24) \ + )) + + #define ST32(a, d) *PTR(uint32_t, a) = (d) + #define ST32BE(a, d) ST32(a, SWAP32(d)) + + #define LD32(a) *PTR(uint32_t, a) + #define LD32BE(a) SWAP32(LD32(a)) + + #define LSL32(x, n) ((uint32_t)(x) << (n)) + #define LSR32(x, n) ((uint32_t)(x) >> (n)) + #define ROR32(x, n) (LSR32(x, n) | LSL32(x, 32 - (n))) + + //first 32 bits of the fractional parts of the square roots of the first 8 primes 2..19 + static const uint32_t T_H[8] = { + 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19, + }; + + //first 32 bits of the fractional parts of the cube roots of the first 64 primes 2..311 + static const uint32_t T_K[64] = { + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, + 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, + 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, + 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, + 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, + 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2, + }; + + struct sha256_ctx { + uint8_t in[64]; + unsigned inlen; + + uint32_t w[64]; + uint32_t h[8]; + uint64_t len; + }; + + void sha256_init(sha256_ctx *p) { + memset(p, 0, sizeof(sha256_ctx)); + memcpy(p->h, T_H, sizeof(T_H)); + } + + static void sha256_block(sha256_ctx *p) { + unsigned i; + uint32_t s0, s1; + uint32_t a, b, c, d, e, f, g, h; + uint32_t t1, t2, maj, ch; + + for(i = 0; i < 16; i++) p->w[i] = LD32BE(p->in + i * 4); + + for(i = 16; i < 64; i++) { + s0 = ROR32(p->w[i - 15], 7) ^ ROR32(p->w[i - 15], 18) ^ LSR32(p->w[i - 15], 3); + s1 = ROR32(p->w[i - 2], 17) ^ ROR32(p->w[i - 2], 19) ^ LSR32(p->w[i - 2], 10); + p->w[i] = p->w[i - 16] + s0 + p->w[i - 7] + s1; + } + + a = p->h[0]; b = p->h[1]; c = p->h[2]; d = p->h[3]; + e = p->h[4]; f = p->h[5]; g = p->h[6]; h = p->h[7]; + + for(i = 0; i < 64; i++) { + s0 = ROR32(a, 2) ^ ROR32(a, 13) ^ ROR32(a, 22); + maj = (a & b) ^ (a & c) ^ (b & c); + t2 = s0 + maj; + s1 = ROR32(e, 6) ^ ROR32(e, 11) ^ ROR32(e, 25); + ch = (e & f) ^ (~e & g); + t1 = h + s1 + ch + T_K[i] + p->w[i]; + + h = g; g = f; f = e; e = d + t1; + d = c; c = b; b = a; a = t1 + t2; + } + + p->h[0] += a; p->h[1] += b; p->h[2] += c; p->h[3] += d; + p->h[4] += e; p->h[5] += f; p->h[6] += g; p->h[7] += h; + + //next block + p->inlen = 0; + } + + void sha256_chunk(sha256_ctx *p, const uint8_t *s, unsigned len) { + unsigned l; + p->len += len; + + while(len) { + l = 64 - p->inlen; + l = (len < l) ? len : l; + + memcpy(p->in + p->inlen, s, l); + s += l; + p->inlen += l; + len -= l; + + if(p->inlen == 64) sha256_block(p); + } + } + + void sha256_final(sha256_ctx *p) { + uint64_t len; + p->in[p->inlen++] = 0x80; + + if(p->inlen > 56) { + memset(p->in + p->inlen, 0, 64 - p->inlen); + sha256_block(p); + } + + memset(p->in + p->inlen, 0, 56 - p->inlen); + + len = p->len << 3; + ST32BE(p->in + 56, len >> 32); + ST32BE(p->in + 60, len); + sha256_block(p); + } + + void sha256_hash(sha256_ctx *p, uint8_t *s) { + uint32_t *t = (uint32_t*)s; + for(unsigned i = 0; i < 8; i++) ST32BE(t++, p->h[i]); + } + + #undef PTR + #undef SWAP32 + #undef ST32 + #undef ST32BE + #undef LD32 + #undef LD32BE + #undef LSL32 + #undef LSR32 + #undef ROR32 +} + +#endif diff --git a/supergameboy/nall/sort.hpp b/supergameboy/nall/sort.hpp new file mode 100644 index 00000000..23c317a5 --- /dev/null +++ b/supergameboy/nall/sort.hpp @@ -0,0 +1,62 @@ +#ifndef NALL_SORT_HPP +#define NALL_SORT_HPP + +#include + +//class: merge sort +//average: O(n log n) +//worst: O(n log n) +//memory: O(n) +//stack: O(log n) +//stable?: yes + +//notes: +//there are two primary reasons for choosing merge sort +//over the (usually) faster quick sort*: +//1: it is a stable sort. +//2: it lacks O(n^2) worst-case overhead. +//(* which is also O(n log n) in the average case.) + +namespace nall { + template + void sort(T list[], unsigned length) { + if(length <= 1) return; //nothing to sort + + //use insertion sort to quickly sort smaller blocks + if(length < 64) { + for(unsigned i = 0; i < length; i++) { + unsigned min = i; + for(unsigned j = i + 1; j < length; j++) { + if(list[j] < list[min]) min = j; + } + if(min != i) swap(list[i], list[min]); + } + return; + } + + //split list in half and recursively sort both + unsigned middle = length / 2; + sort(list, middle); + sort(list + middle, length - middle); + + //left and right are sorted here; perform merge sort + T *buffer = new T[length]; + unsigned offset = 0; + unsigned left = 0; + unsigned right = middle; + while(left < middle && right < length) { + if(list[left] < list[right]) { + buffer[offset++] = list[left++]; + } else { + buffer[offset++] = list[right++]; + } + } + while(left < middle) buffer[offset++] = list[left++]; + while(right < length) buffer[offset++] = list[right++]; + + for(unsigned i = 0; i < length; i++) list[i] = buffer[i]; + delete[] buffer; + } +} + +#endif diff --git a/supergameboy/nall/static.hpp b/supergameboy/nall/static.hpp new file mode 100644 index 00000000..4acb9fd0 --- /dev/null +++ b/supergameboy/nall/static.hpp @@ -0,0 +1,20 @@ +#ifndef NALL_STATIC_HPP +#define NALL_STATIC_HPP + +namespace nall { + template struct static_if { typedef T type; }; + template struct static_if { typedef F type; }; + template struct mp_static_if { typedef typename static_if::type type; }; + + template struct static_and { enum { value = false }; }; + template<> struct static_and { enum { value = true }; }; + template struct mp_static_and { enum { value = static_and::value }; }; + + template struct static_or { enum { value = false }; }; + template<> struct static_or { enum { value = true }; }; + template<> struct static_or { enum { value = true }; }; + template<> struct static_or { enum { value = true }; }; + template struct mp_static_or { enum { value = static_or::value }; }; +} + +#endif diff --git a/supergameboy/nall/stdint.hpp b/supergameboy/nall/stdint.hpp new file mode 100644 index 00000000..d8b6c788 --- /dev/null +++ b/supergameboy/nall/stdint.hpp @@ -0,0 +1,44 @@ +#ifndef NALL_STDINT_HPP +#define NALL_STDINT_HPP + +#include + +#if defined(_MSC_VER) + typedef signed char int8_t; + typedef signed short int16_t; + typedef signed int int32_t; + typedef signed long long int64_t; + typedef int64_t intmax_t; + #if defined(_WIN64) + typedef int64_t intptr_t; + #else + typedef int32_t intptr_t; + #endif + + typedef unsigned char uint8_t; + typedef unsigned short uint16_t; + typedef unsigned int uint32_t; + typedef unsigned long long uint64_t; + typedef uint64_t uintmax_t; + #if defined(_WIN64) + typedef uint64_t uintptr_t; + #else + typedef uint32_t uintptr_t; + #endif +#else + #include +#endif + +namespace nall { + static_assert(sizeof(int8_t) == 1, "int8_t is not of the correct size" ); + static_assert(sizeof(int16_t) == 2, "int16_t is not of the correct size"); + static_assert(sizeof(int32_t) == 4, "int32_t is not of the correct size"); + static_assert(sizeof(int64_t) == 8, "int64_t is not of the correct size"); + + static_assert(sizeof(uint8_t) == 1, "int8_t is not of the correct size" ); + static_assert(sizeof(uint16_t) == 2, "int16_t is not of the correct size"); + static_assert(sizeof(uint32_t) == 4, "int32_t is not of the correct size"); + static_assert(sizeof(uint64_t) == 8, "int64_t is not of the correct size"); +} + +#endif diff --git a/supergameboy/nall/string.hpp b/supergameboy/nall/string.hpp new file mode 100644 index 00000000..3ff0392c --- /dev/null +++ b/supergameboy/nall/string.hpp @@ -0,0 +1,27 @@ +#ifndef NALL_STRING_HPP +#define NALL_STRING_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace nall { + template<> struct has_length { enum { value = true }; }; + template<> struct has_size { enum { value = true }; }; +} + +#endif diff --git a/supergameboy/nall/string/base.hpp b/supergameboy/nall/string/base.hpp new file mode 100644 index 00000000..40d0e98c --- /dev/null +++ b/supergameboy/nall/string/base.hpp @@ -0,0 +1,136 @@ +#ifndef NALL_STRING_BASE_HPP +#define NALL_STRING_BASE_HPP + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace nall { + class string; + template inline string to_string(T); + + class string { + public: + inline void reserve(unsigned); + inline unsigned length() const; + + inline string& assign(const char*); + inline string& append(const char*); + template inline string& operator= (T value); + template inline string& operator<<(T value); + + inline operator const char*() const; + inline char* operator()(); + inline char& operator[](int); + + inline bool operator==(const char*) const; + inline bool operator!=(const char*) const; + inline bool operator< (const char*) const; + inline bool operator<=(const char*) const; + inline bool operator> (const char*) const; + inline bool operator>=(const char*) const; + + inline string& operator=(const string&); + inline string& operator=(string&&); + + inline string(); + inline string(const char*); + inline string(const string&); + inline string(string&&); + inline ~string(); + + inline bool readfile(const char*); + inline string& replace (const char*, const char*); + inline string& qreplace(const char*, const char*); + + protected: + char *data; + unsigned size; + + #if defined(QT_CORE_LIB) + public: + inline operator QString() const; + #endif + }; + + class lstring : public linear_vector { + public: + template inline lstring& operator<<(T value); + + inline int find(const char*); + inline void split (const char*, const char*, unsigned = 0); + inline void qsplit(const char*, const char*, unsigned = 0); + + lstring(); + lstring(std::initializer_list); + }; + + //compare.hpp + inline char chrlower(char c); + inline char chrupper(char c); + inline int stricmp(const char *dest, const char *src); + inline bool strbegin (const char *str, const char *key); + inline bool stribegin(const char *str, const char *key); + inline bool strend (const char *str, const char *key); + inline bool striend(const char *str, const char *key); + + //convert.hpp + inline char* strlower(char *str); + inline char* strupper(char *str); + inline char* strtr(char *dest, const char *before, const char *after); + inline uintmax_t strhex (const char *str); + inline intmax_t strsigned (const char *str); + inline uintmax_t strunsigned(const char *str); + inline uintmax_t strbin (const char *str); + inline double strdouble (const char *str); + + //match.hpp + inline bool match(const char *pattern, const char *str); + + //math.hpp + inline bool strint (const char *str, int &result); + inline bool strmath(const char *str, int &result); + + //strl.hpp + inline unsigned strlcpy(char *dest, const char *src, unsigned length); + inline unsigned strlcat(char *dest, const char *src, unsigned length); + + //trim.hpp + inline char* ltrim(char *str, const char *key = " "); + inline char* rtrim(char *str, const char *key = " "); + inline char* trim (char *str, const char *key = " "); + inline char* ltrim_once(char *str, const char *key = " "); + inline char* rtrim_once(char *str, const char *key = " "); + inline char* trim_once (char *str, const char *key = " "); + + //utility.hpp + inline unsigned strlcpy(string &dest, const char *src, unsigned length); + inline unsigned strlcat(string &dest, const char *src, unsigned length); + inline string substr(const char *src, unsigned start = 0, unsigned length = 0); + inline string& strlower(string &str); + inline string& strupper(string &str); + inline string& strtr(string &dest, const char *before, const char *after); + inline string& ltrim(string &str, const char *key = " "); + inline string& rtrim(string &str, const char *key = " "); + inline string& trim (string &str, const char *key = " "); + inline string& ltrim_once(string &str, const char *key = " "); + inline string& rtrim_once(string &str, const char *key = " "); + inline string& trim_once (string &str, const char *key = " "); + template inline string strhex(uintmax_t value); + template inline string strsigned(intmax_t value); + template inline string strunsigned(uintmax_t value); + template inline string strbin(uintmax_t value); + inline unsigned strdouble(char *str, double value); + inline string strdouble(double value); + + //variadic.hpp + template inline string sprint(Args... args); + template inline void print(Args... args); +}; + +#endif diff --git a/supergameboy/nall/string/cast.hpp b/supergameboy/nall/string/cast.hpp new file mode 100644 index 00000000..7b48eda0 --- /dev/null +++ b/supergameboy/nall/string/cast.hpp @@ -0,0 +1,32 @@ +#ifndef NALL_STRING_CAST_HPP +#define NALL_STRING_CAST_HPP + +namespace nall { + +//this is needed, as C++0x does not support explicit template specialization inside classes +template<> inline string to_string (bool v) { return v ? "true" : "false"; } +template<> inline string to_string (signed int v) { return strsigned(v); } +template<> inline string to_string (unsigned int v) { return strunsigned(v); } +template<> inline string to_string (double v) { return strdouble(v); } +template<> inline string to_string (char *v) { return v; } +template<> inline string to_string (const char *v) { return v; } +template<> inline string to_string (string v) { return v; } +template<> inline string to_string(const string &v) { return v; } + +template string& string::operator= (T value) { return assign(to_string(value)); } +template string& string::operator<<(T value) { return append(to_string(value)); } + +template lstring& lstring::operator<<(T value) { + operator[](size()).assign(to_string(value)); + return *this; +} + +#if defined(QT_CORE_LIB) +template<> inline string to_string(QString v) { return v.toUtf8().constData(); } +template<> inline string to_string(const QString &v) { return v.toUtf8().constData(); } +string::operator QString() const { return QString::fromUtf8(*this); } +#endif + +} + +#endif diff --git a/supergameboy/nall/string/compare.hpp b/supergameboy/nall/string/compare.hpp new file mode 100644 index 00000000..bd289753 --- /dev/null +++ b/supergameboy/nall/string/compare.hpp @@ -0,0 +1,72 @@ +#ifndef NALL_STRING_COMPARE_HPP +#define NALL_STRING_COMPARE_HPP + +namespace nall { + +char chrlower(char c) { + return (c >= 'A' && c <= 'Z') ? c + ('a' - 'A') : c; +} + +char chrupper(char c) { + return (c >= 'a' && c <= 'z') ? c - ('a' - 'A') : c; +} + +int stricmp(const char *dest, const char *src) { + while(*dest) { + if(chrlower(*dest) != chrlower(*src)) break; + dest++; + src++; + } + + return (int)chrlower(*dest) - (int)chrlower(*src); +} + +bool strbegin(const char *str, const char *key) { + int i, ssl = strlen(str), ksl = strlen(key); + + if(ksl > ssl) return false; + return (!memcmp(str, key, ksl)); +} + +bool stribegin(const char *str, const char *key) { + int ssl = strlen(str), ksl = strlen(key); + + if(ksl > ssl) return false; + for(int i = 0; i < ksl; i++) { + if(str[i] >= 'A' && str[i] <= 'Z') { + if(str[i] != key[i] && str[i]+0x20 != key[i])return false; + } else if(str[i] >= 'a' && str[i] <= 'z') { + if(str[i] != key[i] && str[i]-0x20 != key[i])return false; + } else { + if(str[i] != key[i])return false; + } + } + return true; +} + +bool strend(const char *str, const char *key) { + int ssl = strlen(str), ksl = strlen(key); + + if(ksl > ssl) return false; + return (!memcmp(str + ssl - ksl, key, ksl)); +} + +bool striend(const char *str, const char *key) { + int ssl = strlen(str), ksl = strlen(key); + + if(ksl > ssl) return false; + for(int i = ssl - ksl, z = 0; i < ssl; i++, z++) { + if(str[i] >= 'A' && str[i] <= 'Z') { + if(str[i] != key[z] && str[i]+0x20 != key[z])return false; + } else if(str[i] >= 'a' && str[i] <= 'z') { + if(str[i] != key[z] && str[i]-0x20 != key[z])return false; + } else { + if(str[i] != key[z])return false; + } + } + return true; +} + +} + +#endif diff --git a/supergameboy/nall/string/convert.hpp b/supergameboy/nall/string/convert.hpp new file mode 100644 index 00000000..3ff134be --- /dev/null +++ b/supergameboy/nall/string/convert.hpp @@ -0,0 +1,153 @@ +#ifndef NALL_STRING_CONVERT_HPP +#define NALL_STRING_CONVERT_HPP + +namespace nall { + +char* strlower(char *str) { + if(!str) return 0; + int i = 0; + while(str[i]) { + str[i] = chrlower(str[i]); + i++; + } + return str; +} + +char* strupper(char *str) { + if(!str) return 0; + int i = 0; + while(str[i]) { + str[i] = chrupper(str[i]); + i++; + } + return str; +} + +char* strtr(char *dest, const char *before, const char *after) { + if(!dest || !before || !after) return dest; + int sl = strlen(dest), bsl = strlen(before), asl = strlen(after); + + if(bsl != asl || bsl == 0) return dest; //patterns must be the same length for 1:1 replace + for(unsigned i = 0; i < sl; i++) { + for(unsigned l = 0; l < bsl; l++) { + if(dest[i] == before[l]) { + dest[i] = after[l]; + break; + } + } + } + + return dest; +} + +uintmax_t strhex(const char *str) { + if(!str) return 0; + uintmax_t result = 0; + + //skip hex identifiers 0x and $, if present + if(*str == '0' && (*(str + 1) == 'X' || *(str + 1) == 'x')) str += 2; + else if(*str == '$') str++; + + while(*str) { + uint8_t x = *str++; + if(x >= '0' && x <= '9') x -= '0'; + else if(x >= 'A' && x <= 'F') x -= 'A' - 10; + else if(x >= 'a' && x <= 'f') x -= 'a' - 10; + else break; //stop at first invalid character + result = result * 16 + x; + } + + return result; +} + +intmax_t strsigned(const char *str) { + if(!str) return 0; + intmax_t result = 0; + bool negate = false; + + //check for negation + if(*str == '-') { + negate = true; + str++; + } + + while(*str) { + uint8_t x = *str++; + if(x >= '0' && x <= '9') x -= '0'; + else break; //stop at first invalid character + result = result * 10 + x; + } + + return !negate ? result : -result; +} + +uintmax_t strunsigned(const char *str) { + if(!str) return 0; + uintmax_t result = 0; + + while(*str) { + uint8_t x = *str++; + if(x >= '0' && x <= '9') x -= '0'; + else break; //stop at first invalid character + result = result * 10 + x; + } + + return result; +} + +uintmax_t strbin(const char *str) { + if(!str) return 0; + uintmax_t result = 0; + + //skip bin identifiers 0b and %, if present + if(*str == '0' && (*(str + 1) == 'B' || *(str + 1) == 'b')) str += 2; + else if(*str == '%') str++; + + while(*str) { + uint8_t x = *str++; + if(x == '0' || x == '1') x -= '0'; + else break; //stop at first invalid character + result = result * 2 + x; + } + + return result; +} + +double strdouble(const char *str) { + if(!str) return 0.0; + bool negate = false; + + //check for negation + if(*str == '-') { + negate = true; + str++; + } + + intmax_t result_integral = 0; + while(*str) { + uint8_t x = *str++; + if(x >= '0' && x <= '9') x -= '0'; + else if(x == '.') break; //break loop and read fractional part + else return (double)result_integral; //invalid value, assume no fractional part + result_integral = result_integral * 10 + x; + } + + intmax_t result_fractional = 0; + while(*str) { + uint8_t x = *str++; + if(x >= '0' && x <= '9') x -= '0'; + else break; //stop at first invalid character + result_fractional = result_fractional * 10 + x; + } + + //calculate fractional portion + double result = (double)result_fractional; + while((uintmax_t)result > 0) result /= 10.0; + result += (double)result_integral; + + return !negate ? result : -result; +} + +} + +#endif diff --git a/supergameboy/nall/string/core.hpp b/supergameboy/nall/string/core.hpp new file mode 100644 index 00000000..f69802e4 --- /dev/null +++ b/supergameboy/nall/string/core.hpp @@ -0,0 +1,133 @@ +#ifndef NALL_STRING_CORE_HPP +#define NALL_STRING_CORE_HPP + +namespace nall { + +void string::reserve(unsigned size_) { + if(size_ > size) { + size = size_; + data = (char*)realloc(data, size + 1); + data[size] = 0; + } +} + +unsigned string::length() const { + return strlen(data); +} + +string& string::assign(const char *s) { + unsigned length = strlen(s); + reserve(length); + strcpy(data, s); + return *this; +} + +string& string::append(const char *s) { + unsigned length = strlen(data) + strlen(s); + reserve(length); + strcat(data, s); + return *this; +} + +string::operator const char*() const { + return data; +} + +char* string::operator()() { + return data; +} + +char& string::operator[](int index) { + reserve(index); + return data[index]; +} + +bool string::operator==(const char *str) const { return strcmp(data, str) == 0; } +bool string::operator!=(const char *str) const { return strcmp(data, str) != 0; } +bool string::operator< (const char *str) const { return strcmp(data, str) < 0; } +bool string::operator<=(const char *str) const { return strcmp(data, str) <= 0; } +bool string::operator> (const char *str) const { return strcmp(data, str) > 0; } +bool string::operator>=(const char *str) const { return strcmp(data, str) >= 0; } + +string& string::operator=(const string &value) { + assign(value); + return *this; +} + +string& string::operator=(string &&source) { + if(data) free(data); + size = source.size; + data = source.data; + source.data = 0; + source.size = 0; + return *this; +} + +string::string() { + size = 64; + data = (char*)malloc(size + 1); + *data = 0; +} + +string::string(const char *value) { + size = strlen(value); + data = strdup(value); +} + +string::string(const string &value) { + size = strlen(value); + data = strdup(value); +} + +string::string(string &&source) { + size = source.size; + data = source.data; + source.data = 0; +} + +string::~string() { + free(data); +} + +bool string::readfile(const char *filename) { + assign(""); + + #if !defined(_WIN32) + FILE *fp = fopen(filename, "rb"); + #else + FILE *fp = _wfopen(utf16_t(filename), L"rb"); + #endif + if(!fp) return false; + + fseek(fp, 0, SEEK_END); + unsigned size = ftell(fp); + rewind(fp); + char *fdata = new char[size + 1]; + unsigned unused = fread(fdata, 1, size, fp); + fclose(fp); + fdata[size] = 0; + assign(fdata); + delete[] fdata; + + return true; +} + +int lstring::find(const char *key) { + for(unsigned i = 0; i < size(); i++) { + if(operator[](i) == key) return i; + } + return -1; +} + +inline lstring::lstring() { +} + +inline lstring::lstring(std::initializer_list list) { + for(const string *s = list.begin(); s != list.end(); ++s) { + operator<<(*s); + } +} + +} + +#endif diff --git a/supergameboy/nall/string/filename.hpp b/supergameboy/nall/string/filename.hpp new file mode 100644 index 00000000..f3750760 --- /dev/null +++ b/supergameboy/nall/string/filename.hpp @@ -0,0 +1,61 @@ +#ifndef NALL_FILENAME_HPP +#define NALL_FILENAME_HPP + +namespace nall { + +// "foo/bar.c" -> "foo/", "bar.c" -> "./" +inline string dir(char const *name) { + string result = name; + for(signed i = strlen(result); i >= 0; i--) { + if(result[i] == '/' || result[i] == '\\') { + result[i + 1] = 0; + break; + } + if(i == 0) result = "./"; + } + return result; +} + +// "foo/bar.c" -> "bar.c" +inline string notdir(char const *name) { + for(signed i = strlen(name); i >= 0; i--) { + if(name[i] == '/' || name[i] == '\\') { + name += i + 1; + break; + } + } + string result = name; + return result; +} + +// "foo/bar.c" -> "foo/bar" +inline string basename(char const *name) { + string result = name; + for(signed i = strlen(result); i >= 0; i--) { + if(result[i] == '/' || result[i] == '\\') { + //file has no extension + break; + } + if(result[i] == '.') { + result[i] = 0; + break; + } + } + return result; +} + +// "foo/bar.c" -> "c" +inline string extension(char const *name) { + for(signed i = strlen(name); i >= 0; i--) { + if(name[i] == '.') { + name += i + 1; + break; + } + } + string result = name; + return result; +} + +} + +#endif diff --git a/supergameboy/nall/string/match.hpp b/supergameboy/nall/string/match.hpp new file mode 100644 index 00000000..d8cf702d --- /dev/null +++ b/supergameboy/nall/string/match.hpp @@ -0,0 +1,76 @@ +#ifndef NALL_STRING_MATCH_HPP +#define NALL_STRING_MATCH_HPP + +namespace nall { + +bool match(const char *p, const char *s) { + const char *p_ = 0, *s_ = 0; + + for(;;) { + if(!*s) { + while(*p == '*') p++; + return !*p; + } + + //wildcard match + if(*p == '*') { + p_ = p++, s_ = s; + continue; + } + + //any match + if(*p == '?') { + p++, s++; + continue; + } + + //ranged match + if(*p == '{') { + #define pattern(name_, rule_) \ + if(strbegin(p, name_)) { \ + if(rule_) { \ + p += sizeof(name_) - 1, s++; \ + continue; \ + } \ + goto failure; \ + } + + pattern("{alpha}", (*s >= 'A' && *s <= 'Z') || (*s >= 'a' && *s <= 'z')) + pattern("{alphanumeric}", (*s >= 'A' && *s <= 'Z') || (*s >= 'a' && *s <= 'z') || (*s >= '0' && *s <= '9')) + pattern("{binary}", (*s == '0' || *s == '1')) + pattern("{hex}", (*s >= '0' && *s <= '9') || (*s >= 'A' && *s <= 'F') || (*s >= 'a' && *s <= 'f')) + pattern("{lowercase}", (*s >= 'a' && *s <= 'z')) + pattern("{numeric}", (*s >= '0' && *s <= '9')) + pattern("{uppercase}", (*s >= 'A' && *s <= 'Z')) + pattern("{whitespace}", (*s == ' ' || *s == '\t')) + + #undef pattern + goto failure; + } + + //reserved character match + if(*p == '\\') { + p++; + //fallthrough + } + + //literal match + if(*p == *s) { + p++, *s++; + continue; + } + + //attempt wildcard rematch + failure: + if(p_) { + p = p_, s = s_ + 1; + continue; + } + + return false; + } +} + +} + +#endif diff --git a/supergameboy/nall/string/math.hpp b/supergameboy/nall/string/math.hpp new file mode 100644 index 00000000..ea8b99c8 --- /dev/null +++ b/supergameboy/nall/string/math.hpp @@ -0,0 +1,164 @@ +#ifndef NALL_STRING_MATH_HPP +#define NALL_STRING_MATH_HPP + +namespace nall { + +static int eval_integer(const char *&s) { + if(!*s) throw "unrecognized_integer"; + int value = 0, x = *s, y = *(s + 1); + + //hexadecimal + if(x == '0' && (y == 'X' || y == 'x')) { + s += 2; + while(true) { + if(*s >= '0' && *s <= '9') { value = value * 16 + (*s++ - '0'); continue; } + if(*s >= 'A' && *s <= 'F') { value = value * 16 + (*s++ - 'A' + 10); continue; } + if(*s >= 'a' && *s <= 'f') { value = value * 16 + (*s++ - 'a' + 10); continue; } + return value; + } + } + + //binary + if(x == '0' && (y == 'B' || y == 'b')) { + s += 2; + while(true) { + if(*s == '0' || *s == '1') { value = value * 2 + (*s++ - '0'); continue; } + return value; + } + } + + //octal (or decimal '0') + if(x == '0') { + s += 1; + while(true) { + if(*s >= '0' && *s <= '7') { value = value * 8 + (*s++ - '0'); continue; } + return value; + } + } + + //decimal + if(x >= '0' && x <= '9') { + while(true) { + if(*s >= '0' && *s <= '9') { value = value * 10 + (*s++ - '0'); continue; } + return value; + } + } + + //char + if(x == '\'' && y != '\'') { + s += 1; + while(true) { + value = value * 256 + *s++; + if(*s == '\'') { s += 1; return value; } + if(!*s) throw "mismatched_char"; + } + } + + throw "unrecognized_integer"; +} + +static int eval(const char *&s, int depth = 0) { + while(*s == ' ' || *s == '\t') s++; //trim whitespace + if(!*s) throw "unrecognized_token"; + int value = 0, x = *s, y = *(s + 1); + + if(*s == '(') { + value = eval(++s, 1); + if(*s++ != ')') throw "mismatched_group"; + } + + else if(x == '!') value = !eval(++s, 13); + else if(x == '~') value = ~eval(++s, 13); + else if(x == '+') value = +eval(++s, 13); + else if(x == '-') value = -eval(++s, 13); + + else if((x >= '0' && x <= '9') || x == '\'') value = eval_integer(s); + + else throw "unrecognized_token"; + + while(true) { + while(*s == ' ' || *s == '\t') s++; //trim whitespace + if(!*s) break; + x = *s, y = *(s + 1); + + if(depth >= 13) break; + if(x == '*') { value *= eval(++s, 13); continue; } + if(x == '/') { value /= eval(++s, 13); continue; } + if(x == '%') { value %= eval(++s, 13); continue; } + + if(depth >= 12) break; + if(x == '+') { value += eval(++s, 12); continue; } + if(x == '-') { value -= eval(++s, 12); continue; } + + if(depth >= 11) break; + if(x == '<' && y == '<') { value <<= eval(++++s, 11); continue; } + if(x == '>' && y == '>') { value >>= eval(++++s, 11); continue; } + + if(depth >= 10) break; + if(x == '<' && y == '=') { value = value <= eval(++++s, 10); continue; } + if(x == '>' && y == '=') { value = value >= eval(++++s, 10); continue; } + if(x == '<') { value = value < eval(++s, 10); continue; } + if(x == '>') { value = value > eval(++s, 10); continue; } + + if(depth >= 9) break; + if(x == '=' && y == '=') { value = value == eval(++++s, 9); continue; } + if(x == '!' && y == '=') { value = value != eval(++++s, 9); continue; } + + if(depth >= 8) break; + if(x == '&' && y != '&') { value = value & eval(++s, 8); continue; } + + if(depth >= 7) break; + if(x == '^' && y != '^') { value = value ^ eval(++s, 7); continue; } + + if(depth >= 6) break; + if(x == '|' && y != '|') { value = value | eval(++s, 6); continue; } + + if(depth >= 5) break; + if(x == '&' && y == '&') { value = eval(++++s, 5) && value; continue; } + + if(depth >= 4) break; + if(x == '^' && y == '^') { value = (!eval(++++s, 4) != !value); continue; } + + if(depth >= 3) break; + if(x == '|' && y == '|') { value = eval(++++s, 3) || value; continue; } + + if(x == '?') { + int lhs = eval(++s, 2); + if(*s != ':') throw "mismatched_ternary"; + int rhs = eval(++s, 2); + value = value ? lhs : rhs; + continue; + } + if(depth >= 2) break; + + if(depth > 0 && x == ')') break; + + throw "unrecognized_token"; + } + + return value; +} + +bool strint(const char *s, int &result) { + try { + result = eval_integer(s); + return true; + } catch(const char*) { + result = 0; + return false; + } +} + +bool strmath(const char *s, int &result) { + try { + result = eval(s); + return true; + } catch(const char*) { + result = 0; + return false; + } +} + +} + +#endif diff --git a/supergameboy/nall/string/replace.hpp b/supergameboy/nall/string/replace.hpp new file mode 100644 index 00000000..db405a9b --- /dev/null +++ b/supergameboy/nall/string/replace.hpp @@ -0,0 +1,103 @@ +#ifndef NALL_STRING_REPLACE_HPP +#define NALL_STRING_REPLACE_HPP + +namespace nall { + +string& string::replace(const char *key, const char *token) { + int i, z, ksl = strlen(key), tsl = strlen(token), ssl = length(); + unsigned int replace_count = 0, size = ssl; + char *buffer; + + if(ksl <= ssl) { + if(tsl > ksl) { //the new string may be longer than the old string... + for(i = 0; i <= ssl - ksl;) { //so let's find out how big of a string we'll need... + if(!memcmp(data + i, key, ksl)) { + replace_count++; + i += ksl; + } else i++; + } + size = ssl + ((tsl - ksl) * replace_count); + reserve(size); + } + + buffer = new char[size + 1]; + for(i = z = 0; i < ssl;) { + if(i <= ssl - ksl) { + if(!memcmp(data + i, key, ksl)) { + memcpy(buffer + z, token, tsl); + z += tsl; + i += ksl; + } else buffer[z++] = data[i++]; + } else buffer[z++] = data[i++]; + } + buffer[z] = 0; + + assign(buffer); + delete[] buffer; + } + + return *this; +} + +string& string::qreplace(const char *key, const char *token) { + int i, l, z, ksl = strlen(key), tsl = strlen(token), ssl = length(); + unsigned int replace_count = 0, size = ssl; + uint8_t x; + char *buffer; + + if(ksl <= ssl) { + if(tsl > ksl) { + for(i = 0; i <= ssl - ksl;) { + x = data[i]; + if(x == '\"' || x == '\'') { + l = i; + i++; + while(data[i++] != x) { + if(i == ssl) { + i = l; + break; + } + } + } + if(!memcmp(data + i, key, ksl)) { + replace_count++; + i += ksl; + } else i++; + } + size = ssl + ((tsl - ksl) * replace_count); + reserve(size); + } + + buffer = new char[size + 1]; + for(i = z = 0; i < ssl;) { + x = data[i]; + if(x == '\"' || x == '\'') { + l = i++; + while(data[i] != x && i < ssl)i++; + if(i >= ssl)i = l; + else { + memcpy(buffer + z, data + l, i - l); + z += i - l; + } + } + if(i <= ssl - ksl) { + if(!memcmp(data + i, key, ksl)) { + memcpy(buffer + z, token, tsl); + z += tsl; + i += ksl; + replace_count++; + } else buffer[z++] = data[i++]; + } else buffer[z++] = data[i++]; + } + buffer[z] = 0; + + assign(buffer); + delete[] buffer; + } + + return *this; +} + +}; + +#endif diff --git a/supergameboy/nall/string/split.hpp b/supergameboy/nall/string/split.hpp new file mode 100644 index 00000000..bb77dfcd --- /dev/null +++ b/supergameboy/nall/string/split.hpp @@ -0,0 +1,56 @@ +#ifndef NALL_STRING_SPLIT_HPP +#define NALL_STRING_SPLIT_HPP + +namespace nall { + +void lstring::split(const char *key, const char *src, unsigned limit) { + reset(); + + int ssl = strlen(src), ksl = strlen(key); + int lp = 0, split_count = 0; + + for(int i = 0; i <= ssl - ksl;) { + if(!memcmp(src + i, key, ksl)) { + strlcpy(operator[](split_count++), src + lp, i - lp + 1); + i += ksl; + lp = i; + if(!--limit) break; + } else i++; + } + + operator[](split_count++) = src + lp; +} + +void lstring::qsplit(const char *key, const char *src, unsigned limit) { + reset(); + + int ssl = strlen(src), ksl = strlen(key); + int lp = 0, split_count = 0; + + for(int i = 0; i <= ssl - ksl;) { + uint8_t x = src[i]; + + if(x == '\"' || x == '\'') { + int z = i++; //skip opening quote + while(i < ssl && src[i] != x) i++; + if(i >= ssl) i = z; //failed match, rewind i + else { + i++; //skip closing quote + continue; //restart in case next char is also a quote + } + } + + if(!memcmp(src + i, key, ksl)) { + strlcpy(operator[](split_count++), src + lp, i - lp + 1); + i += ksl; + lp = i; + if(!--limit) break; + } else i++; + } + + operator[](split_count++) = src + lp; +} + +}; + +#endif diff --git a/supergameboy/nall/string/strl.hpp b/supergameboy/nall/string/strl.hpp new file mode 100644 index 00000000..84c841fa --- /dev/null +++ b/supergameboy/nall/string/strl.hpp @@ -0,0 +1,52 @@ +#ifndef NALL_STRING_STRL_HPP +#define NALL_STRING_STRL_HPP + +namespace nall { + +//strlcpy, strlcat based on OpenBSD implementation by Todd C. Miller + +//return = strlen(src) +unsigned strlcpy(char *dest, const char *src, unsigned length) { + char *d = dest; + const char *s = src; + unsigned n = length; + + if(n) { + while(--n && (*d++ = *s++)); //copy as many bytes as possible, or until null terminator reached + } + + if(!n) { + if(length) *d = 0; + while(*s++); //traverse rest of s, so that s - src == strlen(src) + } + + return (s - src - 1); //return length of copied string, sans null terminator +} + +//return = strlen(src) + min(length, strlen(dest)) +unsigned strlcat(char *dest, const char *src, unsigned length) { + char *d = dest; + const char *s = src; + unsigned n = length; + + while(n-- && *d) d++; //find end of dest + unsigned dlength = d - dest; + n = length - dlength; //subtract length of dest from maximum string length + + if(!n) return dlength + strlen(s); + + while(*s) { + if(n != 1) { + *d++ = *s; + n--; + } + s++; + } + *d = 0; + + return dlength + (s - src); //return length of resulting string, sans null terminator +} + +} + +#endif diff --git a/supergameboy/nall/string/strpos.hpp b/supergameboy/nall/string/strpos.hpp new file mode 100644 index 00000000..a1bf85b4 --- /dev/null +++ b/supergameboy/nall/string/strpos.hpp @@ -0,0 +1,60 @@ +#ifndef NALL_STRING_STRPOS_HPP +#define NALL_STRING_STRPOS_HPP + +//usage example: +//if(auto pos = strpos(str, key)) print(pos(), "\n"); +//prints position of key within str, only if it is found + +namespace nall { + +class strpos { + bool found; + unsigned position; +public: + inline operator bool() const { return found; } + inline unsigned operator()() const { return position; } + inline strpos(const char *str, const char *key) : found(false), position(0) { + unsigned ssl = strlen(str), ksl = strlen(key); + if(ksl > ssl) return; + + for(unsigned i = 0; i <= ssl - ksl; i++) { + if(!memcmp(str + i, key, ksl)) { + found = true; + position = i; + return; + } + } + } +}; + +class qstrpos { + bool found; + unsigned position; +public: + inline operator bool() const { return found; } + inline unsigned operator()() const { return position; } + inline qstrpos(const char *str, const char *key) : found(false), position(0) { + unsigned ssl = strlen(str), ksl = strlen(key); + if(ksl > ssl) return; + + for(unsigned i = 0; i <= ssl - ksl;) { + uint8_t x = str[i]; + if(x == '\"' || x == '\'') { + uint8_t z = i++; + while(str[i] != x && i < ssl) i++; + if(i >= ssl) i = z; + } + if(!memcmp(str + i, key, ksl)) { + found = true; + position = i; + return; + } else { + i++; + } + } + } +}; + +} + +#endif diff --git a/supergameboy/nall/string/trim.hpp b/supergameboy/nall/string/trim.hpp new file mode 100644 index 00000000..b13ab9ba --- /dev/null +++ b/supergameboy/nall/string/trim.hpp @@ -0,0 +1,54 @@ +#ifndef NALL_STRING_TRIM_HPP +#define NALL_STRING_TRIM_HPP + +namespace nall { + +char* ltrim(char *str, const char *key) { + if(!key || !*key) return str; + while(strbegin(str, key)) { + char *dest = str, *src = str + strlen(key); + while(true) { + *dest = *src++; + if(!*dest) break; + dest++; + } + } + return str; +} + +char* rtrim(char *str, const char *key) { + if(!key || !*key) return str; + while(strend(str, key)) str[strlen(str) - strlen(key)] = 0; + return str; +} + +char* trim(char *str, const char *key) { + return ltrim(rtrim(str, key), key); +} + +char* ltrim_once(char *str, const char *key) { + if(!key || !*key) return str; + if(strbegin(str, key)) { + char *dest = str, *src = str + strlen(key); + while(true) { + *dest = *src++; + if(!*dest) break; + dest++; + } + } + return str; +} + +char* rtrim_once(char *str, const char *key) { + if(!key || !*key) return str; + if(strend(str, key)) str[strlen(str) - strlen(key)] = 0; + return str; +} + +char* trim_once(char *str, const char *key) { + return ltrim_once(rtrim_once(str, key), key); +} + +} + +#endif diff --git a/supergameboy/nall/string/utility.hpp b/supergameboy/nall/string/utility.hpp new file mode 100644 index 00000000..2da2762b --- /dev/null +++ b/supergameboy/nall/string/utility.hpp @@ -0,0 +1,169 @@ +#ifndef NALL_STRING_UTILITY_HPP +#define NALL_STRING_UTILITY_HPP + +namespace nall { + +unsigned strlcpy(string &dest, const char *src, unsigned length) { + dest.reserve(length); + return strlcpy(dest(), src, length); +} + +unsigned strlcat(string &dest, const char *src, unsigned length) { + dest.reserve(length); + return strlcat(dest(), src, length); +} + +string substr(const char *src, unsigned start, unsigned length) { + string dest; + if(length == 0) { + //copy entire string + dest = src + start; + } else { + //copy partial string + strlcpy(dest, src + start, length + 1); + } + return dest; +} + +/* very simplistic wrappers to return string& instead of char* type */ + +string& strlower(string &str) { strlower(str()); return str; } +string& strupper(string &str) { strupper(str()); return str; } +string& strtr(string &dest, const char *before, const char *after) { strtr(dest(), before, after); return dest; } +string& ltrim(string &str, const char *key) { ltrim(str(), key); return str; } +string& rtrim(string &str, const char *key) { rtrim(str(), key); return str; } +string& trim (string &str, const char *key) { trim (str(), key); return str; } +string& ltrim_once(string &str, const char *key) { ltrim_once(str(), key); return str; } +string& rtrim_once(string &str, const char *key) { rtrim_once(str(), key); return str; } +string& trim_once (string &str, const char *key) { trim_once (str(), key); return str; } + +/* arithmetic <> string */ + +template string strhex(uintmax_t value) { + string output; + unsigned offset = 0; + + //render string backwards, as we do not know its length yet + do { + unsigned n = value & 15; + output[offset++] = n < 10 ? '0' + n : 'a' + n - 10; + value >>= 4; + } while(value); + + while(offset < length) output[offset++] = padding; + output[offset--] = 0; + + //reverse the string in-place + for(unsigned i = 0; i < (offset + 1) >> 1; i++) { + char temp = output[i]; + output[i] = output[offset - i]; + output[offset - i] = temp; + } + + return output; +} + +template string strsigned(intmax_t value) { + string output; + unsigned offset = 0; + + bool negative = value < 0; + if(negative) value = abs(value); + + do { + unsigned n = value % 10; + output[offset++] = '0' + n; + value /= 10; + } while(value); + + while(offset < length) output[offset++] = padding; + if(negative) output[offset++] = '-'; + output[offset--] = 0; + + for(unsigned i = 0; i < (offset + 1) >> 1; i++) { + char temp = output[i]; + output[i] = output[offset - i]; + output[offset - i] = temp; + } + + return output; +} + +template string strunsigned(uintmax_t value) { + string output; + unsigned offset = 0; + + do { + unsigned n = value % 10; + output[offset++] = '0' + n; + value /= 10; + } while(value); + + while(offset < length) output[offset++] = padding; + output[offset--] = 0; + + for(unsigned i = 0; i < (offset + 1) >> 1; i++) { + char temp = output[i]; + output[i] = output[offset - i]; + output[offset - i] = temp; + } + + return output; +} + +template string strbin(uintmax_t value) { + string output; + unsigned offset = 0; + + do { + unsigned n = value & 1; + output[offset++] = '0' + n; + value >>= 1; + } while(value); + + while(offset < length) output[offset++] = padding; + output[offset--] = 0; + + for(unsigned i = 0; i < (offset + 1) >> 1; i++) { + char temp = output[i]; + output[i] = output[offset - i]; + output[offset - i] = temp; + } + + return output; +} + +//using sprintf is certainly not the most ideal method to convert +//a double to a string ... but attempting to parse a double by +//hand, digit-by-digit, results in subtle rounding errors. +unsigned strdouble(char *str, double value) { + char buffer[256]; + sprintf(buffer, "%f", value); + + //remove excess 0's in fraction (2.500000 -> 2.5) + for(char *p = buffer; *p; p++) { + if(*p == '.') { + char *p = buffer + strlen(buffer) - 1; + while(*p == '0') { + if(*(p - 1) != '.') *p = 0; //... but not for eg 1.0 -> 1. + p--; + } + break; + } + } + + unsigned length = strlen(buffer); + if(str) strcpy(str, buffer); + return length + 1; +} + +string strdouble(double value) { + string temp; + temp.reserve(strdouble(0, value)); + strdouble(temp(), value); + return temp; +} + +} + +#endif diff --git a/supergameboy/nall/string/variadic.hpp b/supergameboy/nall/string/variadic.hpp new file mode 100644 index 00000000..13c477a8 --- /dev/null +++ b/supergameboy/nall/string/variadic.hpp @@ -0,0 +1,27 @@ +#ifndef NALL_STRING_VARIADIC_HPP +#define NALL_STRING_VARIADIC_HPP + +namespace nall { + +static void isprint(string &output) { +} + +template +static void isprint(string &output, T value, Args... args) { + output << to_string(value); + isprint(output, args...); +} + +template inline string sprint(Args... args) { + string output; + isprint(output, args...); + return output; +} + +template inline void print(Args... args) { + printf("%s", (const char*)sprint(args...)); +} + +} + +#endif diff --git a/supergameboy/nall/string/xml.hpp b/supergameboy/nall/string/xml.hpp new file mode 100644 index 00000000..218b4cbf --- /dev/null +++ b/supergameboy/nall/string/xml.hpp @@ -0,0 +1,265 @@ +#ifndef NALL_STRING_XML_HPP +#define NALL_STRING_XML_HPP + +//XML subset parser +//version 0.05 + +namespace nall { + +struct xml_attribute { + string name; + string content; + virtual string parse() const; +}; + +struct xml_element : xml_attribute { + string parse() const; + linear_vector attribute; + linear_vector element; + +protected: + void parse_doctype(const char *&data); + bool parse_head(string data); + bool parse_body(const char *&data); + friend xml_element xml_parse(const char *data); +}; + +inline string xml_attribute::parse() const { + string data; + unsigned offset = 0; + + const char *source = content; + while(*source) { + if(*source == '&') { + if(strbegin(source, "<")) { data[offset++] = '<'; source += 4; continue; } + if(strbegin(source, ">")) { data[offset++] = '>'; source += 4; continue; } + if(strbegin(source, "&")) { data[offset++] = '&'; source += 5; continue; } + if(strbegin(source, "'")) { data[offset++] = '\''; source += 6; continue; } + if(strbegin(source, """)) { data[offset++] = '"'; source += 6; continue; } + } + + //reject illegal characters + if(*source == '&') return ""; + if(*source == '<') return ""; + if(*source == '>') return ""; + + data[offset++] = *source++; + } + + data[offset] = 0; + return data; +} + +inline string xml_element::parse() const { + string data; + unsigned offset = 0; + + const char *source = content; + while(*source) { + if(*source == '&') { + if(strbegin(source, "<")) { data[offset++] = '<'; source += 4; continue; } + if(strbegin(source, ">")) { data[offset++] = '>'; source += 4; continue; } + if(strbegin(source, "&")) { data[offset++] = '&'; source += 5; continue; } + if(strbegin(source, "'")) { data[offset++] = '\''; source += 6; continue; } + if(strbegin(source, """)) { data[offset++] = '"'; source += 6; continue; } + } + + if(strbegin(source, "")) { + source += pos() + 3; + continue; + } else { + return ""; + } + } + + if(strbegin(source, "")) { + string cdata = substr(source, 9, pos() - 9); + data << cdata; + offset += strlen(cdata); + + source += offset + 3; + continue; + } else { + return ""; + } + } + + //reject illegal characters + if(*source == '&') return ""; + if(*source == '<') return ""; + if(*source == '>') return ""; + + data[offset++] = *source++; + } + + data[offset] = 0; + return data; +} + +inline void xml_element::parse_doctype(const char *&data) { + name = "!DOCTYPE"; + const char *content_begin = data; + + signed counter = 0; + while(*data) { + char value = *data++; + if(value == '<') counter++; + if(value == '>') counter--; + if(counter < 0) { + content = substr(content_begin, 0, data - content_begin - 1); + return; + } + } + throw "..."; +} + +inline bool xml_element::parse_head(string data) { + data.qreplace("\t", " "); + data.qreplace("\r", " "); + data.qreplace("\n", " "); + while(qstrpos(data, " ")) data.qreplace(" ", " "); + data.qreplace(" =", "="); + data.qreplace("= ", "="); + rtrim(data); + + lstring part; + part.qsplit(" ", data); + + name = part[0]; + if(name == "") throw "..."; + + for(unsigned i = 1; i < part.size(); i++) { + lstring side; + side.qsplit("=", part[i]); + if(side.size() != 2) throw "..."; + + xml_attribute attr; + attr.name = side[0]; + attr.content = side[1]; + if(strbegin(attr.content, "\"") && strend(attr.content, "\"")) trim_once(attr.content, "\""); + else if(strbegin(attr.content, "'") && strend(attr.content, "'")) trim_once(attr.content, "'"); + else throw "..."; + attribute.add(attr); + } +} + +inline bool xml_element::parse_body(const char *&data) { + while(true) { + if(!*data) return false; + if(*data++ != '<') continue; + if(*data == '/') return false; + + if(strbegin(data, "!DOCTYPE") == true) { + parse_doctype(data); + return true; + } + + if(strbegin(data, "!--")) { + if(auto offset = strpos(data, "-->")) { + data += offset() + 3; + continue; + } else { + throw "..."; + } + } + + if(strbegin(data, "![CDATA[")) { + if(auto offset = strpos(data, "]]>")) { + data += offset() + 3; + continue; + } else { + throw "..."; + } + } + + auto offset = strpos(data, ">"); + if(!offset) throw "..."; + + string tag = substr(data, 0, offset()); + data += offset() + 1; + const char *content_begin = data; + + bool self_terminating = false; + + if(strend(tag, "?") == true) { + self_terminating = true; + rtrim_once(tag, "?"); + } else if(strend(tag, "/") == true) { + self_terminating = true; + rtrim_once(tag, "/"); + } + + parse_head(tag); + if(self_terminating) return true; + + while(*data) { + unsigned index = element.size(); + xml_element node; + if(node.parse_body(data) == false) { + if(*data == '/') { + signed length = data - content_begin - 1; + if(length > 0) content = substr(content_begin, 0, length); + + data++; + auto offset = strpos(data, ">"); + if(!offset) throw "..."; + + tag = substr(data, 0, offset()); + data += offset() + 1; + + tag.replace("\t", " "); + tag.replace("\r", " "); + tag.replace("\n", " "); + while(strpos(tag, " ")) tag.replace(" ", " "); + rtrim(tag); + + if(name != tag) throw "..."; + return true; + } + } else { + element.add(node); + } + } + } +} + +//ensure there is only one root element +inline bool xml_validate(xml_element &document) { + unsigned root_counter = 0; + + for(unsigned i = 0; i < document.element.size(); i++) { + string &name = document.element[i].name; + if(strbegin(name, "?")) continue; + if(strbegin(name, "!")) continue; + if(++root_counter > 1) return false; + } + + return true; +} + +inline xml_element xml_parse(const char *data) { + xml_element self; + + try { + while(*data) { + xml_element node; + if(node.parse_body(data) == false) { + break; + } else { + self.element.add(node); + } + } + + if(xml_validate(self) == false) throw "..."; + return self; + } catch(const char*) { + xml_element empty; + return empty; + } +} + +} + +#endif diff --git a/supergameboy/nall/ups.hpp b/supergameboy/nall/ups.hpp new file mode 100644 index 00000000..f255ecb3 --- /dev/null +++ b/supergameboy/nall/ups.hpp @@ -0,0 +1,190 @@ +#ifndef NALL_UPS_HPP +#define NALL_UPS_HPP + +#include + +#include +#include +#include +#include + +namespace nall { + class ups { + public: + enum result { + ok, + patch_unreadable, + patch_unwritable, + patch_invalid, + input_invalid, + output_invalid, + patch_crc32_invalid, + input_crc32_invalid, + output_crc32_invalid, + }; + + ups::result create(const char *patch_fn, const uint8_t *x_data, unsigned x_size, const uint8_t *y_data, unsigned y_size) { + if(!fp.open(patch_fn, file::mode_write)) return patch_unwritable; + + crc32 = ~0; + uint32_t x_crc32 = crc32_calculate(x_data, x_size); + uint32_t y_crc32 = crc32_calculate(y_data, y_size); + + //header + write('U'); + write('P'); + write('S'); + write('1'); + encptr(x_size); + encptr(y_size); + + //body + unsigned max_size = max(x_size, y_size); + unsigned relative = 0; + for(unsigned i = 0; i < max_size;) { + uint8_t x = i < x_size ? x_data[i] : 0x00; + uint8_t y = i < y_size ? y_data[i] : 0x00; + + if(x == y) { + i++; + continue; + } + + encptr(i++ - relative); + write(x ^ y); + + while(true) { + if(i >= max_size) { + write(0x00); + break; + } + + x = i < x_size ? x_data[i] : 0x00; + y = i < y_size ? y_data[i] : 0x00; + i++; + write(x ^ y); + if(x == y) break; + } + + relative = i; + } + + //footer + for(unsigned i = 0; i < 4; i++) write(x_crc32 >> (i << 3)); + for(unsigned i = 0; i < 4; i++) write(y_crc32 >> (i << 3)); + uint32_t p_crc32 = ~crc32; + for(unsigned i = 0; i < 4; i++) write(p_crc32 >> (i << 3)); + + fp.close(); + return ok; + } + + ups::result apply(const uint8_t *p_data, unsigned p_size, const uint8_t *x_data, unsigned x_size, uint8_t *&y_data, unsigned &y_size) { + if(p_size < 18) return patch_invalid; + p_buffer = p_data; + + crc32 = ~0; + + //header + if(read() != 'U') return patch_invalid; + if(read() != 'P') return patch_invalid; + if(read() != 'S') return patch_invalid; + if(read() != '1') return patch_invalid; + + unsigned px_size = decptr(); + unsigned py_size = decptr(); + + //mirror + if(x_size != px_size && x_size != py_size) return input_invalid; + y_size = (x_size == px_size) ? py_size : px_size; + y_data = new uint8_t[y_size](); + + for(unsigned i = 0; i < x_size && i < y_size; i++) y_data[i] = x_data[i]; + for(unsigned i = x_size; i < y_size; i++) y_data[i] = 0x00; + + //body + unsigned relative = 0; + while(p_buffer < p_data + p_size - 12) { + relative += decptr(); + + while(true) { + uint8_t x = read(); + if(x && relative < y_size) { + uint8_t y = relative < x_size ? x_data[relative] : 0x00; + y_data[relative] = x ^ y; + } + relative++; + if(!x) break; + } + } + + //footer + unsigned px_crc32 = 0, py_crc32 = 0, pp_crc32 = 0; + for(unsigned i = 0; i < 4; i++) px_crc32 |= read() << (i << 3); + for(unsigned i = 0; i < 4; i++) py_crc32 |= read() << (i << 3); + uint32_t p_crc32 = ~crc32; + for(unsigned i = 0; i < 4; i++) pp_crc32 |= read() << (i << 3); + + uint32_t x_crc32 = crc32_calculate(x_data, x_size); + uint32_t y_crc32 = crc32_calculate(y_data, y_size); + + if(px_size != py_size) { + if(x_size == px_size && x_crc32 != px_crc32) return input_crc32_invalid; + if(x_size == py_size && x_crc32 != py_crc32) return input_crc32_invalid; + if(y_size == px_size && y_crc32 != px_crc32) return output_crc32_invalid; + if(y_size == py_size && y_crc32 != py_crc32) return output_crc32_invalid; + } else { + if(x_crc32 != px_crc32 && x_crc32 != py_crc32) return input_crc32_invalid; + if(y_crc32 != px_crc32 && y_crc32 != py_crc32) return output_crc32_invalid; + if(x_crc32 == y_crc32 && px_crc32 != py_crc32) return output_crc32_invalid; + if(x_crc32 != y_crc32 && px_crc32 == py_crc32) return output_crc32_invalid; + } + + if(p_crc32 != pp_crc32) return patch_crc32_invalid; + return ok; + } + + private: + file fp; + uint32_t crc32; + const uint8_t *p_buffer; + + uint8_t read() { + uint8_t n = *p_buffer++; + crc32 = crc32_adjust(crc32, n); + return n; + } + + void write(uint8_t n) { + fp.write(n); + crc32 = crc32_adjust(crc32, n); + } + + void encptr(uint64_t offset) { + while(true) { + uint64_t x = offset & 0x7f; + offset >>= 7; + if(offset == 0) { + write(0x80 | x); + break; + } + write(x); + offset--; + } + } + + uint64_t decptr() { + uint64_t offset = 0, shift = 1; + while(true) { + uint8_t x = read(); + offset += (x & 0x7f) * shift; + if(x & 0x80) break; + shift <<= 7; + offset += shift; + } + return offset; + } + }; +} + +#endif diff --git a/supergameboy/nall/utf8.hpp b/supergameboy/nall/utf8.hpp new file mode 100644 index 00000000..c66c341a --- /dev/null +++ b/supergameboy/nall/utf8.hpp @@ -0,0 +1,72 @@ +#ifndef NALL_UTF8_HPP +#define NALL_UTF8_HPP + +//UTF-8 <> UTF-16 conversion +//used only for Win32; Linux, etc use UTF-8 internally + +#if defined(_WIN32) + +#undef _WIN32_WINNT +#define _WIN32_WINNT 0x0501 +#undef NOMINMAX +#define NOMINMAX +#include +#undef interface + +namespace nall { + //UTF-8 to UTF-16 + class utf16_t { + public: + operator wchar_t*() { + return buffer; + } + + operator const wchar_t*() const { + return buffer; + } + + utf16_t(const char *s = "") { + if(!s) s = ""; + unsigned length = MultiByteToWideChar(CP_UTF8, 0, s, -1, 0, 0); + buffer = new wchar_t[length + 1](); + MultiByteToWideChar(CP_UTF8, 0, s, -1, buffer, length); + } + + ~utf16_t() { + delete[] buffer; + } + + private: + wchar_t *buffer; + }; + + //UTF-16 to UTF-8 + class utf8_t { + public: + operator char*() { + return buffer; + } + + operator const char*() const { + return buffer; + } + + utf8_t(const wchar_t *s = L"") { + if(!s) s = L""; + unsigned length = WideCharToMultiByte(CP_UTF8, 0, s, -1, 0, 0, (const char*)0, (BOOL*)0); + buffer = new char[length + 1](); + WideCharToMultiByte(CP_UTF8, 0, s, -1, buffer, length, (const char*)0, (BOOL*)0); + } + + ~utf8_t() { + delete[] buffer; + } + + private: + char *buffer; + }; +} + +#endif //if defined(_WIN32) + +#endif diff --git a/supergameboy/nall/utility.hpp b/supergameboy/nall/utility.hpp new file mode 100644 index 00000000..2a63f515 --- /dev/null +++ b/supergameboy/nall/utility.hpp @@ -0,0 +1,30 @@ +#ifndef NALL_UTILITY_HPP +#define NALL_UTILITY_HPP + +#include +#include + +namespace nall { + template struct enable_if { typedef T type; }; + template struct enable_if {}; + template struct mp_enable_if : enable_if {}; + + template inline void swap(T &x, T &y) { + T temp(std::move(x)); + x = std::move(y); + y = std::move(temp); + } + + template struct base_from_member { + T value; + base_from_member(T value_) : value(value_) {} + }; + + template inline T* allocate(size_t size, const T &value) { + T *array = new T[size]; + for(size_t i = 0; i < size; i++) array[i] = value; + return array; + } +} + +#endif diff --git a/supergameboy/nall/varint.hpp b/supergameboy/nall/varint.hpp new file mode 100644 index 00000000..cc3bb17c --- /dev/null +++ b/supergameboy/nall/varint.hpp @@ -0,0 +1,92 @@ +#ifndef NALL_VARINT_HPP +#define NALL_VARINT_HPP + +#include +#include +#include + +namespace nall { + template class uint_t { + private: + enum { bytes = (bits + 7) >> 3 }; //minimum number of bytes needed to store value + typedef typename static_if< + sizeof(int) >= bytes, + unsigned int, + typename static_if< + sizeof(long) >= bytes, + unsigned long, + typename static_if< + sizeof(long long) >= bytes, + unsigned long long, + void + >::type + >::type + >::type T; + static_assert::value> uint_assert; + T data; + + public: + inline operator T() const { return data; } + inline T operator ++(int) { T r = data; data = uclip(data + 1); return r; } + inline T operator --(int) { T r = data; data = uclip(data - 1); return r; } + inline T operator ++() { return data = uclip(data + 1); } + inline T operator --() { return data = uclip(data - 1); } + inline T operator =(const T i) { return data = uclip(i); } + inline T operator |=(const T i) { return data = uclip(data | i); } + inline T operator ^=(const T i) { return data = uclip(data ^ i); } + inline T operator &=(const T i) { return data = uclip(data & i); } + inline T operator<<=(const T i) { return data = uclip(data << i); } + inline T operator>>=(const T i) { return data = uclip(data >> i); } + inline T operator +=(const T i) { return data = uclip(data + i); } + inline T operator -=(const T i) { return data = uclip(data - i); } + inline T operator *=(const T i) { return data = uclip(data * i); } + inline T operator /=(const T i) { return data = uclip(data / i); } + inline T operator %=(const T i) { return data = uclip(data % i); } + + inline uint_t() : data(0) {} + inline uint_t(const T i) : data(uclip(i)) {} + }; + + template class int_t { + private: + enum { bytes = (bits + 7) >> 3 }; //minimum number of bytes needed to store value + typedef typename static_if< + sizeof(int) >= bytes, + signed int, + typename static_if< + sizeof(long) >= bytes, + signed long, + typename static_if< + sizeof(long long) >= bytes, + signed long long, + void + >::type + >::type + >::type T; + static_assert::value> int_assert; + T data; + + public: + inline operator T() const { return data; } + inline T operator ++(int) { T r = data; data = sclip(data + 1); return r; } + inline T operator --(int) { T r = data; data = sclip(data - 1); return r; } + inline T operator ++() { return data = sclip(data + 1); } + inline T operator --() { return data = sclip(data - 1); } + inline T operator =(const T i) { return data = sclip(i); } + inline T operator |=(const T i) { return data = sclip(data | i); } + inline T operator ^=(const T i) { return data = sclip(data ^ i); } + inline T operator &=(const T i) { return data = sclip(data & i); } + inline T operator<<=(const T i) { return data = sclip(data << i); } + inline T operator>>=(const T i) { return data = sclip(data >> i); } + inline T operator +=(const T i) { return data = sclip(data + i); } + inline T operator -=(const T i) { return data = sclip(data - i); } + inline T operator *=(const T i) { return data = sclip(data * i); } + inline T operator /=(const T i) { return data = sclip(data / i); } + inline T operator %=(const T i) { return data = sclip(data % i); } + + inline int_t() : data(0) {} + inline int_t(const T i) : data(sclip(i)) {} + }; +} + +#endif diff --git a/supergameboy/nall/vector.hpp b/supergameboy/nall/vector.hpp new file mode 100644 index 00000000..3d69d4d5 --- /dev/null +++ b/supergameboy/nall/vector.hpp @@ -0,0 +1,240 @@ +#ifndef NALL_VECTOR_HPP +#define NALL_VECTOR_HPP + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace nall { + //linear_vector + //memory: O(capacity * 2) + // + //linear_vector uses placement new + manual destructor calls to create a + //contiguous block of memory for all objects. accessing individual elements + //is fast, though resizing the array incurs significant overhead. + //reserve() overhead is reduced from quadratic time to amortized constant time + //by resizing twice as much as requested. + // + //if objects hold memory address references to themselves (introspection), a + //valid copy constructor will be needed to keep pointers valid. + + template class linear_vector { + protected: + T *pool; + unsigned poolsize, objectsize; + + public: + unsigned size() const { return objectsize; } + unsigned capacity() const { return poolsize; } + + void reset() { + if(pool) { + for(unsigned i = 0; i < objectsize; i++) pool[i].~T(); + free(pool); + } + pool = 0; + poolsize = 0; + objectsize = 0; + } + + void reserve(unsigned newsize) { + newsize = bit::round(newsize); //round to nearest power of two (for amortized growth) + + T *poolcopy = (T*)malloc(newsize * sizeof(T)); + for(unsigned i = 0; i < min(objectsize, newsize); i++) new(poolcopy + i) T(pool[i]); + for(unsigned i = 0; i < objectsize; i++) pool[i].~T(); + free(pool); + pool = poolcopy; + poolsize = newsize; + objectsize = min(objectsize, newsize); + } + + void resize(unsigned newsize) { + if(newsize > poolsize) reserve(newsize); + + if(newsize < objectsize) { + //vector is shrinking; destroy excess objects + for(unsigned i = newsize; i < objectsize; i++) pool[i].~T(); + } else if(newsize > objectsize) { + //vector is expanding; allocate new objects + for(unsigned i = objectsize; i < newsize; i++) new(pool + i) T; + } + + objectsize = newsize; + } + + void add(const T data) { + if(objectsize + 1 > poolsize) reserve(objectsize + 1); + new(pool + objectsize++) T(data); + } + + inline T& operator[](unsigned index) { + if(index >= objectsize) resize(index + 1); + return pool[index]; + } + + inline const T& operator[](unsigned index) const { + if(index >= objectsize) throw "vector[] out of bounds"; + return pool[index]; + } + + //copy + inline linear_vector& operator=(const linear_vector &source) { + reset(); + reserve(source.capacity()); + resize(source.size()); + for(unsigned i = 0; i < source.size(); i++) operator[](i) = source.operator[](i); + return *this; + } + + linear_vector(const linear_vector &source) : pool(0), poolsize(0), objectsize(0) { + operator=(source); + } + + //move + inline linear_vector& operator=(linear_vector &&source) { + reset(); + pool = source.pool; + poolsize = source.poolsize; + objectsize = source.objectsize; + source.pool = 0; + source.reset(); + return *this; + } + + linear_vector(linear_vector &&source) : pool(0), poolsize(0), objectsize(0) { + operator=(std::move(source)); + } + + //construction + linear_vector() : pool(0), poolsize(0), objectsize(0) { + } + + linear_vector(std::initializer_list list) : pool(0), poolsize(0), objectsize(0) { + for(const T *p = list.begin(); p != list.end(); ++p) add(*p); + } + + ~linear_vector() { + reset(); + } + }; + + //pointer_vector + //memory: O(1) + // + //pointer_vector keeps an array of pointers to each vector object. this adds + //significant overhead to individual accesses, but allows for optimal memory + //utilization. + // + //by guaranteeing that the base memory address of each objects never changes, + //this avoids the need for an object to have a valid copy constructor. + + template class pointer_vector { + protected: + T **pool; + unsigned poolsize, objectsize; + + public: + unsigned size() const { return objectsize; } + unsigned capacity() const { return poolsize; } + + void reset() { + if(pool) { + for(unsigned i = 0; i < objectsize; i++) { if(pool[i]) delete pool[i]; } + free(pool); + } + pool = 0; + poolsize = 0; + objectsize = 0; + } + + void reserve(unsigned newsize) { + newsize = bit::round(newsize); //round to nearest power of two (for amortized growth) + + for(unsigned i = newsize; i < objectsize; i++) { + if(pool[i]) { delete pool[i]; pool[i] = 0; } + } + + pool = (T**)realloc(pool, newsize * sizeof(T*)); + for(unsigned i = poolsize; i < newsize; i++) pool[i] = 0; + poolsize = newsize; + objectsize = min(objectsize, newsize); + } + + void resize(unsigned newsize) { + if(newsize > poolsize) reserve(newsize); + + for(unsigned i = newsize; i < objectsize; i++) { + if(pool[i]) { delete pool[i]; pool[i] = 0; } + } + + objectsize = newsize; + } + + void add(const T data) { + if(objectsize + 1 > poolsize) reserve(objectsize + 1); + pool[objectsize++] = new T(data); + } + + inline T& operator[](unsigned index) { + if(index >= objectsize) resize(index + 1); + if(!pool[index]) pool[index] = new T; + return *pool[index]; + } + + inline const T& operator[](unsigned index) const { + if(index >= objectsize || !pool[index]) throw "vector[] out of bounds"; + return *pool[index]; + } + + //copy + inline pointer_vector& operator=(const pointer_vector &source) { + reset(); + reserve(source.capacity()); + resize(source.size()); + for(unsigned i = 0; i < source.size(); i++) operator[](i) = source.operator[](i); + return *this; + } + + pointer_vector(const pointer_vector &source) : pool(0), poolsize(0), objectsize(0) { + operator=(source); + } + + //move + inline pointer_vector& operator=(pointer_vector &&source) { + reset(); + pool = source.pool; + poolsize = source.poolsize; + objectsize = source.objectsize; + source.pool = 0; + source.reset(); + return *this; + } + + pointer_vector(pointer_vector &&source) : pool(0), poolsize(0), objectsize(0) { + operator=(std::move(source)); + } + + //construction + pointer_vector() : pool(0), poolsize(0), objectsize(0) { + } + + pointer_vector(std::initializer_list list) : pool(0), poolsize(0), objectsize(0) { + for(const T *p = list.begin(); p != list.end(); ++p) add(*p); + } + + ~pointer_vector() { + reset(); + } + }; + + template struct has_size> { enum { value = true }; }; + template struct has_size> { enum { value = true }; }; +} + +#endif diff --git a/supergameboy/supergameboy.cpp b/supergameboy/supergameboy.cpp new file mode 100644 index 00000000..0c011af7 --- /dev/null +++ b/supergameboy/supergameboy.cpp @@ -0,0 +1,68 @@ +#include "supergameboy.hpp" + +#ifdef _WIN32 + #define dllexport __declspec(dllexport) +#else + #define dllexport +#endif + +#include +#include +#include + +#include + +dllexport void sgb_rom(uint8_t *data, unsigned size) { + supergameboy.romdata = data; + supergameboy.romsize = size; +} + +dllexport void sgb_ram(uint8_t *data, unsigned size) { + supergameboy.ramdata = data; + supergameboy.ramsize = size; +} + +dllexport void sgb_rtc(uint8_t *data, unsigned size) { + supergameboy.rtcdata = data; + supergameboy.rtcsize = size; +} + +dllexport bool sgb_init(bool version) { + return supergameboy.init(version); +} + +dllexport void sgb_term() { + supergameboy.term(); +} + +dllexport void sgb_power() { + supergameboy.power(); +} + +dllexport void sgb_reset() { + supergameboy.reset(); +} + +dllexport void sgb_row(unsigned row) { + supergameboy.row(row); +} + +dllexport uint8_t sgb_read(uint16_t addr) { + return supergameboy.read(addr); +} + +dllexport void sgb_write(uint16_t addr, uint8_t data) { + supergameboy.write(addr, data); +} + +dllexport unsigned sgb_run(uint32_t *samplebuffer, unsigned clocks) { + return supergameboy.run(samplebuffer, clocks); +} + +dllexport void sgb_save() { + supergameboy.save(); +} + +dllexport void sgb_serialize(nall::serializer &s) { + supergameboy.serialize(s); +} diff --git a/supergameboy/supergameboy.hpp b/supergameboy/supergameboy.hpp new file mode 100644 index 00000000..715e4893 --- /dev/null +++ b/supergameboy/supergameboy.hpp @@ -0,0 +1,32 @@ +#ifndef SUPERGAMEBOY_HPP +#define SUPERGAMEBOY_HPP + +#include +#include +#include +#include + +#include +#include +using namespace nall; + +#include +#include + +extern "C" { + void sgb_rom(uint8_t *data, unsigned size); + void sgb_ram(uint8_t *data, unsigned size); + void sgb_rtc(uint8_t *data, unsigned size); + bool sgb_init(bool version); + void sgb_term(); + void sgb_power(); + void sgb_reset(); + void sgb_row(unsigned row); + uint8_t sgb_read(uint16_t addr); + void sgb_write(uint16_t addr, uint8_t data); + unsigned sgb_run(uint32_t *samplebuffer, unsigned clocks); + void sgb_save(); + void sgb_serialize(nall::serializer &s); +} + +#endif diff --git a/supergameboy/sync.sh b/supergameboy/sync.sh new file mode 100644 index 00000000..4bbaf34f --- /dev/null +++ b/supergameboy/sync.sh @@ -0,0 +1,2 @@ +rm -r nall +cp -r ../nall ./nall