diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 0000000..5c726f1 --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,71 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL" + +on: + push: + branches: [ main ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ main ] + schedule: + - cron: '44 20 * * 3' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'cpp' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] + # Learn more: + # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v1 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v1 + + # ℹī¸ Command-line programs to run using the OS shell. + # 📚 https://git.io/JvXDl + + # ✏ī¸ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language + + #- run: | + # make bootstrap + # make release + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v1 diff --git a/.gitignore b/.gitignore index c73d3b8..f42e8c9 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,5 @@ **/Release/ *_cso.h publish/ +**_dxbc.txt +/.vs/ diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index 08fe58b..0000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "thirdparty\\SlashDiablo-HD"] - path = thirdparty\\SlashDiablo-HD - url = https://github.com/bolrog/SlashDiablo-HD.git diff --git a/README.md b/README.md index f228901..1195b1b 100644 --- a/README.md +++ b/README.md @@ -1,76 +1,61 @@ # D2DX -D2DX is a preservation project for running classic Diablo II/LoD on modern PCs. +D2DX is a Glide-wrapper and mod that makes the classic Diablo II/LoD run well on modern PCs, while honoring the original look and feel of the game. +Play in a window or in fullscreen, glitch-free, with or without enhancements like widescreen, true high framerate and anti-aliasing. -Version 0.99.403b +Version 0.99.529 -## Mission statement - - Turn the game into a well behaved DirectX 11 title on Windows 10 (or 8+). - - High quality scaling to modern resolutions, including widescreen. - - Aim to preserve the classic Diablo 2/LoD experience as much as possible. - -## Implemented - - High performance DirectX 11 renderer (Glide wrapper). - - Proper gamma/contrast. - - Improved fullscreen mode: instant ALT-TAB and low latency. - - Improved windowed mode. - - Widescreen support (in vanilla D2/LoD). +**WANT TO HELP OUT?** Take the one-question game version poll: https://strawpoll.com/w4y72p4f6 + +Update July 2021: I have been too busy to work on D2DX for a while, but hope to resume shortly. + +## Features + - Turns the game into a well behaved DirectX 11 title on Windows 10 (also 7, 8 and 8.1). + - High quality scaling to fit modern screen sizes, including [widescreen aspect ratios](https://raw.githubusercontent.com/bolrog/d2dx/main/screenshots/d2dx2.png). + - High FPS mod using motion prediction, bypassing the internal 25 fps limit. Play at 60 fps and higher! ([video](https://imgur.com/a/J1F8Ctb)) + - [Anti-aliasing](https://github.com/bolrog/d2dx/wiki/Screenshots#anti-aliasing) of specific jagged edges in the game (sprites, walls, some floors). - Seamless windowed/fullscreen switching with (ALT-Enter). - - Fixes various glitches in the supported game versions. + - Improved fullscreen: instant ALT-TAB and low latency. + - Improved windowed mode. + - Proper gamma/contrast. + - Fixes a few window-related glitches in Diablo II itself. + +### Video Showcasing Motion Prediction + [FPS increase in menus, FPS increase for projectiles, monsters, +more](https://imgur.com/a/J1F8Ctb) ## Upcoming - - Better scaling. + - Suggestions welcome! ## Requirements - Diablo 2: LoD (see Compatibility section below). - - Windows 8 and above (10 recommended). + - Windows 7 SP1 and above (10 recommended for latency improvements). - A CPU with SSE2 support. - - Integrated graphics or discrete GPU with DirectX 11 support (feature level 10.0 required). + - Integrated graphics or discrete GPU with DirectX 10.1 support. ## Compatibility Game versions supported: - - All features: 1.12, 1.13c and 1.13d. - - No widescreen: 1.09d, 1.10 and 1.14d. + - All features: 1.09d, 1.13c, 1.13d and 1.14d. + - Without resolution switching: 1.10f, 1.12. - Other versions are unsupported, will display a warning at startup and exhibit glitches. -D2DX has been tested working with the following mods: - - MedianXL (1024x768) - - PlugY - - D2ModMaker +For compatibility with mods, see the [wiki](https://github.com/bolrog/d2dx/wiki/Compatibility-with-other-mods). + +## Documentation + This readme contains basic information to get you started. See the [D2DX wiki](https://github.com/bolrog/d2dx/wiki/) for more documentation. ## Installation Copy the included "glide3x.dll" into your Diablo II folder. Note that in some cases you may have to also download and install the Visual C++ runtime library from Microsoft: https://aka.ms/vs/16/release/vc_redist.x86.exe - The wrapper should work with PlugY, just make sure you have (at a minimum) -3dfx in the ini file: - ``` - [LAUNCHING] - Param=-3dfx - ``` - If you wish to use the widescreen modes, also copy the included "D2DX_SlashDiabloHD.dll" and "SlashDiabloHD.mpq" into your Diablo II folder. - ## Usage -### To start the game with D2DX enabled +To start the game with D2DX enabled, just provide -3dfx, e.g. ``` Game.exe -3dfx ``` -Windowed/fullscreen mode can be switched at any time by pressing ALT-Enter. - -### Experimental widescreen (windowed and fullscreen) modes - PLEASE NOTE: This only works with 1.12, 1.13c and 1.13d at this time. - - Ensure the "D2DX_SlashDiabloHD.dll" and "SlashDiabloHD.mpq" files are in your Diablo II folder. - When it is present, D2DX will enable a new in-game resolution, close to the normal ones but having the aspect ratio of your monitor. - The goal of this is to achieve proper fullscreen scaling without artifacts when displaying the game on modern PCs. +Windowed/fullscreen mode can be switched at any time by pressing ALT-Enter. The normal -w command-line option works too. - - For a 1920x1080 monitor, this is 960x540 (in fullscreen: 2x integer scaling). - - For a 2560×1440 monitor, this is 853x480 (in fullscreen: 3x integer scaling). - - For a 3840x2160 monitor, this is 960x540 (in fullscreen: 4x integer scaling). - -### Miscellaneous -- To get rid of the "DX" logo on the title screen, add -gxskiplogo to the command line. -- To scale the window by 2x or 3x, add -gxscale2 or -gxscale3 to the command line. Note that if the Window doesn't fit on the desktop, the scale factor will be lowered. +Many of the default settings of D2DX can be changed. For a full list of command-line options and how to use a configuration file, see the [wiki](https://github.com/bolrog/d2dx/wiki/). ## Troubleshooting @@ -80,89 +65,172 @@ Windowed/fullscreen mode can be switched at any time by pressing ALT-Enter. ### It's ugly/slow/buggy. Let me know by filing an issue! I'd like to keep improving D2DX (within the scope of the project). -## Third-party libraries -This project uses the following third party libraries: +## Credits +Main development/maintenance: bolrog +Patch contributions: Xenthalon + +The research of many people in the Diablo II community over twenty years made this project possible. + +Thanks to Mir Drualga for making the fantastic SGD2FreeRes mod! +Thanks also to everyone who contributes bug reports. + +D2DX uses the following third party libraries: - FNV1a hash reference implementation, which is in the public domain. - Detours by Microsoft. -- SlashDiablo-HD by Mir Drualga and Bartosz Jankowski, licensed under Affero GPL v3. - -## Release history - -### 0.99.403b - - Fix mouse sensitivy being wrong in the horizontal direction in widescreen mode. - - Fix bug occasionally causing visual corruption when many things were on screen. - - Fix the well-known issue of the '5' character looking like a '6'. (Shouldn't interfere with other mods.) - - Fix "tearing" seen due to vsync being disabled. Re-enable vsync by default (use -dxnovsync to disable it). - - Some small performance improvements. - - Updated: include the relevant license texts. - -### 0.99.402 - - Add seamless alt-enter switching between windowed and fullscreen modes. - -### 0.99.401c - - Add experimental support for widescreen modes using a fork of SlashDiablo-HD by Mir Drualga and Bartosz Jankowski. - - Remove the use of "AA bilinear" filtering, in favor of point filtering. This is part of a work in progress and will be tweaked further. - - Cut VRAM footprint by 75% and reduce performance overhead. - - Source code is now in the git. - - Updated: fix occasional glitches due to the wrong texture cache policy being used. - - Updated again: forgot to include SlashDiabloHD.mpq, which is required. - -### 0.99.329b - - Add support for 1024x768, tested with MedianXL which now seems to work. - - Fix window being re-centered by the game even if user moved it. - - Fix occasional crash introduced in 0.99.329. - -### 0.99.329 - - Add support for 640x480 mode, and polish behavior around in-game resolution switching. - -### 0.98.329 - - Add support for LoD 1.09d, 1.10 and 1.12. - - Add warning dialog on startup when using an unsupported game version. - -### 0.91.328 - - Fix mouse pointer "jumping" when opening inventory and clicking items in fullscreen or scaled-window modes. - -### 0.91.327 - - Fix two types of crashes in areas where there are many things on screen at once. - - Fix window not being movable. - -### 0.91.325b - - Fix game being scaled wrong in LoD 1.13c/d. - -### 0.91.325 - - Fix mode switching flicker on startup in fullscreen modes. - - Fix mouse cursor being "teleported" when leaving the window in windowed mode. - - Fix mouse speed not being consistent with the desktop. - - Fix game looking fuzzy on high DPI displays. - - Improve frame time consistency/latency. - - Add experimental window scaling support. - - More performance improvements. - -### 0.91.324 - - Fix crash when hosting TCP/IP game. - - Speed up texture cache lookup. - - Changed to a slightly less insane version numbering scheme. - -### 0.9.210323c - - Fix crash occurring after playing a while. - -### 0.9.210323b: - - Add support for LoD 1.13d. - - Fix accidental performance degradation in last build. - -### 0.9.210323: - - Add support for LoD 1.13c. - - Fix the delay/weird characters in the corner on startup in LoD 1.13c. - - Fix glitchy window movement/sizing on startup in LoD 1.13c. - - Performance improvements. - -### 0.9.210322: - - Fix line rendering (missing exp. bar, rain, npc crosses on mini map). - - Fix smudged fonts. - - Default fullscreen mode now uses the desktop resolution, and uses improved scaling (less fuzzy). - -### 0.9.210321b: - - Fix default fullscreen mode. - -### 0.9.210321: - - Initial release. +- SGD2FreeRes by Mir Drualga, licensed under Affero GPL v3. +- FXAA implementation by Timothy Lottes. (This software contains source code provided by NVIDIA Corporation.) +- stb_image by Sean Barrett +- pocketlzma by Robin Berg Pettersen +- 9-tap Catmull-Rom texture filtering by TheRealMJP. + +## Donations + +D2DX is free software, but if you enjoy the project and want to buy me a coffee, [click here](https://commerce.coinbase.com/checkout/13ed8fc7-d7f3-428c-b834-ab7bd6501db7). + +## Recent release history + +### 0.99.529 + - Add motion prediction for 1.09d, complete except for hovering text (it's coming). + - Fix low fps in the menus for 1.09d. + +### 0.99.527b + - Add 'filtering' option in cfg file, which allows using bilinear filtering or Catmull-Rom for scaling the game, + for those who prefer the softer look over the default integer-scale/sharp-bilinear. + - Fix artifacts when vsync is off. + +### 0.99.526b + - Fix motion-predicted texts looking corrupted/being positioned wrong. + +### 0.99.525 + - Fix motion prediction of shadows not working for some units. + - Fix window size when window is scaled beyond the desktop dimensions. + - Fix some black text in old versions of MedianXL. + - Remove -dxtestsleepfix, this is now enabled by default (along with the fps fix). + +### 0.99.521 + - Fix low fps in menu for 1.13d with basemod. + - Fix low fps for 1.13c and 1.14 with basemod. + - Fix basemod compatibility so that "BypassFPS" can be enabled without ill effects. + +### 0.99.519 + - Unlock FPS in menus. (Known issue: char select screen animates too fast) + - Add experimental "sleep fix" to reduce microstutter in-game. Can be enabled with -dxtestsleepfix. + Let me know if you notice any improvements to overall smoothness in-game, or any problems! + +### 0.99.518b + - Fix size of window being larger than desktop area with -dxscaleN. + +### 0.99.517 + - Fix in-game state detection, causing DX logo to show in-game and other issues. + +### 0.99.516 + - High FPS (motion prediction) is now default enabled on supported game versions (1.12, 1.13c, 1.13d and 1.14d). + - Fix crash when trying to host TCP/IP game. + +### 0.99.512c + - Add "frameless" window option in cfg file, for hiding the window frame. + - Fix corrupt graphics in low lighting detail mode. + - Fix corrupt graphics in perspective mode. + - Fix distorted automap cross. + - Fix mouse sometimes getting stuck on the edge of the screen when setting a custom resolution in the cfg file. + +### 0.99.511 + - Change resolution mod from D2HD to the newer SGD2FreeRes (both by Mir Drualga). + Custom resolutions now work in 1.09 and 1.14d, but (at this time) there is no support for 1.12. Let me know if this is a problem! + - Some performance optimizations. + - Remove sizing handles on the window (this was never intended). + +### 0.99.510 + - Add the possibility to set a custom in-game resolution in d2dx.cfg. See the wiki for details. + - Remove special case for MedianXL Sigma (don't limit to 1024x768). + +### 0.99.508 + - Fix motion prediction of water splashes e.g. in Kurast Docks. + +### 0.99.507 + - Add motion prediction to weather. + - Improve visual quality of weather a bit (currently only with -dxtestmop). + - Don't block Project Diablo 2 when detected. + +### 0.99.506 + - Fix crash sometimes happening when using a town portal. + - Add motion prediction to missiles. + +### 0.99.505b + - Fix bug causing crash when using config file. + - Add option to set window position in config file. (default is to center) + - Update: Fix tracking of belt items and auras. + - Update: Fix teleportation causing "drift" in motion prediction. + +### 0.99.504 + - Revamp configuration file support. NOTE that the old d2dx.cfg format has changed! See d2dx-defaults.cfg for instructions. + +### 0.99.503 + - Add -dxnotitlechange option to leave window title alone. [patch by Xenthalon] + - Fix -dxscale2/3 not being applied correctly. [patch by Xenthalon] + - Improve the WIP -dxtestmop mode. Now handles movement of all units, not just the player. + +### 0.99.430b + - Add experimental motion prediction ("smooth movement") feature. This gives actual in-game fps above 25. It is a work in progress, see + the Wiki page (https://github.com/bolrog/d2dx/wiki/Motion-Prediction) for info on how to enable it. + - Updated: fix some glitches. + +### 0.99.429 + - Fix AA being applied on merc portraits, and on text (again). + +### 0.99.428 + - Fix AA sometimes being applied to the interior of text. + +### 0.99.423b + - Fix high CPU usage. + - Improve caching. + - Remove registry fix (no longer needed). + - Updated: Fix AA being applied to some UI elements (it should not). + - Updated: Fix d2dx logo. + +### 0.99.422 + - Fix missing stars in Arcane Sanctuary. + - Fix AA behind some transparent effects, e.g. character behind aura fx. + - Add -dxnocompatmodefix command line option (can be used in cases where other mods require XP compat mode). + +### 0.99.419 + - Fix issue where "tooltip" didn't pop up immediately after placing an item in the inventory. + - Add support for cfg file (named d2dx.cfg, should contain cmdline args including '-'). + - Further limit where AA can possibly be applied (only on relevant edges in-game and exclude UI surfaces). + - Performance optimizations. + +### 0.99.415 + - Add fps fix that greatly smoothes out frame pacing and mouse cursor movement. Can be disabled with -dxnofpsfix. + - Improve color precision a bit on devices that support 10 bits per channel (this is not HDR, just reduces precision loss from in-game lighting/gamma). + - To improve bug reports, a log file (d2dx_log.txt) will be written to the Diablo II folder. + +### 0.99.414 + - The mouse cursor is now confined to the game window by default. Disable with -dxnoclipcursor. + - Finish AA implementation, giving improved quality. + - Reverted behavior: the mouse cursor will now "jump" when opening inventory like in the unmodded game. This avoids the character suddenly changing movement direction if holding LMB while opening the inventory. + - Fix issue where D2DX was not able to turn off XP (etc) compatibility mode. + - Fix issue where D2DX used the DX 10.1 feature level by default even on better GPUs. + - Fix misc bugs. + +### 0.99.413 + - Turn off XP compatibility mode for the game executables. It is not necessary with D2DX and causes issues like graphics corruption. + - Fix initial window size erroneously being 640x480 during intro FMVs. + +### 0.99.412 + - Added (tasteful) FXAA, which is enabled by default since it looked so nice. (It doesn't destroy any detail.) + +### 0.99.411 + - D2DX should now work on DirectX 10/10.1 graphics cards. + +### 0.99.410 + - Improved non-integer scaling quality, using "anti-aliased nearest" sampling (similar to sharp-bilinear). + - Specifying -dxnowide will now select a custom screen mode that gives integer scaling, but with ~4:3 aspect ratio. + - Added -dxnoresmod option, which turns off the built-in SlashDiablo-HD (no custom resolutions). + - (For res mod authors) Tuned configurator API to disable the built-in SlashDiablo-HD automatically when used. + - Other internal improvements. + +### 0.99.408 + - Fix window size being squashed vertically after alt-entering from fullscreen mode. + - Fix crash when running on Windows 7. + +A full release history can be found on the Wiki. diff --git a/d2dx-defaults.cfg b/d2dx-defaults.cfg new file mode 100644 index 0000000..3ef456e --- /dev/null +++ b/d2dx-defaults.cfg @@ -0,0 +1,32 @@ +# +# This is an example config file for D2DX. +# +# If you don't like the default settings, you can edit this file, rename it to "d2dx.cfg" +# and place it in the game folder. +# + +[window] +scale=1 # range 1-3, an integer scale factor for the window +position=[-1,-1] # if [-1,-1] the window will be centered, otherwise placed at the explicit position given here +frameless=false # if true, the window frame (caption bar etc) will be removed + +[game] +size=[-1,-1] # if [-1,-1] d2dx will decide a suitable game size, otherwise will use the size given here +filtering=0 # if 0, will use high quality filtering (sharp, more pixelated) + # 1, will use bilinear filtering (blurry) + # 2, will use catmull-rom filtering (higher quality than bilinear) + +# +# Opt-outs from default D2DX behavior +# +[optouts] +noclipcursor=false # if true, will not lock the mouse cursor to the game window +nofpsfix=false # if true, will not apply the basic fps fix (precludes high fps support) +noresmod=false # if true, will not apply the built-in D2HD resolution mod (precludes widescreen support) +nowide=false # if true, will not choose a widescreen resolution (if noresmod is true, this does nothing) +nologo=false # if true, will not display the D2DX logo on the title screen +novsync=false # if true, will not use vertical sync +noaa=false # if true, will not apply anti-aliasing to jagged edges +nocompatmodefix=false # if true, will not block the use of "Windows XP compatibility mode" +notitlechange=false # if true, will not change the window title text +nomotionprediction=false # if true, will not run the game graphics at high fps diff --git a/screenshots/d2dx1.png b/screenshots/d2dx1.png new file mode 100644 index 0000000..3c9918e Binary files /dev/null and b/screenshots/d2dx1.png differ diff --git a/screenshots/d2dx2.png b/screenshots/d2dx2.png new file mode 100644 index 0000000..1c71179 Binary files /dev/null and b/screenshots/d2dx2.png differ diff --git a/screenshots/d2dx3.png b/screenshots/d2dx3.png new file mode 100644 index 0000000..51d04a5 Binary files /dev/null and b/screenshots/d2dx3.png differ diff --git a/screenshots/d2dx_dxnowide1.png b/screenshots/d2dx_dxnowide1.png new file mode 100644 index 0000000..f85f825 Binary files /dev/null and b/screenshots/d2dx_dxnowide1.png differ diff --git a/screenshots/d2dx_dxnowide2.png b/screenshots/d2dx_dxnowide2.png new file mode 100644 index 0000000..701e20a Binary files /dev/null and b/screenshots/d2dx_dxnowide2.png differ diff --git a/screenshots/d2dx_dxnowide3.png b/screenshots/d2dx_dxnowide3.png new file mode 100644 index 0000000..3df88f0 Binary files /dev/null and b/screenshots/d2dx_dxnowide3.png differ diff --git a/screenshots/d2dx_fxaa1.png b/screenshots/d2dx_fxaa1.png new file mode 100644 index 0000000..ccadc48 Binary files /dev/null and b/screenshots/d2dx_fxaa1.png differ diff --git a/screenshots/d2dx_nofxaa1.png b/screenshots/d2dx_nofxaa1.png new file mode 100644 index 0000000..0260059 Binary files /dev/null and b/screenshots/d2dx_nofxaa1.png differ diff --git a/screenshots/d2dx_scaling_0.png b/screenshots/d2dx_scaling_0.png new file mode 100644 index 0000000..18de731 Binary files /dev/null and b/screenshots/d2dx_scaling_0.png differ diff --git a/screenshots/d2dx_scaling_1.png b/screenshots/d2dx_scaling_1.png new file mode 100644 index 0000000..5d28a4b Binary files /dev/null and b/screenshots/d2dx_scaling_1.png differ diff --git a/screenshots/d2dx_scaling_2.png b/screenshots/d2dx_scaling_2.png new file mode 100644 index 0000000..21124fe Binary files /dev/null and b/screenshots/d2dx_scaling_2.png differ diff --git a/screenshots/svens1.png b/screenshots/svens1.png new file mode 100644 index 0000000..c0fc601 Binary files /dev/null and b/screenshots/svens1.png differ diff --git a/screenshots/svens2.png b/screenshots/svens2.png new file mode 100644 index 0000000..9fa2f38 Binary files /dev/null and b/screenshots/svens2.png differ diff --git a/screenshots/svens3.png b/screenshots/svens3.png new file mode 100644 index 0000000..268e007 Binary files /dev/null and b/screenshots/svens3.png differ diff --git a/src/d2dx.sln b/src/d2dx.sln index 7f477a2..deece4c 100644 --- a/src/d2dx.sln +++ b/src/d2dx.sln @@ -7,8 +7,6 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "d2dx", "d2dx\d2dx.vcxproj", EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "d2dxtests", "d2dxtests\d2dxtests.vcxproj", "{64214704-FE00-4DB6-BEFA-1E622F7262A1}" EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SlashDiabloHD", "..\thirdparty\SlashDiablo-HD\vc\SlashDiabloHD.vcxproj", "{E1A40089-D269-4C75-8815-FFC971085E7C}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x86 = Debug|x86 @@ -23,10 +21,6 @@ Global {64214704-FE00-4DB6-BEFA-1E622F7262A1}.Debug|x86.Build.0 = Debug|Win32 {64214704-FE00-4DB6-BEFA-1E622F7262A1}.Release|x86.ActiveCfg = Release|Win32 {64214704-FE00-4DB6-BEFA-1E622F7262A1}.Release|x86.Build.0 = Release|Win32 - {E1A40089-D269-4C75-8815-FFC971085E7C}.Debug|x86.ActiveCfg = Debug|Win32 - {E1A40089-D269-4C75-8815-FFC971085E7C}.Debug|x86.Build.0 = Debug|Win32 - {E1A40089-D269-4C75-8815-FFC971085E7C}.Release|x86.ActiveCfg = Release|Win32 - {E1A40089-D269-4C75-8815-FFC971085E7C}.Release|x86.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/d2dx/Batch.h b/src/d2dx/Batch.h index 4bdab6a..957a2bf 100644 --- a/src/d2dx/Batch.h +++ b/src/d2dx/Batch.h @@ -26,7 +26,7 @@ namespace d2dx class Batch final { public: - Batch() : + Batch() noexcept : _textureStartAddress(0), _startVertexHigh_textureIndex(0), _textureHash(0), @@ -39,88 +39,76 @@ namespace d2dx { } - inline GameAddress GetGameAddress() const + inline GameAddress GetGameAddress() const noexcept { return (GameAddress)((_isChromaKeyEnabled_gameAddress_paletteIndex & 0x70) >> 4); } - inline void SetGameAddress(GameAddress gameAddress) + inline void SetGameAddress(GameAddress gameAddress) noexcept { assert((int32_t)gameAddress < 8); _isChromaKeyEnabled_gameAddress_paletteIndex &= ~0x70; _isChromaKeyEnabled_gameAddress_paletteIndex |= (uint8_t)((uint32_t)gameAddress << 4) & 0x70; } - inline int32_t GetPaletteIndex() const + inline int32_t GetPaletteIndex() const noexcept { return _isChromaKeyEnabled_gameAddress_paletteIndex & 0xF; } - inline void SetPaletteIndex(int32_t paletteIndex) + inline void SetPaletteIndex(int32_t paletteIndex) noexcept { assert(paletteIndex >= 0 && paletteIndex < 16); _isChromaKeyEnabled_gameAddress_paletteIndex &= 0xF0; _isChromaKeyEnabled_gameAddress_paletteIndex |= paletteIndex; } - inline bool IsChromaKeyEnabled() const + inline bool IsChromaKeyEnabled() const noexcept { return (_isChromaKeyEnabled_gameAddress_paletteIndex & 0x80) != 0; } - inline void SetIsChromaKeyEnabled(bool enable) + inline void SetIsChromaKeyEnabled(bool enable) noexcept { _isChromaKeyEnabled_gameAddress_paletteIndex &= 0x7F; _isChromaKeyEnabled_gameAddress_paletteIndex |= enable ? 0x80 : 0; } - inline PrimitiveType GetPrimitiveType() const - { - return (PrimitiveType)((_textureCategory_primitiveType_combiners >> 2) & 0x03); - } - - inline void SetPrimitiveType(PrimitiveType primitiveType) - { - assert((int32_t)primitiveType < 4); - _textureCategory_primitiveType_combiners &= ~0x0C; - _textureCategory_primitiveType_combiners |= ((uint8_t)primitiveType << 2) & 0x0C; - } - - inline RgbCombine GetRgbCombine() const + inline RgbCombine GetRgbCombine() const noexcept { return (RgbCombine)(_textureCategory_primitiveType_combiners & 0x01); } - inline void SetRgbCombine(RgbCombine rgbCombine) + inline void SetRgbCombine(RgbCombine rgbCombine) noexcept { assert((int32_t)rgbCombine >= 0 && (int32_t)rgbCombine < 2); _textureCategory_primitiveType_combiners &= ~0x01; _textureCategory_primitiveType_combiners |= (uint8_t)rgbCombine & 0x01; } - inline AlphaCombine GetAlphaCombine() const + inline AlphaCombine GetAlphaCombine() const noexcept { return (AlphaCombine)((_textureCategory_primitiveType_combiners >> 1) & 0x01); } - inline void SetAlphaCombine(AlphaCombine alphaCombine) + inline void SetAlphaCombine(AlphaCombine alphaCombine) noexcept { assert((int32_t)alphaCombine >= 0 && (int32_t)alphaCombine < 2); _textureCategory_primitiveType_combiners &= ~0x02; _textureCategory_primitiveType_combiners |= ((uint8_t)alphaCombine << 1) & 0x02; } - inline int32_t GetWidth() const + inline int32_t GetTextureWidth() const noexcept { return 1 << (((_textureHeight_textureWidth_alphaBlend >> 2) & 7) + 1); } - inline int32_t GetHeight() const + inline int32_t GetTextureHeight() const noexcept { return 1 << (((_textureHeight_textureWidth_alphaBlend >> 5) & 7) + 1); } - inline void SetTextureSize(int32_t width, int32_t height) + inline void SetTextureSize(int32_t width, int32_t height) noexcept { DWORD w, h; BitScanReverse(&w, (uint32_t)width); @@ -132,24 +120,24 @@ namespace d2dx _textureHeight_textureWidth_alphaBlend |= (w - 1) << 2; } - inline AlphaBlend GetAlphaBlend() const + inline AlphaBlend GetAlphaBlend() const noexcept { return (AlphaBlend)(_textureHeight_textureWidth_alphaBlend & 3); } - inline void SetAlphaBlend(AlphaBlend alphaBlend) + inline void SetAlphaBlend(AlphaBlend alphaBlend) noexcept { assert((uint32_t)alphaBlend < 4); _textureHeight_textureWidth_alphaBlend &= ~0x03; _textureHeight_textureWidth_alphaBlend |= (uint32_t)alphaBlend & 3; } - inline int32_t GetStartVertex() const + inline int32_t GetStartVertex() const noexcept { return _startVertexLow | ((_startVertexHigh_textureIndex & 0xF000) << 4); } - inline void SetStartVertex(int32_t startVertex) + inline void SetStartVertex(int32_t startVertex) noexcept { assert(startVertex <= 0xFFFFF); _startVertexLow = startVertex & 0xFFFF; @@ -157,77 +145,83 @@ namespace d2dx _startVertexHigh_textureIndex |= (startVertex >> 4) & 0xF000; } - inline uint32_t GetVertexCount() const + inline uint32_t GetVertexCount() const noexcept { return _vertexCount; } - inline void SetVertexCount(uint32_t vertexCount) + inline void SetVertexCount(uint32_t vertexCount) noexcept { assert(vertexCount >= 0 && vertexCount <= 0xFFFF); _vertexCount = vertexCount; } - inline uint32_t SelectColorAndAlpha(uint32_t iteratedColor, uint32_t constantColor) const + inline uint32_t SelectColorAndAlpha(uint32_t iteratedColor, uint32_t constantColor) const noexcept { const auto rgbCombine = GetRgbCombine(); uint32_t result = (rgbCombine == RgbCombine::ConstantColor ? constantColor : iteratedColor) & 0x00FFFFFF; result |= constantColor & 0xFF000000; + + if (GetAlphaBlend() != AlphaBlend::SrcAlphaInvSrcAlpha) + { + result |= 0xFF000000; + } + return result; } - inline uint32_t GetHash() const + inline uint32_t GetHash() const noexcept { return _textureHash; } - void SetTextureHash(uint32_t textureHash) + void SetTextureHash(uint32_t textureHash) noexcept { _textureHash = textureHash; } - inline uint32_t GetTextureAtlas() const + inline uint32_t GetTextureAtlas() const noexcept { return (uint32_t)(_textureAtlas & 7); } - inline void SetTextureAtlas(uint32_t textureAtlas) + inline void SetTextureAtlas(uint32_t textureAtlas) noexcept { assert(textureAtlas < 8); _textureAtlas = textureAtlas & 7; } - inline uint32_t GetTextureIndex() const + inline uint32_t GetTextureIndex() const noexcept { return (uint32_t)(_startVertexHigh_textureIndex & 0x0FFF); } - inline void SetTextureIndex(uint32_t textureIndex) + inline void SetTextureIndex(uint32_t textureIndex) noexcept { assert(textureIndex < 4096); _startVertexHigh_textureIndex &= ~0x0FFF; _startVertexHigh_textureIndex = (uint16_t)(textureIndex & 0x0FFF); } - inline TextureCategory GetTextureCategory() const + inline TextureCategory GetTextureCategory() const noexcept { return (TextureCategory)(_textureCategory_primitiveType_combiners >> 5U); } - inline void SetTextureCategory(TextureCategory category) + inline void SetTextureCategory(TextureCategory category) noexcept { assert((uint32_t)category < 8); _textureCategory_primitiveType_combiners &= ~0xE0; _textureCategory_primitiveType_combiners |= ((uint32_t)category << 5U) & 0xE0; } - inline int32_t GetTextureStartAddress() const + inline int32_t GetTextureStartAddress() const noexcept { const uint32_t startAddress = _textureStartAddress; return (startAddress - 1) << 8; } - inline void SetTextureStartAddress(int32_t startAddress) + inline void SetTextureStartAddress(int32_t startAddress) noexcept { assert(!(startAddress & (D2DX_TMU_ADDRESS_ALIGNMENT-1))); assert(startAddress >= 0 && startAddress <= (D2DX_TMU_MEMORY_SIZE - D2DX_TMU_ADDRESS_ALIGNMENT)); @@ -240,7 +234,7 @@ namespace d2dx _textureStartAddress = startAddress & 0xFFFF; } - inline bool IsValid() const + inline bool IsValid() const noexcept { return _textureStartAddress != 0; } diff --git a/src/d2dx/Buffer.h b/src/d2dx/Buffer.h index f60fbe9..2b807d6 100644 --- a/src/d2dx/Buffer.h +++ b/src/d2dx/Buffer.h @@ -18,6 +18,8 @@ */ #pragma once +#include "ErrorHandling.h" + namespace d2dx { template @@ -29,11 +31,37 @@ namespace d2dx { } - Buffer(uint32_t capacity_) noexcept : + Buffer(uint32_t capacity_, bool zeroFill = false) noexcept : items((T* __restrict)_aligned_malloc(sizeof(T)* capacity_, 256)), capacity(capacity_) { assert(items); + + if (!items) + { + D2DX_FATAL_ERROR("Out of memory."); + } + + if (!zeroFill) + { + return; + } + + ::memset(items, 0, sizeof(T) * capacity); + } + + Buffer(uint32_t capacity_, bool fill, T fillValue) noexcept : + Buffer(capacity_, false) + { + if (!fill) + { + return; + } + + for (uint32_t i = 0; i < capacity; ++i) + { + items[i] = fillValue; + } } Buffer(const Buffer&) = delete; diff --git a/src/d2dx/BuiltinResMod.cpp b/src/d2dx/BuiltinResMod.cpp new file mode 100644 index 0000000..43c4bd4 --- /dev/null +++ b/src/d2dx/BuiltinResMod.cpp @@ -0,0 +1,202 @@ +/* + This file is part of D2DX. + + Copyright (C) 2021 Bolrog + + D2DX is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + D2DX is distributed in the hope that it will be 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 D2DX. If not, see . +*/ +#include "pch.h" +#include "BuiltinResMod.h" +#include "Utils.h" +#include "resource.h" +#include "GameHelper.h" + +using namespace d2dx; + +_Use_decl_annotations_ +BuiltinResMod::BuiltinResMod( + HMODULE hModule, + Size gameSize, + const std::shared_ptr& gameHelper) +{ + if (!hModule || !gameHelper) + { + D2DX_CHECK_HR(E_INVALIDARG); + } + + _isActive = false; + +#ifndef D2DX_UNITTEST + if (IsCompatible(gameHelper.get())) + { + D2DX_LOG("Writing SGD2FreeRes files."); + + if (!WriteResourceToFile(hModule, IDR_SGD2FR_MPQ, "mpq", "d2dx_sgd2freeres.mpq")) + { + D2DX_LOG("Failed to write d2dx_sgd2freeres.mpq"); + } + + if (!WriteResourceToFile(hModule, IDR_SGD2FR_DLL, "dll", "d2dx_sgd2freeres.dll")) + { + D2DX_LOG("Failed to write d2dx_sgd2freeres.mpq"); + } + + if (!WriteConfig(gameSize)) + { + D2DX_LOG("Failed to write SGD2FreeRes configuration."); + } + + D2DX_LOG("Initializing SGD2FreeRes."); + LoadLibraryA("d2dx_sgd2freeres.dll"); + + _isActive = true; + return; + } +#endif +} + +bool BuiltinResMod::IsActive() const +{ + return _isActive; +} + +_Use_decl_annotations_ +bool BuiltinResMod::IsCompatible( + IGameHelper* gameHelper) +{ + auto gameVersion = gameHelper->GetVersion(); + + if (gameVersion != d2dx::GameVersion::Lod109d && + gameVersion != d2dx::GameVersion::Lod113c && + gameVersion != d2dx::GameVersion::Lod113d && + gameVersion != d2dx::GameVersion::Lod114d) + { + D2DX_LOG("Unsupported game version, won't use built-in resolution mod."); + return false; + } + + return true; +} + +_Use_decl_annotations_ +bool BuiltinResMod::WriteResourceToFile( + HMODULE hModule, + int32_t resourceId, + const char* ext, + const char* filename) +{ + void* payloadPtr = nullptr; + DWORD payloadSize = 0; + HRSRC resourceInfo = nullptr; + HGLOBAL resourceData = nullptr; + HANDLE file = nullptr; + DWORD bytesWritten = 0; + bool succeeded = true; + + resourceInfo = FindResourceA(hModule, MAKEINTRESOURCEA(resourceId), ext); + if (!resourceInfo) + { + succeeded = false; + goto end; + } + + resourceData = LoadResource(hModule, resourceInfo); + if (!resourceData) + { + succeeded = false; + goto end; + } + + payloadSize = SizeofResource(hModule, resourceInfo); + payloadPtr = LockResource(resourceData); + if (!payloadPtr || !payloadSize) + { + succeeded = false; + goto end; + } + + succeeded = DecompressLZMAToFile((const uint8_t*)payloadPtr, payloadSize, filename); + +end: + if (resourceData) + { + UnlockResource(resourceData); + } + if (file) + { + CloseHandle(file); + } + + return succeeded; +} + +_Use_decl_annotations_ +bool BuiltinResMod::WriteConfig( + _In_ Size gameSize) +{ + HANDLE file = nullptr; + DWORD bytesWritten = 0; + bool succeeded = true; + + file = CreateFileA("d2dx_sgd2freeres.json", GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + if (!file) + { + succeeded = false; + goto end; + } + + char configStr[1024]; + + sprintf_s(configStr, + "{\r\n" + " \"D2DX\" : \"PLEASE DO NOT EDIT THIS CONFIGURATION FILE. IT IS GENERATED ON EVERY STARTUP.\",\r\n" + " \"SlashGaming Diablo II Free Resolution\": {\r\n" + " \"!!!Metadata (Do not modify)!!!\": {\r\n" + " \"Major Version A\": 3,\r\n" + " \"Major Version B\" : 0,\r\n" + " \"Minor Version A\" : 1,\r\n" + " \"Minor Version B\" : 0\r\n" + " },\r\n" + " \"Ingame Resolutions\": [\r\n" + " \"640x480\",\r\n" + " \"800x600\",\r\n" + " \"%ix%i\"\r\n" + " ],\r\n" + " \"Ingame Resolution Mode\" : 2,\r\n" + " \"Main Menu Resolution\" : \"800x600\",\r\n" + " \"Custom MPQ File\": \"d2dx_sgd2freeres.mpq\",\r\n" + " \"Enable Screen Border Frame?\" : true,\r\n" + " \"Use Original Screen Border Frame?\" : false,\r\n" + " \"Use 800 Interface Bar?\" : true\r\n" + " },\r\n" + " \"!!!Globals!!!\": {\r\n" + " \"Config Tab Width\": 4\r\n" + " }\r\n" + "}\r\n", + gameSize.width, gameSize.height); + + if (!WriteFile(file, configStr, strlen(configStr), &bytesWritten, nullptr)) + { + succeeded = false; + goto end; + } + +end: + if (file) + { + CloseHandle(file); + } + + return succeeded; +} diff --git a/src/d2dx/BuiltinResMod.h b/src/d2dx/BuiltinResMod.h new file mode 100644 index 0000000..b319938 --- /dev/null +++ b/src/d2dx/BuiltinResMod.h @@ -0,0 +1,53 @@ +/* + This file is part of D2DX. + + Copyright (C) 2021 Bolrog + + D2DX is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + D2DX is distributed in the hope that it will be 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 D2DX. If not, see . +*/ +#pragma once + +#include "IBuiltinResMod.h" +#include "IGameHelper.h" + +namespace d2dx +{ + class BuiltinResMod final : public IBuiltinResMod + { + public: + BuiltinResMod( + _In_ HMODULE hModule, + _In_ Size gameSize, + _In_ const std::shared_ptr& gameHelper); + + virtual ~BuiltinResMod() noexcept {} + + virtual bool IsActive() const override; + + private: + bool IsCompatible( + _In_ IGameHelper* gameHelper); + + bool WriteResourceToFile( + _In_ HMODULE hModule, + _In_ int32_t resourceId, + _In_z_ const char* ext, + _In_z_ const char* filename); + + bool WriteConfig( + _In_ Size gameSize); + + bool _isActive = false; + }; +} diff --git a/src/d2dx/CompatibilityModeDisabler.cpp b/src/d2dx/CompatibilityModeDisabler.cpp new file mode 100644 index 0000000..154d08d --- /dev/null +++ b/src/d2dx/CompatibilityModeDisabler.cpp @@ -0,0 +1,147 @@ +/* + This file is part of D2DX. + + Copyright (C) 2021 Bolrog + + D2DX is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + D2DX is distributed in the hope that it will be 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 D2DX. If not, see . +*/ +#include "pch.h" +#include "CompatibilityModeDisabler.h" +#include "Utils.h" + +using namespace d2dx; + +static const wchar_t* osCompatibilityOptions[] = +{ + L"WIN95", + L"WIN98", + L"WINXP", + L"VISTA", + L"WIN7", + L"WIN8" +}; + +CompatibilityModeDisabler::CompatibilityModeDisabler() +{ +} + +void CompatibilityModeDisabler::DisableCompatibilityMode() +{ + bool fixedCompatibilityMode = false; + + if (FixCompatibilityMode(HKEY_CURRENT_USER, L"Game.exe")) + { + fixedCompatibilityMode = true; + } + + if (FixCompatibilityMode(HKEY_CURRENT_USER, L"Diablo II.exe")) + { + fixedCompatibilityMode = true; + } + + if (fixedCompatibilityMode) + { + MessageBox(NULL, L"D2DX detected that 'compatibility mode' (e.g. for Windows XP) was set for the game, but this isn't necessary for D2DX and will cause problems.\n\nThis has now been fixed for you. Please re-launch the game.", L"D2DX", MB_OK); + TerminateProcess(GetCurrentProcess(), -1); + } + + // If compat mode is set for LOCAL_MACHINE, we can't see it in the registry and can't fix that... so try to detect it at least. + + // Get the reported (false) OS version. + auto reportedWindowsVersion = d2dx::GetWindowsVersion(); + auto realWindowsVersion = d2dx::GetActualWindowsVersion(); + + if (reportedWindowsVersion.major != realWindowsVersion.major) + { + MessageBox(NULL, L"D2DX detected that 'compatibility mode' (e.g. for Windows XP) was set for the game, but this isn't necessary for D2DX and will cause problems.\n\nPlease disable 'compatibility mode' for both 'Game.exe' and 'Diablo II.exe'.", L"D2DX", MB_OK); + TerminateProcess(GetCurrentProcess(), -1); + } + + if (reportedWindowsVersion.major < 6 || (reportedWindowsVersion.major == 6 && reportedWindowsVersion.minor == 0)) + { + MessageBox(NULL, L"D2DX requires Windows 7 or above.", L"D2DX", MB_OK); + TerminateProcess(GetCurrentProcess(), -1); + } +} + +_Use_decl_annotations_ +bool CompatibilityModeDisabler::FixCompatibilityMode( + _In_ HKEY hRootKey, + _In_z_ const wchar_t* filename) +{ + HKEY hKey; + LPCTSTR compatibilityLayersKey = L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\AppCompatFlags\\Layers"; + LONG result = RegOpenKeyEx(hRootKey, compatibilityLayersKey, 0, KEY_READ | KEY_WRITE, &hKey); + if (result != ERROR_SUCCESS) + { + return false; + } + + Buffer fullPath(2048); + uint32_t numChars = GetModuleFileName(GetModuleHandle(nullptr), fullPath.items, fullPath.capacity); + if (numChars < 1 || numChars >= fullPath.capacity) + { + RegCloseKey(hKey); + return false; + } + + bool hasFixed = false; + + Buffer value(2048); + uint32_t type = REG_SZ; + uint32_t size = value.capacity; + + result = RegGetValue(hKey, nullptr, fullPath.items, RRF_RT_REG_SZ, (LPDWORD)&type, nullptr, (LPDWORD)&size); + + if (result == ERROR_SUCCESS) + { + if (size > value.capacity) + { + RegCloseKey(hKey); + return false; + } + + result = RegGetValue(hKey, nullptr, fullPath.items, RRF_RT_REG_SZ, (LPDWORD)&type, (LPBYTE)value.items, (LPDWORD)&size); + + if (result == ERROR_SUCCESS) + { + if (HasOsCompatibilityOption(value.items)) + { + result = RegDeleteValue(hKey, fullPath.items); + assert(result == ERROR_SUCCESS); + + RegCloseKey(hKey); + return true; + } + } + } + + RegCloseKey(hKey); + return false; +} + +_Use_decl_annotations_ +bool CompatibilityModeDisabler::HasOsCompatibilityOption( + const wchar_t* options) +{ + for (int32_t i = 0; i < ARRAYSIZE(osCompatibilityOptions); ++i) + { + if (wcsstr(options, osCompatibilityOptions[i])) + { + return true; + } + } + + return false; +} diff --git a/src/d2dx/CompatibilityModeDisabler.h b/src/d2dx/CompatibilityModeDisabler.h new file mode 100644 index 0000000..5f4265c --- /dev/null +++ b/src/d2dx/CompatibilityModeDisabler.h @@ -0,0 +1,40 @@ +/* + This file is part of D2DX. + + Copyright (C) 2021 Bolrog + + D2DX is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + D2DX is distributed in the hope that it will be 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 D2DX. If not, see . +*/ +#pragma once + +namespace d2dx +{ + class CompatibilityModeDisabler final + { + public: + CompatibilityModeDisabler(); + + ~CompatibilityModeDisabler() noexcept {} + + void DisableCompatibilityMode(); + + private: + bool FixCompatibilityMode( + _In_ HKEY hRootKey, + _In_z_ const wchar_t* filename); + + bool HasOsCompatibilityOption( + _In_z_ const wchar_t* options); + }; +} diff --git a/src/d2dx/Constants.hlsli b/src/d2dx/Constants.hlsli index f9376e9..f3c7141 100644 --- a/src/d2dx/Constants.hlsli +++ b/src/d2dx/Constants.hlsli @@ -19,30 +19,12 @@ cbuffer Constants : register(b0) { - float2 vertexOffset; - float2 vertexScale; - float2 screenSize; - float2 dummy; -}; - -struct PixelShaderInput -{ - float4 pos : SV_POSITION; - float2 tc : TEXCOORD0; - half4 color : COLOR0; - nointerpolation uint2 misc : TEXCOORD1; + float2 c_screenSize : packoffset(c0); + float2 c_invScreenSize : packoffset(c0.z); + uint4 flagsx : packoffset(c1); }; SamplerState PointSampler : register(s0); SamplerState BilinearSampler : register(s1); -#define MISC_RGB_ITERATED_COLOR_MULTIPLIED_BY_TEXTURE (0 << 0) -#define MISC_RGB_CONSTANT_COLOR (1 << 0) -#define MISC_RGB_MASK (1 << 0) - -#define MISC_ALPHA_ONE (0 << 1) -#define MISC_ALPHA_TEXTURE (1 << 1) -#define MISC_ALPHA_MASK (1 << 1) - -#define MISC_CHROMAKEY_ENABLED_MASK (1 << 2) -#define MISC_CHROMAKEY_THRESHOLD_LOW_MASK (1 << 3) +#define FLAGS_CHROMAKEY_ENABLED_MASK 1 diff --git a/src/d2dx/D2DXConfigurator.cpp b/src/d2dx/D2DXConfigurator.cpp new file mode 100644 index 0000000..b5e05f5 --- /dev/null +++ b/src/d2dx/D2DXConfigurator.cpp @@ -0,0 +1,131 @@ +/* + This file is part of D2DX. + + Copyright (C) 2021 Bolrog + + D2DX is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + D2DX is distributed in the hope that it will be 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 D2DX. If not, see . +*/ +#include "pch.h" +#include "D2DXContextFactory.h" +#include "D2DXConfigurator.h" + +using namespace d2dx; +using namespace std; +using namespace Microsoft::WRL; + +class D2DXConfigurator : public ID2DXConfigurator +{ +public: + D2DXConfigurator() noexcept + { + } + + virtual ~D2DXConfigurator() noexcept + { + } + + STDMETHOD(QueryInterface)( + /* [in] */ REFIID riid, + /* [iid_is][out] */ _COM_Outptr_ void __RPC_FAR* __RPC_FAR* ppvObject) + { + if (IsEqualIID(riid, __uuidof(IUnknown)) || + IsEqualIID(riid, __uuidof(ID2DXConfigurator))) + { + *ppvObject = this; + return S_OK; + } + else + { + *ppvObject = nullptr; + return E_NOINTERFACE; + } + } + + STDMETHOD_(ULONG, AddRef)(void) + { + /* We just pretend to be refcounted. */ + return 1U; + } + + STDMETHOD_(ULONG, Release)(void) + { + /* We just pretend to be refcounted. */ + return 1U; + } + + STDMETHOD(SetCustomResolution)( + int32_t width, + int32_t height) noexcept + { + auto d2dxContext = D2DXContextFactory::GetInstance(false); + + if (!d2dxContext) + { + return E_POINTER; + } + + d2dxContext->SetCustomResolution({ width, height }); + + return S_OK; + } + + STDMETHOD(GetSuggestedCustomResolution)( + _Out_ int32_t* width, + _Out_ int32_t* height) noexcept + { + if (!width || !height) + { + return E_INVALIDARG; + } + + auto d2dxContext = D2DXContextFactory::GetInstance(false); + + if (!d2dxContext) + { + return E_POINTER; + } + + Size size = d2dxContext->GetSuggestedCustomResolution(); + *width = size.width; + *height = size.height; + + return S_OK; + } +}; + +namespace d2dx +{ + ID2DXConfigurator* GetConfiguratorInternal() + { + static D2DXConfigurator configurator; + return &configurator; + } +} + +extern "C" +{ + D2DX_EXPORTED ID2DXConfigurator* __stdcall D2DXGetConfigurator() + { + auto d2dxInstance = D2DXContextFactory::GetInstance(false); + + if (!d2dxInstance) + { + return nullptr; + } + + d2dxInstance->DisableBuiltinResMod(); + + return GetConfiguratorInternal(); + } +} diff --git a/src/d2dx/D2DXConfigurator.h b/src/d2dx/D2DXConfigurator.h new file mode 100644 index 0000000..3edd49d --- /dev/null +++ b/src/d2dx/D2DXConfigurator.h @@ -0,0 +1,116 @@ +/* + This file is part of D2DX. + + Copyright (C) 2021 Bolrog + + D2DX is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + D2DX is distributed in the hope that it will be 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 D2DX. If not, see . +*/ +#pragma once +#include +#include + +#ifdef D2DX_EXPORT +#define D2DX_EXPORTED __declspec(dllexport) +#else +#define D2DX_EXPORTED +#endif + +/* + ID2DXConfigurator is a pseudo-COM interface that can be used to integrate with D2DX. + It is suitable e.g. for "high-res" mods that want to use arbitrary resolutions. + + It can be accessed via the helper d2dx::TryGetConfigurator, or manually by + using LoadLibrary/GetProcAddress to get a pointer to _D2DXGetConfigurator@4. + + Note that the object is not really refcounted and there's no need to call AddRef/Release. + + Note also that the built-in resolution mod in D2DX will be disabled if D2DXGetConfigurator + is called prior to the first Glide call made. + + Example use: + + ID2DXConfigurator* d2dxConfigurator = d2dx::TryGetConfigurator(); + if (d2dxConfigurator) + { + d2dxConfigurator->SetCustomResolution(1000, 500); + } + grSstWinOpen(hWnd, ...) + +*/ +MIDL_INTERFACE("B11C5FA4-983F-4E34-9E43-BD82F9CCDB65") + ID2DXConfigurator : public IUnknown +{ +public: + /* + Tell D2DX that the following call(s) to grSstWinOpen intends this custom resolution, + and to ignore the 'screen_resolution' argument. To return to normal behavior, call + this method with a width and height of zero. + + Returns S_OK on success and an HRESULT error code otherwise. + */ + virtual HRESULT STDMETHODCALLTYPE SetCustomResolution( + int width, + int height) = 0; + + /* + Get a suggested custom resolution from D2DX (typically close to 640x480 or 800x600, + but in the aspect ratio of the monitor). + + This method allows matching D2DX's default behavior. + + Returns S_OK on success and an HRESULT error code otherwise. + */ + virtual HRESULT STDMETHODCALLTYPE GetSuggestedCustomResolution( + /* [out] */ int* width, + /* [out] */ int* height) = 0; +}; + +extern "C" +{ + D2DX_EXPORTED ID2DXConfigurator* __stdcall D2DXGetConfigurator(); +} + +#if __cplusplus > 199711L +namespace d2dx +{ + namespace detail + { + class D2DXGetConfiguratorFuncLoader final + { + public: + D2DXGetConfiguratorFuncLoader() : _d2dxGetConfiguratorFunc{ nullptr } + { + HINSTANCE hD2DX = LoadLibraryA("glide3x.dll"); + if (hD2DX) + { + _d2dxGetConfiguratorFunc = (D2DXGetConfiguratorFunc)GetProcAddress( + hD2DX, "_D2DXGetConfigurator@4"); + } + } + ID2DXConfigurator* operator()() + { + return _d2dxGetConfiguratorFunc ? _d2dxGetConfiguratorFunc() : nullptr; + } + private: + typedef ID2DXConfigurator* (__stdcall *D2DXGetConfiguratorFunc)(); + D2DXGetConfiguratorFunc _d2dxGetConfiguratorFunc; + }; + } + static ID2DXConfigurator* TryGetConfigurator() + { + static detail::D2DXGetConfiguratorFuncLoader d2dxGetConfiguratorFuncLoader; + return d2dxGetConfiguratorFuncLoader(); + } +} +#endif diff --git a/src/d2dx/D2DXContext.cpp b/src/d2dx/D2DXContext.cpp index fa6d8a0..bd31663 100644 --- a/src/d2dx/D2DXContext.cpp +++ b/src/d2dx/D2DXContext.cpp @@ -18,54 +18,105 @@ */ #include "pch.h" #include "D2DXContext.h" +#include "Detours.h" +#include "BuiltinResMod.h" +#include "RenderContext.h" #include "GameHelper.h" -#include "GlideHelpers.h" -#include "Simd.h" +#include "SimdSse2.h" +#include "Metrics.h" #include "Utils.h" +#include "Vertex.h" #include "dx256_bmp.h" using namespace d2dx; using namespace DirectX::PackedVector; using namespace std; -D2DXContext::D2DXContext() : - _renderFilter(0), - _capturingFrame(false), +extern D2::UnitAny* currentlyDrawingUnit; +extern uint32_t currentlyDrawingWeatherParticles; +extern uint32_t* currentlyDrawingWeatherParticleIndexPtr; + +#define D2DX_GLIDE_ALPHA_BLEND(rgb_sf, rgb_df, alpha_sf, alpha_df) \ + (uint16_t)(((rgb_sf & 0xF) << 12) | ((rgb_df & 0xF) << 8) | ((alpha_sf & 0xF) << 4) | (alpha_df & 0xF)) + +static Options GetCommandLineOptions() +{ + Options options; + auto fileData = ReadTextFile("d2dx.cfg"); + options.ApplyCfg(fileData.items); + options.ApplyCommandLine(GetCommandLineA()); + return options; +} + +_Use_decl_annotations_ +D2DXContext::D2DXContext( + const std::shared_ptr& gameHelper, + const std::shared_ptr& simd, + const std::shared_ptr& compatibilityModeDisabler) : + _gameHelper{ gameHelper }, + _simd{ simd }, + _compatibilityModeDisabler{ compatibilityModeDisabler }, _frame(0), _majorGameState(MajorGameState::Unknown), - _vertexLayout(0xFF), - _tmuMemory(D2DX_TMU_MEMORY_SIZE), - _sideTmuMemory(D2DX_SIDE_TMU_MEMORY_SIZE), - _constantColor(0xFFFFFFFF), - _paletteKeys(D2DX_MAX_PALETTES), - _gammaTable(256), + _paletteKeys(D2DX_MAX_PALETTES, true), _batchCount(0), _batches(D2DX_MAX_BATCHES_PER_FRAME), _vertexCount(0), - _vertices(D2DX_MAX_VERTICES_PER_FRAME) + _vertices(D2DX_MAX_VERTICES_PER_FRAME), + _customGameSize{ 0,0 }, + _suggestedGameSize{ 0, 0 }, + _options{ GetCommandLineOptions() }, + _lastScreenOpenMode{ 0 }, + _surfaceIdTracker{ gameHelper }, + _textMotionPredictor{ gameHelper }, + _unitMotionPredictor{ gameHelper }, + _weatherMotionPredictor{ gameHelper }, + _featureFlags{ 0 } { - memset(_paletteKeys.items, 0, sizeof(uint32_t) * _paletteKeys.capacity); + _threadId = GetCurrentThreadId(); - const char* commandLine = GetCommandLineA(); - bool windowed = strstr(commandLine, "-w") != nullptr; - _options.skipLogo = strstr(commandLine, "-dxskiplogo") != nullptr || strstr(commandLine, "-gxskiplogo") != nullptr; - _options.noVSync = strstr(commandLine, "-dxnovsync") != nullptr; + if (!_options.GetFlag(OptionsFlag::NoCompatModeFix)) + { + _compatibilityModeDisabler->DisableCompatibilityMode(); + } + + auto apparentWindowsVersion = GetWindowsVersion(); + auto actualWindowsVersion = GetActualWindowsVersion(); + D2DX_LOG("Apparent Windows version: %u.%u (build %u).", apparentWindowsVersion.major, apparentWindowsVersion.minor, apparentWindowsVersion.build); + D2DX_LOG("Actual Windows version: %u.%u (build %u).", actualWindowsVersion.major, actualWindowsVersion.minor, actualWindowsVersion.build); - bool dxscale2 = strstr(commandLine, "-dxscale2") != nullptr || strstr(commandLine, "-gxscale2") != nullptr; - bool dxscale3 = strstr(commandLine, "-dxscale3") != nullptr || strstr(commandLine, "-gxscale3") != nullptr; - _options.defaultZoomLevel = - dxscale3 ? 3 : - dxscale2 ? 2 : - 1; + if (!_options.GetFlag(OptionsFlag::NoResMod)) + { + try + { + _builtinResMod = std::make_unique(GetModuleHandleA("glide3x.dll"), GetSuggestedCustomResolution(), _gameHelper); + if (!_builtinResMod->IsActive()) + { + _options.SetFlag(OptionsFlag::NoResMod, true); + } + } + catch (...) + { + _options.SetFlag(OptionsFlag::NoResMod, true); + } + } - _options.screenMode = windowed ? ScreenMode::Windowed : ScreenMode::FullscreenDefault; + if (!_options.GetFlag(OptionsFlag::NoFpsFix)) + { + _gameHelper->TryApplyInGameFpsFix(); + _gameHelper->TryApplyMenuFpsFix(); + _gameHelper->TryApplyInGameSleepFixes(); + } } -D2DXContext::~D2DXContext() +D2DXContext::~D2DXContext() noexcept { + DetachLateDetours(); } -const char* D2DXContext::OnGetString(uint32_t pname) +_Use_decl_annotations_ +const char* D2DXContext::OnGetString( + uint32_t pname) { switch (pname) { @@ -83,7 +134,11 @@ const char* D2DXContext::OnGetString(uint32_t pname) return NULL; } -uint32_t D2DXContext::OnGet(uint32_t pname, uint32_t plength, int32_t* params) +_Use_decl_annotations_ +uint32_t D2DXContext::OnGet( + uint32_t pname, + uint32_t plength, + int32_t* params) { switch (pname) { @@ -119,59 +174,55 @@ uint32_t D2DXContext::OnGet(uint32_t pname, uint32_t plength, int32_t* params) } } -bool D2DXContext::IsDrawingDisabled() const +_Use_decl_annotations_ +void D2DXContext::OnSstWinOpen( + uint32_t hWnd, + int32_t width, + int32_t height) { - return false; -} - -bool D2DXContext::IsCapturingFrame() const -{ - return _capturingFrame; -} + _threadId = GetCurrentThreadId(); -void D2DXContext::LogGlideCall(const char* s) -{ -} - -void D2DXContext::OnGlideInit() -{ -} - -void D2DXContext::OnGlideShutdown() -{ -} + Size windowSize = _gameHelper->GetConfiguredGameSize(); + if (!_options.GetFlag(OptionsFlag::NoResMod)) + { + windowSize = { 800,600 }; + } -void D2DXContext::OnSstWinOpen(uint32_t hWnd, int32_t width, int32_t height) -{ - int32_t windowWidth, windowHeight; - _gameHelper.GetConfiguredGameSize(&windowWidth, &windowHeight); + Size gameSize{ width, height }; - if (_customWidth > 0) + if (_customGameSize.width > 0) { - width = _customWidth; - height = _customHeight; + gameSize = _customGameSize; + _customGameSize = { 0,0 }; } - if (width > 800) + if (gameSize.width != 640 || gameSize.height != 480) { - windowWidth = width; - windowHeight = height; + windowSize = gameSize; } - if (!_d3d11Context) + if (!_renderContext) { - auto simd = Simd::Create(); - auto textureProcessor = make_shared(); - _d3d11Context = make_unique((HWND)hWnd, windowWidth * _options.defaultZoomLevel, windowHeight * _options.defaultZoomLevel, width, height, _options, simd, textureProcessor); + auto initialScreenMode = strstr(GetCommandLineA(), "-w") ? + ScreenMode::Windowed : + ScreenMode::FullscreenDefault; + + _renderContext = std::make_shared( + (HWND)hWnd, + gameSize, + windowSize * _options.GetWindowScale(), + initialScreenMode, + this, + _simd); } else { - if (width > windowWidth || height > windowHeight) + if (width > windowSize.width || height > windowSize.height) { - windowWidth = width; - windowHeight = height; + windowSize.width = width; + windowSize.height = height; } - _d3d11Context->SetSizes(width, height, windowWidth * _options.defaultZoomLevel, windowHeight * _options.defaultZoomLevel); + _renderContext->SetSizes(gameSize, windowSize * _options.GetWindowScale()); } _batchCount = 0; @@ -179,42 +230,71 @@ void D2DXContext::OnSstWinOpen(uint32_t hWnd, int32_t width, int32_t height) _scratchBatch = Batch(); } -void D2DXContext::OnVertexLayout(uint32_t param, int32_t offset) +_Use_decl_annotations_ +void D2DXContext::OnVertexLayout( + uint32_t param, + int32_t offset) { switch (param) { case GR_PARAM_XY: - _vertexLayout = (_vertexLayout & 0xFFFF) | ((offset & 0xFF) << 16); - break; - case GR_PARAM_ST0: - case GR_PARAM_ST1: - _vertexLayout = (_vertexLayout & 0xFF00FF) | ((offset & 0xFF) << 8); + assert(offset == 0); break; case GR_PARAM_PARGB: - _vertexLayout = (_vertexLayout & 0xFFFF00) | (offset & 0xFF); + assert(offset == 8); + break; + case GR_PARAM_ST0: + assert(offset == 16); break; } } -void D2DXContext::OnTexDownload(uint32_t tmu, const uint8_t* sourceAddress, uint32_t startAddress, int32_t width, int32_t height) +_Use_decl_annotations_ +void D2DXContext::OnTexDownload( + uint32_t tmu, + const uint8_t* sourceAddress, + uint32_t startAddress, + int32_t width, + int32_t height) { assert(tmu == 0 && (startAddress & 255) == 0); + if (!(tmu == 0 && (startAddress & 255) == 0)) + { + return; + } + + _textureHasher.Invalidate(startAddress); uint32_t memRequired = (uint32_t)(width * height); - auto pStart = _tmuMemory.items + startAddress; - auto pEnd = _tmuMemory.items + startAddress + memRequired; - assert(pEnd <= (_tmuMemory.items + _tmuMemory.capacity)); - memcpy_s(pStart, _tmuMemory.capacity - startAddress, sourceAddress, memRequired); + auto pStart = _glideState.tmuMemory.items + startAddress; + auto pEnd = _glideState.tmuMemory.items + startAddress + memRequired; + assert(pEnd <= (_glideState.tmuMemory.items + _glideState.tmuMemory.capacity)); + memcpy_s(pStart, _glideState.tmuMemory.capacity - startAddress, sourceAddress, memRequired); } -void D2DXContext::OnTexSource(uint32_t tmu, uint32_t startAddress, int32_t width, int32_t height) +_Use_decl_annotations_ +void D2DXContext::OnTexSource( + uint32_t tmu, + uint32_t startAddress, + int32_t width, + int32_t height) { assert(tmu == 0 && (startAddress & 255) == 0); + if (!(tmu == 0 && (startAddress & 255) == 0)) + { + return; + } + + _readVertexState.isDirty = true; - uint8_t* pixels = _tmuMemory.items + startAddress; + uint8_t* pixels = _glideState.tmuMemory.items + startAddress; const uint32_t pixelsSize = width * height; - uint32_t hash = fnv_32a_buf(pixels, pixelsSize, FNV1_32A_INIT); + int32_t stShift = 0; + _BitScanReverse((DWORD*)&stShift, max(width, height)); + _glideState.stShift = 8 - stShift; + + uint32_t hash = _textureHasher.GetHash(startAddress, pixels, pixelsSize); /* Patch the '5' to not look like '6'. */ if (hash == 0x8a12f6bb) @@ -227,7 +307,16 @@ void D2DXContext::OnTexSource(uint32_t tmu, uint32_t startAddress, int32_t width _scratchBatch.SetTextureStartAddress(startAddress); _scratchBatch.SetTextureHash(hash); _scratchBatch.SetTextureSize(width, height); - _scratchBatch.SetTextureCategory(_gameHelper.GetTextureCategoryFromHash(hash)); + + if (_scratchBatch.GetTextureCategory() == TextureCategory::Unknown) + { + _scratchBatch.SetTextureCategory(_gameHelper->GetTextureCategoryFromHash(hash)); + } + + if (_options.GetFlag(OptionsFlag::DbgDumpTextures)) + { + DumpTexture(hash, width, height, pixels, pixelsSize, (uint32_t)_scratchBatch.GetTextureCategory(), _glideState.palettes.items + _scratchBatch.GetPaletteIndex() * 256); + } } void D2DXContext::CheckMajorGameState() @@ -241,41 +330,30 @@ void D2DXContext::CheckMajorGameState() _majorGameState = MajorGameState::Menus; - if (_gameHelper.ScreenOpenMode() == 3) + if (_gameHelper->IsInGame()) { _majorGameState = MajorGameState::InGame; + AttachLateDetours(_gameHelper.get(), this); } else { for (int32_t i = 0; i < batchCount; ++i) { const Batch& batch = _batches.items[i]; + const int32_t y0 = _vertices.items[batch.GetStartVertex()].GetY(); - if ((GameAddress)batch.GetGameAddress() == GameAddress::DrawFloor) + if (batch.GetHash() == 0x4bea7b80 && y0 >= 550) { - _majorGameState = MajorGameState::InGame; + _majorGameState = MajorGameState::TitleScreen; break; } } - - if (_majorGameState == MajorGameState::Menus) - { - for (int32_t i = 0; i < batchCount; ++i) - { - const Batch& batch = _batches.items[i]; - const float y0 = _vertices.items[batch.GetStartVertex()].GetY(); - - if (batch.GetHash() == 0x4bea7b80 && y0 >= 550) - { - _majorGameState = MajorGameState::TitleScreen; - break; - } - } - } } } -void D2DXContext::DrawBatches() +_Use_decl_annotations_ +void D2DXContext::DrawBatches( + uint32_t startVertexLocation) { const int32_t batchCount = (int32_t)_batchCount; @@ -288,7 +366,7 @@ void D2DXContext::DrawBatches() if (!batch.IsValid()) { - DEBUG_PRINT("Skipping batch %i, it is invalid.", i); + D2DX_DEBUG_LOG("Skipping batch %i, it is invalid.", i); continue; } @@ -298,13 +376,12 @@ void D2DXContext::DrawBatches() } else { - if (_d3d11Context->GetTextureCache(batch) != _d3d11Context->GetTextureCache(mergedBatch) || - (batch.GetTextureAtlas() != mergedBatch.GetTextureAtlas()) || + if (_renderContext->GetTextureCache(batch) != _renderContext->GetTextureCache(mergedBatch) || + batch.GetTextureAtlas() != mergedBatch.GetTextureAtlas() || batch.GetAlphaBlend() != mergedBatch.GetAlphaBlend() || - batch.GetPrimitiveType() != mergedBatch.GetPrimitiveType() || ((mergedBatch.GetVertexCount() + batch.GetVertexCount()) > 65535)) { - _d3d11Context->Draw(mergedBatch); + _renderContext->Draw(mergedBatch, startVertexLocation); ++drawCalls; mergedBatch = batch; } @@ -317,43 +394,96 @@ void D2DXContext::DrawBatches() if (mergedBatch.IsValid()) { - _d3d11Context->Draw(mergedBatch); + _renderContext->Draw(mergedBatch, startVertexLocation); ++drawCalls; } - - if (!(_frame & 31)) + if (!(_frame & 255)) { - DEBUG_PRINT("Nr draw calls: %i", drawCalls); + D2DX_DEBUG_LOG("Nr draw calls: %i", drawCalls); } } + void D2DXContext::OnBufferSwap() { CheckMajorGameState(); InsertLogoOnTitleScreen(); - _d3d11Context->BulkWriteVertices(_vertices.items, _vertexCount); + if (IsFeatureEnabled(Feature::UnitMotionPrediction) && + _majorGameState == MajorGameState::InGame) + { + const Offset offset = _unitMotionPredictor.GetOffset(_gameHelper->GetPlayerUnit()); + + for (uint32_t i = 0; i < _batchCount; ++i) + { + const auto& batch = _batches.items[i]; + auto surfaceId = _vertices.items[batch.GetStartVertex()].GetSurfaceId(); - DrawBatches(); + if (surfaceId != D2DX_SURFACE_ID_USER_INTERFACE && + batch.GetTextureCategory() != TextureCategory::Player) + { + const auto batchVertexCount = batch.GetVertexCount(); + auto vertexIndex = batch.GetStartVertex(); + for (uint32_t j = 0; j < batchVertexCount; ++j) + { + _vertices.items[vertexIndex++].AddOffset( + -offset.x, + -offset.y); + } + } + } + } + + auto startVertexLocation = _renderContext->BulkWriteVertices(_vertices.items, _vertexCount); + + DrawBatches(startVertexLocation); - _d3d11Context->Present(); + _skipCountingSleep = true; + _renderContext->Present(); + _skipCountingSleep = false; ++_frame; + if (!(_frame & 255)) + { + _textureHasher.PrintStats(); + + D2DX_DEBUG_LOG("Sleeps/frame: %.2f", _sleeps / 256.0f); + _sleeps = 0; + } + _batchCount = 0; _vertexCount = 0; + + _lastScreenOpenMode = _gameHelper->ScreenOpenMode(); + + _surfaceIdTracker.OnNewFrame(); + + _renderContext->GetCurrentMetrics(&_gameSize, nullptr, nullptr); + + _avgDir = { 0.0f, 0.0f }; + + _readVertexState.isDirty = true; } -void D2DXContext::OnColorCombine(GrCombineFunction_t function, GrCombineFactor_t factor, GrCombineLocal_t local, GrCombineOther_t other, bool invert) +_Use_decl_annotations_ +void D2DXContext::OnColorCombine( + GrCombineFunction_t function, + GrCombineFactor_t factor, + GrCombineLocal_t local, + GrCombineOther_t other, + bool invert) { auto rgbCombine = RgbCombine::ColorMultipliedByTexture; - if (function == GR_COMBINE_FUNCTION_SCALE_OTHER && factor == GR_COMBINE_FACTOR_LOCAL && local == GR_COMBINE_LOCAL_ITERATED && other == GR_COMBINE_OTHER_TEXTURE) + if (function == GR_COMBINE_FUNCTION_SCALE_OTHER && factor == GR_COMBINE_FACTOR_LOCAL && + local == GR_COMBINE_LOCAL_ITERATED && other == GR_COMBINE_OTHER_TEXTURE) { rgbCombine = RgbCombine::ColorMultipliedByTexture; } - else if (function == GR_COMBINE_FUNCTION_LOCAL && factor == GR_COMBINE_FACTOR_ZERO && local == GR_COMBINE_LOCAL_CONSTANT && other == GR_COMBINE_OTHER_CONSTANT) + else if (function == GR_COMBINE_FUNCTION_LOCAL && factor == GR_COMBINE_FACTOR_ZERO && + local == GR_COMBINE_LOCAL_CONSTANT && other == GR_COMBINE_OTHER_CONSTANT) { rgbCombine = RgbCombine::ConstantColor; } @@ -363,34 +493,43 @@ void D2DXContext::OnColorCombine(GrCombineFunction_t function, GrCombineFactor_t } _scratchBatch.SetRgbCombine(rgbCombine); + + _readVertexState.isDirty = true; } -void D2DXContext::OnAlphaCombine(GrCombineFunction_t function, GrCombineFactor_t factor, GrCombineLocal_t local, GrCombineOther_t other, bool invert) +_Use_decl_annotations_ +void D2DXContext::OnAlphaCombine( + GrCombineFunction_t function, + GrCombineFactor_t factor, + GrCombineLocal_t local, + GrCombineOther_t other, + bool invert) { auto alphaCombine = AlphaCombine::One; - if (function == GR_COMBINE_FUNCTION_ZERO && factor == GR_COMBINE_FACTOR_ZERO && local == GR_COMBINE_LOCAL_CONSTANT && other == GR_COMBINE_OTHER_CONSTANT) - { - alphaCombine = AlphaCombine::One; - } - else if (function == GR_COMBINE_FUNCTION_LOCAL && factor == GR_COMBINE_FACTOR_ZERO && local == GR_COMBINE_LOCAL_CONSTANT && other == GR_COMBINE_OTHER_CONSTANT) + if (function == GR_COMBINE_FUNCTION_LOCAL && factor == GR_COMBINE_FACTOR_ZERO && + local == GR_COMBINE_LOCAL_CONSTANT && other == GR_COMBINE_OTHER_CONSTANT) { - alphaCombine = AlphaCombine::Texture; - } - else - { - assert(false && "Unhandled alpha combine."); + alphaCombine = AlphaCombine::FromColor; } _scratchBatch.SetAlphaCombine(alphaCombine); } -void D2DXContext::OnConstantColorValue(uint32_t color) +_Use_decl_annotations_ +void D2DXContext::OnConstantColorValue( + uint32_t color) { - _constantColor = (color >> 8) | (color << 24); + _glideState.constantColor = (color >> 8) | (color << 24); + _readVertexState.isDirty = true; } -void D2DXContext::OnAlphaBlendFunction(GrAlphaBlendFnc_t rgb_sf, GrAlphaBlendFnc_t rgb_df, GrAlphaBlendFnc_t alpha_sf, GrAlphaBlendFnc_t alpha_df) +_Use_decl_annotations_ +void D2DXContext::OnAlphaBlendFunction( + GrAlphaBlendFnc_t rgb_sf, + GrAlphaBlendFnc_t rgb_df, + GrAlphaBlendFnc_t alpha_sf, + GrAlphaBlendFnc_t alpha_df) { auto alphaBlend = AlphaBlend::Opaque; @@ -411,231 +550,393 @@ void D2DXContext::OnAlphaBlendFunction(GrAlphaBlendFnc_t rgb_sf, GrAlphaBlendFnc } _scratchBatch.SetAlphaBlend(alphaBlend); -} + _readVertexState.isDirty = true; +} -void D2DXContext::OnDrawLine(const void* v1, const void* v2, uint32_t gameContext) +_Use_decl_annotations_ +void D2DXContext::OnDrawPoint( + const void* pt, + uint32_t gameContext) { - FixIngameMousePosition(); - - auto gameAddress = _gameHelper.IdentifyGameAddress(gameContext); - Batch batch = _scratchBatch; - batch.SetPrimitiveType(PrimitiveType::Triangles); - batch.SetGameAddress(gameAddress); + batch.SetGameAddress(GameAddress::Unknown); batch.SetStartVertex(_vertexCount); - batch.SetTextureCategory(_gameHelper.RefineTextureCategoryFromGameAddress(batch.GetTextureCategory(), gameAddress)); - Vertex startVertex = ReadVertex((const uint8_t*)v1, _vertexLayout, batch); - Vertex endVertex = ReadVertex((const uint8_t*)v2, _vertexLayout, batch); + EnsureReadVertexStateUpdated(batch); - float dx = endVertex.GetX() - startVertex.GetX(); - float dy = endVertex.GetY() - startVertex.GetY(); - const float lensqr = dx * dx + dy * dy; - const float len = lensqr > 0.01f ? sqrtf(lensqr) : 1.0f; - std::swap(dx, dy); - dx = -dx; - const float halfinvlen = 1.0f / (2.0f * len); - dx *= halfinvlen; - dy *= halfinvlen; + auto vertex0 = _readVertexState.templateVertex; - Vertex vertex0 = startVertex; - vertex0.SetX(vertex0.GetX() - dx); - vertex0.SetY(vertex0.GetY() - dy); + const uint32_t iteratedColorMask = _readVertexState.iteratedColorMask; + const uint32_t maskedConstantColor = _readVertexState.maskedConstantColor; + const int32_t stShift = _glideState.stShift; - Vertex vertex1 = startVertex; - vertex1.SetX(vertex1.GetX() + dx); - vertex1.SetY(vertex1.GetY() + dy); + const D2::Vertex* d2Vertex = (const D2::Vertex*)pt; - Vertex vertex2 = endVertex; - vertex2.SetX(vertex2.GetX() - dx); - vertex2.SetY(vertex2.GetY() - dy); + vertex0.SetPosition((int32_t)d2Vertex->x, (int32_t)d2Vertex->y); + vertex0.SetTexcoord((int32_t)d2Vertex->s >> stShift, (int32_t)d2Vertex->t >> stShift); + vertex0.SetColor(maskedConstantColor | (d2Vertex->color & iteratedColorMask)); + vertex0.SetSurfaceId(_surfaceIdTracker.GetCurrentSurfaceId()); - Vertex vertex3 = endVertex; - vertex3.SetX(vertex3.GetX() + dx); - vertex3.SetY(vertex3.GetY() + dy); + Vertex vertex1 = vertex0; + Vertex vertex2 = vertex0; - assert((_vertexCount + 6) < _vertices.capacity); - int32_t vertexWriteIndex = _vertexCount; - _vertices.items[vertexWriteIndex++] = vertex0; - _vertices.items[vertexWriteIndex++] = vertex1; - _vertices.items[vertexWriteIndex++] = vertex2; - _vertices.items[vertexWriteIndex++] = vertex1; - _vertices.items[vertexWriteIndex++] = vertex2; - _vertices.items[vertexWriteIndex++] = vertex3; - _vertexCount = vertexWriteIndex; + vertex1.AddOffset(1, 0); + vertex2.AddOffset(1, 1); + + assert((_vertexCount + 3) < _vertices.capacity); + _vertices.items[_vertexCount++] = vertex0; + _vertices.items[_vertexCount++] = vertex1; + _vertices.items[_vertexCount++] = vertex2; + + batch.SetVertexCount(3); - batch.SetVertexCount(6); + _surfaceIdTracker.UpdateBatchSurfaceId(batch, _majorGameState, _gameSize, &_vertices.items[batch.GetStartVertex()], batch.GetVertexCount()); assert(_batchCount < _batches.capacity); _batches.items[_batchCount++] = batch; } -Vertex D2DXContext::ReadVertex(const uint8_t* vertex, uint32_t vertexLayout, const Batch& batch) +_Use_decl_annotations_ +void D2DXContext::OnDrawLine( + const void* v1, + const void* v2, + uint32_t gameContext) { - uint32_t stShift = 0; - _BitScanReverse((DWORD*)&stShift, max(batch.GetWidth(), batch.GetHeight())); - stShift = 8 - stShift; + Batch batch = _scratchBatch; + batch.SetGameAddress(GameAddress::DrawLine); + batch.SetStartVertex(_vertexCount); + batch.SetPaletteIndex(D2DX_WHITE_PALETTE_INDEX); + batch.SetTextureCategory(TextureCategory::UserInterface); + + EnsureReadVertexStateUpdated(batch); + + auto vertex0 = _readVertexState.templateVertex; + vertex0.SetSurfaceId(D2DX_SURFACE_ID_USER_INTERFACE); + + const uint32_t iteratedColorMask = _readVertexState.iteratedColorMask; + const uint32_t maskedConstantColor = _readVertexState.maskedConstantColor; + + const D2::Vertex* d2Vertex0 = (const D2::Vertex*)v1; + const D2::Vertex* d2Vertex1 = (const D2::Vertex*)v2; + + vertex0.SetTexcoord((int32_t)d2Vertex1->s >> _glideState.stShift, (int32_t)d2Vertex1->t >> _glideState.stShift); + vertex0.SetColor(maskedConstantColor | (d2Vertex1->color & iteratedColorMask)); + + if (IsFeatureEnabled(Feature::WeatherMotionPrediction) && + currentlyDrawingWeatherParticles) + { + uint32_t currentWeatherParticleIndex = *currentlyDrawingWeatherParticleIndexPtr; + const int32_t act = _gameHelper->GetCurrentAct(); + + OffsetF startPos{ d2Vertex0->x, d2Vertex0->y }; + OffsetF endPos{ d2Vertex1->x, d2Vertex1->y }; + + // Snow is drawn with two independent lines per particle index (different places on screen). + // We solve this by tracking each line separately. + if (currentWeatherParticleIndex == _lastWeatherParticleIndex) + { + currentWeatherParticleIndex += 256; + } + + const auto offset = _weatherMotionPredictor.GetOffset(currentWeatherParticleIndex, startPos); + startPos += offset; + endPos += offset; + + auto dir = endPos - startPos; + float len = dir.Length(); + dir.Normalize(); + + const float blendFactor = 0.1f; + const float oneMinusBlendFactor = 1.0f - blendFactor; + + if (_avgDir.x == 0.0f && _avgDir.y == 0.0f) + { + _avgDir = dir; + } + else + { + _avgDir = _avgDir * oneMinusBlendFactor + dir * blendFactor; + _avgDir.Normalize(); + } + dir = _avgDir; + + const OffsetF wideningVec{ -dir.y * 1.25f, dir.x * 1.25f }; + const float stretchBack = act == 4 ? 1.0f : 3.0f; + const float stretchAhead = act == 4 ? 1.0f : 1.0f; + + auto midPos = startPos; + startPos -= dir * len * stretchBack; + endPos = startPos + dir * len * (stretchBack + stretchAhead); + + Vertex vertex1 = vertex0; + Vertex vertex2 = vertex0; + Vertex vertex3 = vertex0; + Vertex vertex4 = vertex0; + + vertex0.SetPosition((int32_t)midPos.x, (int32_t)midPos.y); + vertex1.SetPosition((int32_t)startPos.x, (int32_t)startPos.y); + vertex2.SetPosition((int32_t)(midPos.x + wideningVec.x), (int32_t)(midPos.y + wideningVec.y)); + vertex3.SetPosition((int32_t)endPos.x, (int32_t)endPos.y); + vertex4.SetPosition((int32_t)(midPos.x - wideningVec.x), (int32_t)(midPos.y - wideningVec.y)); + + uint32_t c = vertex0.GetColor(); + c &= 0x00FFFFFF; + vertex1.SetColor(c); + vertex2.SetColor(c); + vertex3.SetColor(c); + vertex4.SetColor(c); + + assert((_vertexCount + 3 * 4) < _vertices.capacity); + + _vertices.items[_vertexCount++] = vertex0; + _vertices.items[_vertexCount++] = vertex1; + _vertices.items[_vertexCount++] = vertex2; + + _vertices.items[_vertexCount++] = vertex0; + _vertices.items[_vertexCount++] = vertex2; + _vertices.items[_vertexCount++] = vertex3; + + _vertices.items[_vertexCount++] = vertex0; + _vertices.items[_vertexCount++] = vertex3; + _vertices.items[_vertexCount++] = vertex4; - const int32_t xyOffset = (vertexLayout >> 16) & 0xFF; - const int32_t stOffset = (vertexLayout >> 8) & 0xFF; - const int32_t pargbOffset = vertexLayout & 0xFF; + _vertices.items[_vertexCount++] = vertex0; + _vertices.items[_vertexCount++] = vertex4; + _vertices.items[_vertexCount++] = vertex1; - auto xy = (const float*)(vertex + xyOffset); - auto st = (const float*)(vertex + stOffset); - assert((st[0] - floor(st[0])) < 1e6); - assert((st[1] - floor(st[1])) < 1e6); - int16_t s = ((int16_t)st[0] >> stShift); - int16_t t = ((int16_t)st[1] >> stShift); + batch.SetVertexCount(3 * 4); - auto pargb = pargbOffset != 0xFF ? *(const uint32_t*)(vertex + pargbOffset) : 0xFFFFFFFF; + _lastWeatherParticleIndex = currentWeatherParticleIndex; + } + else + { + OffsetF wideningVec = { d2Vertex0->y - d2Vertex1->y, d2Vertex1->x - d2Vertex0->x }; + const float len = wideningVec.Length(); + const float halfinvlen = 1.0f / (2.0f * len); + wideningVec *= halfinvlen; + + Vertex vertex1 = vertex0; + Vertex vertex2 = vertex0; + Vertex vertex3 = vertex0; + + vertex0.SetPosition( + (int32_t)(d2Vertex0->x - wideningVec.x), + (int32_t)(d2Vertex0->y - wideningVec.y)); + + vertex1.SetPosition( + (int32_t)(d2Vertex0->x + wideningVec.x), + (int32_t)(d2Vertex0->y + wideningVec.y)); + + vertex2.SetPosition( + (int32_t)(d2Vertex1->x - wideningVec.x), + (int32_t)(d2Vertex1->y - wideningVec.y)); + + vertex3.SetPosition( + (int32_t)(d2Vertex1->x + wideningVec.x), + (int32_t)(d2Vertex1->y + wideningVec.y)); + + assert((_vertexCount + 6) < _vertices.capacity); + _vertices.items[_vertexCount++] = vertex0; + _vertices.items[_vertexCount++] = vertex1; + _vertices.items[_vertexCount++] = vertex2; + _vertices.items[_vertexCount++] = vertex1; + _vertices.items[_vertexCount++] = vertex2; + _vertices.items[_vertexCount++] = vertex3; + + batch.SetVertexCount(6); + } - return Vertex(xy[0], xy[1], s, t, batch.SelectColorAndAlpha(pargb, _constantColor), batch.GetRgbCombine(), batch.GetAlphaCombine(), batch.IsChromaKeyEnabled(), batch.GetTextureIndex(), batch.GetPaletteIndex()); + assert(_batchCount < _batches.capacity); + _batches.items[_batchCount++] = batch; } -const Batch D2DXContext::PrepareBatchForSubmit(Batch batch, PrimitiveType primitiveType, uint32_t vertexCount, uint32_t gameContext) const +_Use_decl_annotations_ +const Batch D2DXContext::PrepareBatchForSubmit( + Batch batch, + PrimitiveType primitiveType, + uint32_t vertexCount, + uint32_t gameContext) const { - auto gameAddress = _gameHelper.IdentifyGameAddress(gameContext); - batch.SetPrimitiveType(PrimitiveType::Triangles); - - auto tcl = _d3d11Context->UpdateTexture(batch, _tmuMemory.items); + auto gameAddress = _gameHelper->IdentifyGameAddress(gameContext); + + auto tcl = _renderContext->UpdateTexture(batch, _glideState.tmuMemory.items, _glideState.tmuMemory.capacity); + + if (tcl._textureAtlas < 0) + { + return batch; + } + batch.SetTextureAtlas(tcl._textureAtlas); batch.SetTextureIndex(tcl._textureIndex); batch.SetGameAddress(gameAddress); batch.SetStartVertex(_vertexCount); batch.SetVertexCount(vertexCount); - batch.SetTextureCategory(_gameHelper.RefineTextureCategoryFromGameAddress(batch.GetTextureCategory(), gameAddress)); + batch.SetTextureCategory(_gameHelper->RefineTextureCategoryFromGameAddress(batch.GetTextureCategory(), gameAddress)); return batch; } -void D2DXContext::OnDrawVertexArray(uint32_t mode, uint32_t count, uint8_t** pointers, uint32_t gameContext) +_Use_decl_annotations_ +void D2DXContext::EnsureReadVertexStateUpdated( + const Batch& batch) { - FixIngameMousePosition(); + if (!_readVertexState.isDirty) + { + return; + } + + _readVertexState.templateVertex = Vertex( + 0, 0, + 0, 0, + 0, + batch.IsChromaKeyEnabled(), + batch.GetTextureIndex(), + batch.GetRgbCombine() == RgbCombine::ColorMultipliedByTexture ? batch.GetPaletteIndex() : D2DX_WHITE_PALETTE_INDEX, + 0); + + const bool isIteratedColor = batch.GetRgbCombine() == RgbCombine::ColorMultipliedByTexture; + const uint32_t constantColorMask = isIteratedColor ? 0xFF000000 : 0xFFFFFFFF; + _readVertexState.constantColorMask = constantColorMask; + _readVertexState.iteratedColorMask = isIteratedColor ? 0x00FFFFFF : 0x00000000; + _readVertexState.maskedConstantColor = constantColorMask & (_glideState.constantColor | (batch.GetAlphaBlend() != AlphaBlend::SrcAlphaInvSrcAlpha ? 0xFF000000 : 0)); + _readVertexState.isDirty = false; +} - const Batch batch = PrepareBatchForSubmit(_scratchBatch, PrimitiveType::Triangles, (count - 2) * 3, gameContext); - const uint32_t vertexLayout = _vertexLayout; +_Use_decl_annotations_ +void D2DXContext::OnDrawVertexArray( + uint32_t mode, + uint32_t count, + uint8_t** pointers, + uint32_t gameContext) +{ + assert(mode == GR_TRIANGLE_STRIP || mode == GR_TRIANGLE_FAN); - switch (mode) + if (count < 3 || (mode != GR_TRIANGLE_STRIP && mode != GR_TRIANGLE_FAN)) { - case GR_TRIANGLE_FAN: + return; + } + + Batch batch = PrepareBatchForSubmit(_scratchBatch, PrimitiveType::Triangles, 3 * (count - 2), gameContext); + + if (!batch.IsValid()) { - Vertex firstVertex = ReadVertex((const uint8_t*)pointers[0], vertexLayout, batch); - Vertex prevVertex = ReadVertex((const uint8_t*)pointers[1], vertexLayout, batch); + return; + } - for (uint32_t i = 2; i < count; ++i) - { - Vertex currentVertex = ReadVertex((const uint8_t*)pointers[i], vertexLayout, batch); + EnsureReadVertexStateUpdated(batch); - assert((_vertexCount + 3) < _vertices.capacity); - int32_t vertexWriteIndex = _vertexCount; - _vertices.items[vertexWriteIndex++] = firstVertex; - _vertices.items[vertexWriteIndex++] = prevVertex; - _vertices.items[vertexWriteIndex++] = currentVertex; - _vertexCount = vertexWriteIndex; + Vertex v = _readVertexState.templateVertex; - prevVertex = currentVertex; - } - break; + const uint32_t iteratedColorMask = _readVertexState.iteratedColorMask; + const uint32_t maskedConstantColor = _readVertexState.maskedConstantColor; + + Vertex* pVertices = &_vertices.items[_vertexCount]; + + for (int32_t i = 0; i < 3; ++i) + { + const D2::Vertex* d2Vertex = (const D2::Vertex*)pointers[i]; + v.SetPosition((int32_t)d2Vertex->x, (int32_t)d2Vertex->y); + v.SetTexcoord((int32_t)d2Vertex->s >> _glideState.stShift, (int32_t)d2Vertex->t >> _glideState.stShift); + v.SetColor(maskedConstantColor | (d2Vertex->color & iteratedColorMask)); + *pVertices++ = v; } - case GR_TRIANGLE_STRIP: + + if (mode == GR_TRIANGLE_FAN) { - Vertex prevPrevVertex = ReadVertex((const uint8_t*)pointers[0], vertexLayout, batch); - Vertex prevVertex = ReadVertex((const uint8_t*)pointers[1], vertexLayout, batch); + auto vertex0 = pVertices[-3]; - for (uint32_t i = 2; i < count; ++i) + for (uint32_t i = 0; i < (count - 3); ++i) { - Vertex currentVertex = ReadVertex((const uint8_t*)pointers[i], vertexLayout, batch); - - assert((_vertexCount + 3) < _vertices.capacity); - int32_t vertexWriteIndex = _vertexCount; - _vertices.items[vertexWriteIndex++] = prevPrevVertex; - _vertices.items[vertexWriteIndex++] = prevVertex; - _vertices.items[vertexWriteIndex++] = currentVertex; - _vertexCount = vertexWriteIndex; - - prevPrevVertex = prevVertex; - prevVertex = currentVertex; + *pVertices++ = vertex0; + *pVertices++ = pVertices[-2]; + const D2::Vertex* d2Vertex = (const D2::Vertex*)pointers[i + 3]; + v.SetPosition((int32_t)d2Vertex->x, (int32_t)d2Vertex->y); + v.SetTexcoord((int32_t)d2Vertex->s >> _glideState.stShift, (int32_t)d2Vertex->t >> _glideState.stShift); + v.SetColor(maskedConstantColor | (d2Vertex->color & iteratedColorMask)); + *pVertices++ = v; } - break; } - default: - assert(false && "Unhandled primitive type."); - return; + else + { + for (uint32_t i = 0; i < (count - 3); ++i) + { + *pVertices++ = pVertices[-2]; + *pVertices++ = pVertices[-2]; + const D2::Vertex* d2Vertex = (const D2::Vertex*)pointers[i + 3]; + v.SetPosition((int32_t)d2Vertex->x, (int32_t)d2Vertex->y); + v.SetTexcoord((int32_t)d2Vertex->s >> _glideState.stShift, (int32_t)d2Vertex->t >> _glideState.stShift); + v.SetColor(maskedConstantColor | (d2Vertex->color & iteratedColorMask)); + *pVertices++ = v; + } } + _vertexCount += 3 * (count - 2); + + _surfaceIdTracker.UpdateBatchSurfaceId(batch, _majorGameState, _gameSize, &_vertices.items[batch.GetStartVertex()], batch.GetVertexCount()); + assert(_batchCount < _batches.capacity); _batches.items[_batchCount++] = batch; } -void D2DXContext::OnDrawVertexArrayContiguous(uint32_t mode, uint32_t count, uint8_t* vertex, uint32_t stride, uint32_t gameContext) +_Use_decl_annotations_ +void D2DXContext::OnDrawVertexArrayContiguous( + uint32_t mode, + uint32_t count, + uint8_t* vertex, + uint32_t stride, + uint32_t gameContext) { - FixIngameMousePosition(); - - const Batch batch = PrepareBatchForSubmit(_scratchBatch, PrimitiveType::Triangles, (count - 2) * 3, gameContext); - const uint32_t vertexLayout = _vertexLayout; + assert(count == 4); + assert(mode == GR_TRIANGLE_FAN); + assert(stride == sizeof(D2::Vertex)); - switch (mode) + if (mode != GR_TRIANGLE_FAN || count != 4 || stride != sizeof(D2::Vertex)) { - case GR_TRIANGLE_FAN: + return; + } + + Batch batch = PrepareBatchForSubmit(_scratchBatch, PrimitiveType::Triangles, 6, gameContext); + + if (!batch.IsValid()) { - Vertex firstVertex = ReadVertex(vertex, _vertexLayout, batch); - vertex += stride; + return; + } - Vertex prevVertex = ReadVertex(vertex, _vertexLayout, batch); - vertex += stride; + EnsureReadVertexStateUpdated(batch); - for (uint32_t i = 2; i < count; ++i) - { - Vertex currentVertex = ReadVertex(vertex, _vertexLayout, batch); - vertex += stride; + const uint32_t iteratedColorMask = _readVertexState.iteratedColorMask; + const uint32_t maskedConstantColor = _readVertexState.maskedConstantColor; - assert((_vertexCount + 3) < _vertices.capacity); - int32_t vertexWriteIndex = _vertexCount; - _vertices.items[vertexWriteIndex++] = firstVertex; - _vertices.items[vertexWriteIndex++] = prevVertex; - _vertices.items[vertexWriteIndex++] = currentVertex; - _vertexCount = vertexWriteIndex; + const D2::Vertex* d2Vertices = (const D2::Vertex*)vertex; - prevVertex = currentVertex; - } - break; - } - case GR_TRIANGLE_STRIP: - { - Vertex prevPrevVertex = ReadVertex(vertex, _vertexLayout, batch); - vertex += stride; + Vertex v = _readVertexState.templateVertex; - Vertex prevVertex = ReadVertex(vertex, _vertexLayout, batch); - vertex += stride; + Vertex* pVertices = &_vertices.items[_vertexCount]; - for (uint32_t i = 2; i < count; ++i) - { - Vertex currentVertex = ReadVertex(vertex, _vertexLayout, batch); - vertex += stride; - - assert((_vertexCount + 3) < _vertices.capacity); - int32_t vertexWriteIndex = _vertexCount; - _vertices.items[vertexWriteIndex++] = prevPrevVertex; - _vertices.items[vertexWriteIndex++] = prevVertex; - _vertices.items[vertexWriteIndex++] = currentVertex; - _vertexCount = vertexWriteIndex; - - prevPrevVertex = prevVertex; - prevVertex = currentVertex; - } - break; - } - default: - assert(false && "Unhandled primitive type."); - return; + for (int32_t i = 0; i < 4; ++i) + { + v.SetPosition((int32_t)d2Vertices[i].x, (int32_t)d2Vertices[i].y); + v.SetTexcoord((int32_t)d2Vertices[i].s >> _glideState.stShift, (int32_t)d2Vertices[i].t >> _glideState.stShift); + v.SetColor(maskedConstantColor | (d2Vertices[i].color & iteratedColorMask)); + pVertices[i] = v; } + pVertices[4] = pVertices[0]; + pVertices[5] = pVertices[2]; + + _vertexCount += 6; + + _surfaceIdTracker.UpdateBatchSurfaceId(batch, _majorGameState, _gameSize, &_vertices.items[batch.GetStartVertex()], batch.GetVertexCount()); + assert(_batchCount < _batches.capacity); _batches.items[_batchCount++] = batch; } -void D2DXContext::OnTexDownloadTable(GrTexTable_t type, void* data) +_Use_decl_annotations_ +void D2DXContext::OnTexDownloadTable( + GrTexTable_t type, + void* data) { if (type != GR_TEXTABLE_PALETTE) { @@ -643,10 +944,12 @@ void D2DXContext::OnTexDownloadTable(GrTexTable_t type, void* data) return; } + _readVertexState.isDirty = true; + uint32_t hash = fnv_32a_buf(data, 1024, FNV1_32A_INIT); assert(hash != 0); - for (uint32_t i = 0; i < _paletteKeys.capacity; ++i) + for (uint32_t i = 0; i < D2DX_MAX_GAME_PALETTES; ++i) { if (_paletteKeys.items[i] == 0) { @@ -660,44 +963,87 @@ void D2DXContext::OnTexDownloadTable(GrTexTable_t type, void* data) } } - for (uint32_t i = 0; i < _paletteKeys.capacity; ++i) + for (uint32_t i = 0; i < D2DX_MAX_GAME_PALETTES; ++i) { if (_paletteKeys.items[i] == 0) { _paletteKeys.items[i] = hash; _scratchBatch.SetPaletteIndex(i); - _d3d11Context->SetPalette(i, (const uint32_t*)data); + + uint32_t* palette = (uint32_t*)data; + + for (int32_t j = 0; j < 256; ++j) + { + palette[j] |= 0xFF000000; + } + + if (_options.GetFlag(OptionsFlag::DbgDumpTextures)) + { + memcpy(_glideState.palettes.items + 256 * i, palette, 1024); + } + + _renderContext->SetPalette(i, palette); return; } } assert(false && "Too many palettes."); - ALWAYS_PRINT("Too many palettes."); + D2DX_LOG("Too many palettes."); } -void D2DXContext::OnChromakeyMode(GrChromakeyMode_t mode) +_Use_decl_annotations_ +void D2DXContext::OnChromakeyMode( + GrChromakeyMode_t mode) { _scratchBatch.SetIsChromaKeyEnabled(mode == GR_CHROMAKEY_ENABLE); + + _readVertexState.isDirty = true; } -void D2DXContext::OnLoadGammaTable(uint32_t nentries, uint32_t* red, uint32_t* green, uint32_t* blue) +_Use_decl_annotations_ +void D2DXContext::OnLoadGammaTable( + uint32_t nentries, + uint32_t* red, + uint32_t* green, + uint32_t* blue) { for (int32_t i = 0; i < (int32_t)min(nentries, 256); ++i) { - _gammaTable.items[i] = ((blue[i] & 0xFF) << 16) | ((green[i] & 0xFF) << 8) | (red[i] & 0xFF); + _glideState.gammaTable.items[i] = ((blue[i] & 0xFF) << 16) | ((green[i] & 0xFF) << 8) | (red[i] & 0xFF); } - _d3d11Context->LoadGammaTable(_gammaTable.items); + _renderContext->LoadGammaTable(_glideState.gammaTable.items, _glideState.gammaTable.capacity); } -void D2DXContext::OnLfbUnlock(const uint32_t* lfbPtr, uint32_t strideInBytes) +_Use_decl_annotations_ +void D2DXContext::OnLfbUnlock( + const uint32_t* lfbPtr, + uint32_t strideInBytes) { - _d3d11Context->WriteToScreen(lfbPtr, 640, 480); + _renderContext->WriteToScreen(lfbPtr, 640, 480); } -void D2DXContext::OnGammaCorrectionRGB(float red, float green, float blue) +_Use_decl_annotations_ +void D2DXContext::OnGammaCorrectionRGB( + float red, + float green, + float blue) { - _d3d11Context->SetGamma(red, green, blue); + uint32_t gammaTable[256]; + + for (int32_t i = 0; i < 256; ++i) + { + float v = i / 255.0f; + float r = powf(v, 1.0f / red); + float g = powf(v, 1.0f / green); + float b = powf(v, 1.0f / blue); + uint32_t ri = (uint32_t)(r * 255.0f); + uint32_t gi = (uint32_t)(g * 255.0f); + uint32_t bi = (uint32_t)(b * 255.0f); + gammaTable[i] = (ri << 16) | (gi << 8) | bi; + } + + _renderContext->LoadGammaTable(gammaTable, ARRAYSIZE(gammaTable)); } void D2DXContext::PrepareLogoTextureBatch() @@ -708,116 +1054,387 @@ void D2DXContext::PrepareLogoTextureBatch() } const uint8_t* srcPixels = dx_logo256 + 0x436; - const uint32_t* palette = (const uint32_t*)(dx_logo256 + 0x36); - _d3d11Context->SetPalette(15, palette); + Buffer palette(256); + memcpy_s(palette.items, palette.capacity * sizeof(uint32_t), (uint32_t*)(dx_logo256 + 0x36), 256 * sizeof(uint32_t)); + + for (int32_t i = 0; i < 256; ++i) + { + palette.items[i] |= 0xFF000000; + } + + _renderContext->SetPalette(D2DX_LOGO_PALETTE_INDEX, palette.items); uint32_t hash = fnv_32a_buf((void*)srcPixels, sizeof(uint8_t) * 81 * 40, FNV1_32A_INIT); - uint8_t* data = _sideTmuMemory.items; + uint8_t* data = _glideState.sideTmuMemory.items; _logoTextureBatch.SetTextureStartAddress(0); _logoTextureBatch.SetTextureHash(hash); _logoTextureBatch.SetTextureSize(128, 128); _logoTextureBatch.SetTextureCategory(TextureCategory::TitleScreen); - _logoTextureBatch.SetPrimitiveType(PrimitiveType::Triangles); _logoTextureBatch.SetAlphaBlend(AlphaBlend::SrcAlphaInvSrcAlpha); _logoTextureBatch.SetIsChromaKeyEnabled(true); _logoTextureBatch.SetRgbCombine(RgbCombine::ColorMultipliedByTexture); _logoTextureBatch.SetAlphaCombine(AlphaCombine::One); - _logoTextureBatch.SetPaletteIndex(15); + _logoTextureBatch.SetPaletteIndex(D2DX_LOGO_PALETTE_INDEX); _logoTextureBatch.SetVertexCount(6); - memset(data, 0, _logoTextureBatch.GetWidth() * _logoTextureBatch.GetHeight()); + memset(data, 0, _logoTextureBatch.GetTextureWidth() * _logoTextureBatch.GetTextureHeight()); for (int32_t y = 0; y < 41; ++y) { for (int32_t x = 0; x < 80; ++x) { - data[x + (40-y) * 128] = *srcPixels++; + data[x + (40 - y) * 128] = *srcPixels++; } } } void D2DXContext::InsertLogoOnTitleScreen() { - if (_options.skipLogo || _majorGameState != MajorGameState::TitleScreen || _batchCount <= 0) + if (_options.GetFlag(OptionsFlag::NoLogo) || _majorGameState != MajorGameState::TitleScreen || _batchCount <= 0) return; PrepareLogoTextureBatch(); - auto tcl = _d3d11Context->UpdateTexture(_logoTextureBatch, _sideTmuMemory.items); + auto tcl = _renderContext->UpdateTexture(_logoTextureBatch, _glideState.sideTmuMemory.items, _glideState.sideTmuMemory.capacity); _logoTextureBatch.SetTextureAtlas(tcl._textureAtlas); _logoTextureBatch.SetTextureIndex(tcl._textureIndex); _logoTextureBatch.SetStartVertex(_vertexCount); - const float x = (float)(_d3d11Context->GetMetrics().gameWidth - 90 - 16); - const float y = (float)(_d3d11Context->GetMetrics().gameHeight - 50 - 16); + Size gameSize; + _renderContext->GetCurrentMetrics(&gameSize, nullptr, nullptr); + + const int32_t x = gameSize.width - 90 - 16; + const int32_t y = gameSize.height - 50 - 16; const uint32_t color = 0xFFFFa090; - Vertex vertex0(x, y, 0, 0, color, RgbCombine::ColorMultipliedByTexture, AlphaCombine::One, true, _logoTextureBatch.GetTextureIndex(), 15); - Vertex vertex1(x + 80, y, 80, 0, color, RgbCombine::ColorMultipliedByTexture, AlphaCombine::One, true, _logoTextureBatch.GetTextureIndex(), 15); - Vertex vertex2(x + 80, y + 41, 80, 41, color, RgbCombine::ColorMultipliedByTexture, AlphaCombine::One, true, _logoTextureBatch.GetTextureIndex(), 15); - Vertex vertex3(x, y + 41, 0, 41, color, RgbCombine::ColorMultipliedByTexture, AlphaCombine::One, true, _logoTextureBatch.GetTextureIndex(), 15); + Vertex vertex0(x, y, 0, 0, color, true, _logoTextureBatch.GetTextureIndex(), D2DX_LOGO_PALETTE_INDEX, D2DX_SURFACE_ID_USER_INTERFACE); + Vertex vertex1(x + 80, y, 80, 0, color, true, _logoTextureBatch.GetTextureIndex(), D2DX_LOGO_PALETTE_INDEX, D2DX_SURFACE_ID_USER_INTERFACE); + Vertex vertex2(x + 80, y + 41, 80, 41, color, true, _logoTextureBatch.GetTextureIndex(), D2DX_LOGO_PALETTE_INDEX, D2DX_SURFACE_ID_USER_INTERFACE); + Vertex vertex3(x, y + 41, 0, 41, color, true, _logoTextureBatch.GetTextureIndex(), D2DX_LOGO_PALETTE_INDEX, D2DX_SURFACE_ID_USER_INTERFACE); assert((_vertexCount + 6) < _vertices.capacity); - int32_t vertexWriteIndex = _vertexCount; - _vertices.items[vertexWriteIndex++] = vertex0; - _vertices.items[vertexWriteIndex++] = vertex1; - _vertices.items[vertexWriteIndex++] = vertex2; - _vertices.items[vertexWriteIndex++] = vertex0; - _vertices.items[vertexWriteIndex++] = vertex2; - _vertices.items[vertexWriteIndex++] = vertex3; - _vertexCount = vertexWriteIndex; + _vertices.items[_vertexCount++] = vertex0; + _vertices.items[_vertexCount++] = vertex1; + _vertices.items[_vertexCount++] = vertex2; + _vertices.items[_vertexCount++] = vertex0; + _vertices.items[_vertexCount++] = vertex2; + _vertices.items[_vertexCount++] = vertex3; _batches.items[_batchCount++] = _logoTextureBatch; } GameVersion D2DXContext::GetGameVersion() const { - return _gameHelper.GetVersion(); + return _gameHelper->GetVersion(); } -void D2DXContext::OnMousePosChanged(int32_t x, int32_t y) +_Use_decl_annotations_ +Offset D2DXContext::OnSetCursorPos( + Offset pos) { - _mouseX = x; - _mouseY = y; + auto currentScreenOpenMode = _gameHelper->ScreenOpenMode(); + + if (_lastScreenOpenMode != currentScreenOpenMode) + { + POINT originalPos; + GetCursorPos(&originalPos); + + auto hWnd = _renderContext->GetHWnd(); + + ScreenToClient(hWnd, (LPPOINT)&pos); + + Size gameSize; + Rect renderRect; + Size desktopSize; + _renderContext->GetCurrentMetrics(&gameSize, &renderRect, &desktopSize); + + const bool isFullscreen = _renderContext->GetScreenMode() == ScreenMode::FullscreenDefault; + const float scale = (float)renderRect.size.height / gameSize.height; + const uint32_t scaledWidth = (uint32_t)(scale * gameSize.width); + const float mouseOffsetX = isFullscreen ? (float)(desktopSize.width / 2 - scaledWidth / 2) : 0.0f; + + pos.x = (int32_t)(pos.x * scale + mouseOffsetX); + pos.y = (int32_t)(pos.y * scale); + + ClientToScreen(hWnd, (LPPOINT)&pos); + + pos.y = originalPos.y; + + return pos; + } + + return { -1, -1 }; +} + +_Use_decl_annotations_ +Offset D2DXContext::OnMouseMoveMessage( + Offset pos) +{ + auto currentScreenOpenMode = _gameHelper->ScreenOpenMode(); + + Size gameSize; + Rect renderRect; + Size desktopSize; + _renderContext->GetCurrentMetrics(&gameSize, &renderRect, &desktopSize); + + const bool isFullscreen = _renderContext->GetScreenMode() == ScreenMode::FullscreenDefault; + const float scale = (float)renderRect.size.height / gameSize.height; + const uint32_t scaledWidth = (uint32_t)(scale * gameSize.width); + const float mouseOffsetX = isFullscreen ? (float)(desktopSize.width / 2 - scaledWidth / 2) : 0.0f; + + pos.x = (int32_t)(pos.x * scale + mouseOffsetX); + pos.y = (int32_t)(pos.y * scale); + + return pos; +} + +_Use_decl_annotations_ +int32_t D2DXContext::OnSleep( + int32_t ms) +{ + if (_skipCountingSleep || _threadId != GetCurrentThreadId()) + { + return ms; + } + + ++_sleeps; + + if (_majorGameState == MajorGameState::InGame) + { + return ms; + } + + return ms; } -void D2DXContext::FixIngameMousePosition() +_Use_decl_annotations_ +void D2DXContext::SetCustomResolution( + Size size) { - /* When opening UI panels, the game will screw up the mouse position when - we are using a non-1 window scale. This fix, which is run as early in the frame - as we can, forces the ingame variables back to the proper values. */ + _customGameSize = size; +} - if (_batchCount == 0) +Size D2DXContext::GetSuggestedCustomResolution() +{ + if (_suggestedGameSize.width == 0) { - _gameHelper.SetIngameMousePos(_mouseX, _mouseY); + if (_gameHelper->IsProjectDiablo2()) + { + _suggestedGameSize = { 1068, 600 }; + } + else + { + Size desktopSize{ GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN) }; + + _suggestedGameSize = _options.GetUserSpecifiedGameSize(); + _suggestedGameSize.width = min(_suggestedGameSize.width, desktopSize.width); + _suggestedGameSize.height = min(_suggestedGameSize.height, desktopSize.height); + + if (_suggestedGameSize.width < 0 || _suggestedGameSize.height < 0) + { + _suggestedGameSize = Metrics::GetSuggestedGameSize(desktopSize, !_options.GetFlag(OptionsFlag::NoWide)); + } + } + + D2DX_LOG("Suggesting game size %ix%i.", _suggestedGameSize.width, _suggestedGameSize.height); } + + return _suggestedGameSize; } -void D2DXContext::SetCustomResolution(int32_t width, int32_t height) +void D2DXContext::DisableBuiltinResMod() { - _customWidth = width; - _customHeight = height; + _options.SetFlag(OptionsFlag::NoResMod, true); } -void D2DXContext::GetSuggestedCustomResolution(int32_t* width, int32_t* height) +const Options& D2DXContext::GetOptions() const { - int32_t desktopWidth = GetSystemMetrics(SM_CXSCREEN); - int32_t desktopHeight = GetSystemMetrics(SM_CYSCREEN); - int32_t customWidth = desktopWidth; - int32_t customHeight = desktopHeight; - int32_t scaleFactor = 1; + return _options; +} - while (customHeight > 600) +void D2DXContext::OnBufferClear() +{ + if (_majorGameState == MajorGameState::InGame) { - ++scaleFactor; - customWidth = desktopWidth / scaleFactor; - customHeight = desktopHeight / scaleFactor; + if (IsFeatureEnabled(Feature::UnitMotionPrediction)) + { + _unitMotionPredictor.Update(_renderContext.get()); + } + + if (IsFeatureEnabled(Feature::TextMotionPrediction)) + { + _textMotionPredictor.Update(_renderContext.get()); + } + + if (IsFeatureEnabled(Feature::WeatherMotionPrediction)) + { + _weatherMotionPredictor.Update(_renderContext.get()); + } + } +} + +_Use_decl_annotations_ +Offset D2DXContext::BeginDrawText( + wchar_t* str, + Offset pos, + uint32_t returnAddress, + D2Function d2Function) +{ + _scratchBatch.SetTextureCategory(TextureCategory::UserInterface); + _isDrawingText = true; + + Offset offset{ 0, 0 }; + + if (!str) + { + return offset; + } + + if (d2Function != D2Function::D2Win_DrawText && IsFeatureEnabled(Feature::TextMotionPrediction)) + { + auto hash = fnv_32a_buf((void*)str, wcslen(str), FNV1_32A_INIT); + + const uint64_t textId = + (((uint64_t)(returnAddress & 0xFFFFFF) << 40ULL) | + ((uint64_t)((uintptr_t)str & 0xFFFFFF) << 16ULL)) ^ + (uint64_t)hash; + + offset = _textMotionPredictor.GetOffset(textId, pos); + } + + if (_gameHelper->GetVersion() == GameVersion::Lod114d) + { + // In 1.14d, some color codes are black. Remap them. + + // Bright white -> white + while (wchar_t* subStr = wcsstr(str, L"˙c/")) + { + subStr[2] = L'0'; + } + } + + return offset; +} + +void D2DXContext::EndDrawText() +{ + _scratchBatch.SetTextureCategory(TextureCategory::Unknown); + _isDrawingText = false; +} + +_Use_decl_annotations_ +Offset D2DXContext::BeginDrawImage( + const D2::CellContext* cellContext, + uint32_t drawMode, + Offset pos, + D2Function d2Function) +{ + Offset offset{ 0,0 }; + + if (_isDrawingText) + { + return offset; + } + + if (currentlyDrawingUnit) + { + _unitMotionPredictor.SetUnitScreenPos(currentlyDrawingUnit, pos.x, pos.y); + + if (currentlyDrawingUnit == _gameHelper->GetPlayerUnit()) + { + // The player unit itself. + _scratchBatch.SetTextureCategory(TextureCategory::Player); + _playerScreenPos = pos; + } + else + { + offset = _unitMotionPredictor.GetOffset(currentlyDrawingUnit); + } + } + else + { + if (d2Function == D2Function::D2Gfx_DrawShadow) + { + const bool isPlayerShadow = + _playerScreenPos.x > 0 && + max(abs(pos.x - _playerScreenPos.x), abs(pos.y - _playerScreenPos.y)) < 8; + + if (isPlayerShadow) + { + _scratchBatch.SetTextureCategory(TextureCategory::Player); + } + else + { + offset = _unitMotionPredictor.GetOffsetForShadow(pos.x, pos.y); + } + } + else + { + DrawParameters drawParameters = _gameHelper->GetDrawParameters(cellContext); + const bool isMiscUi = drawParameters.unitType == 0 && cellContext->dwMode == 0 && drawMode != 3; + const bool isBeltItem = drawParameters.unitType == 4 && cellContext->dwMode == 4; + + if (isMiscUi || isBeltItem) + { + _scratchBatch.SetTextureCategory(TextureCategory::UserInterface); + } + } + } + + return offset; +} + +void D2DXContext::EndDrawImage() +{ + if (_isDrawingText) + { + return; + } + + _scratchBatch.SetTextureCategory(TextureCategory::Unknown); +} + +_Use_decl_annotations_ +bool D2DXContext::IsFeatureEnabled( + Feature feature) +{ + if (!_areFeatureFlagsInitialized) + { + const auto gameVersion = _gameHelper->GetVersion(); + + _featureFlags = 0; + D2DX_LOG("Feature flags:"); + + if (!_options.GetFlag(OptionsFlag::NoMotionPrediction)) + { + if ( + gameVersion == GameVersion::Lod109d || + gameVersion == GameVersion::Lod112 || + gameVersion == GameVersion::Lod113c || + gameVersion == GameVersion::Lod113d || + gameVersion == GameVersion::Lod114d) + { + _featureFlags |= (uint32_t)Feature::UnitMotionPrediction; + D2DX_LOG(" UnitMotionPrediction"); + _featureFlags |= (uint32_t)Feature::WeatherMotionPrediction; + D2DX_LOG(" WeatherMotionPrediction"); + } + + if (gameVersion == GameVersion::Lod113c || + gameVersion == GameVersion::Lod113d || + gameVersion == GameVersion::Lod114d) + { + _featureFlags |= (uint32_t)Feature::TextMotionPrediction; + D2DX_LOG(" TextMotionPrediction"); + } + } + + _areFeatureFlagsInitialized = true; } - *width = customWidth; - *height = customHeight; + return (_featureFlags & (uint32_t)feature) != 0; } diff --git a/src/d2dx/D2DXContext.h b/src/d2dx/D2DXContext.h index 3dcce1a..5a43b06 100644 --- a/src/d2dx/D2DXContext.h +++ b/src/d2dx/D2DXContext.h @@ -18,97 +18,280 @@ */ #pragma once -#include "D3D11Context.h" -#include "GameHelper.h" -#include "Types.h" #include "Batch.h" +#include "Buffer.h" +#include "IBuiltinResMod.h" +#include "ID2DXContext.h" +#include "IGameHelper.h" +#include "IGlide3x.h" +#include "IRenderContext.h" +#include "IWin32InterceptionHandler.h" +#include "CompatibilityModeDisabler.h" +#include "SurfaceIdTracker.h" +#include "TextureHasher.h" +#include "TextMotionPredictor.h" +#include "UnitMotionPredictor.h" +#include "WeatherMotionPredictor.h" #include "Vertex.h" namespace d2dx { - class D2DXContext final + class Vertex; + + class D2DXContext final : + public ID2DXContext { public: - D2DXContext(); - ~D2DXContext(); - - bool IsCapturingFrame() const; - bool IsDrawingDisabled() const; - - void OnGlideInit(); - void OnGlideShutdown(); - const char* OnGetString(uint32_t pname); - uint32_t OnGet(uint32_t pname, uint32_t plength, int32_t* params); - void OnSstWinOpen(uint32_t hWnd, int32_t width, int32_t height); - void OnVertexLayout(uint32_t param, int32_t offset); - void OnTexDownload(uint32_t tmu, const uint8_t* sourceAddress, uint32_t startAddress, int32_t width, int32_t height); - void OnTexSource(uint32_t tmu, uint32_t startAddress, int32_t width, int32_t height); - void OnConstantColorValue(uint32_t color); - void OnAlphaBlendFunction(GrAlphaBlendFnc_t rgb_sf, GrAlphaBlendFnc_t rgb_df, GrAlphaBlendFnc_t alpha_sf, GrAlphaBlendFnc_t alpha_df); - void OnColorCombine(GrCombineFunction_t function, GrCombineFactor_t factor, GrCombineLocal_t local, GrCombineOther_t other, bool invert); - void OnAlphaCombine(GrCombineFunction_t function, GrCombineFactor_t factor, GrCombineLocal_t local, GrCombineOther_t other, bool invert); - void OnDrawLine(const void* v1, const void* v2, uint32_t gameContext); - void OnDrawVertexArray(uint32_t mode, uint32_t count, uint8_t** pointers, uint32_t gameContext); - void OnDrawVertexArrayContiguous(uint32_t mode, uint32_t count, uint8_t* vertex, uint32_t stride, uint32_t gameContext); - void OnTexDownloadTable(GrTexTable_t type, void* data); - void OnLoadGammaTable(uint32_t nentries, uint32_t* red, uint32_t* green, uint32_t* blue); - void OnChromakeyMode(GrChromakeyMode_t mode); - void OnLfbUnlock(const uint32_t* lfbPtr, uint32_t strideInBytes); - void OnGammaCorrectionRGB(float red, float green, float blue); - void OnBufferSwap(); - - void OnMousePosChanged(int32_t x, int32_t y); - - void SetCustomResolution(int32_t width, int32_t height); - void GetSuggestedCustomResolution(int32_t* width, int32_t* height); - - void LogGlideCall(const char* s); - - GameVersion GetGameVersion() const; - - private: + D2DXContext( + _In_ const std::shared_ptr& gameHelper, + _In_ const std::shared_ptr& simd, + _In_ const std::shared_ptr& compatibilityModeDisabler); + + virtual ~D2DXContext() noexcept; + +#pragma region IGlide3x + + virtual const char* OnGetString( + _In_ uint32_t pname); + + virtual uint32_t OnGet( + _In_ uint32_t pname, + _In_ uint32_t plength, + _Out_writes_(plength) int32_t* params); + + virtual void OnSstWinOpen( + _In_ uint32_t hWnd, + _In_ int32_t width, + _In_ int32_t height); + + virtual void OnVertexLayout( + _In_ uint32_t param, + _In_ int32_t offset); + + virtual void OnTexDownload( + _In_ uint32_t tmu, + _In_reads_(width * height) const uint8_t* sourceAddress, + _In_ uint32_t startAddress, + _In_ int32_t width, + _In_ int32_t height); + + virtual void OnTexSource( + _In_ uint32_t tmu, + _In_ uint32_t startAddress, + _In_ int32_t width, + _In_ int32_t height); + + virtual void OnConstantColorValue( + _In_ uint32_t color); + + virtual void OnAlphaBlendFunction( + _In_ GrAlphaBlendFnc_t rgb_sf, + _In_ GrAlphaBlendFnc_t rgb_df, + _In_ GrAlphaBlendFnc_t alpha_sf, + _In_ GrAlphaBlendFnc_t alpha_df); + + virtual void OnColorCombine( + _In_ GrCombineFunction_t function, + _In_ GrCombineFactor_t factor, + _In_ GrCombineLocal_t local, + _In_ GrCombineOther_t other, + _In_ bool invert); + + virtual void OnAlphaCombine( + _In_ GrCombineFunction_t function, + _In_ GrCombineFactor_t factor, + _In_ GrCombineLocal_t local, + _In_ GrCombineOther_t other, + _In_ bool invert); + + virtual void OnDrawPoint( + _In_ const void* pt, + _In_ uint32_t gameContext); + + virtual void OnDrawLine( + _In_ const void* v1, + _In_ const void* v2, + _In_ uint32_t gameContext); + + virtual void OnDrawVertexArray( + _In_ uint32_t mode, + _In_ uint32_t count, + _In_reads_(count) uint8_t** pointers, + _In_ uint32_t gameContext); + + virtual void OnDrawVertexArrayContiguous( + _In_ uint32_t mode, + _In_ uint32_t count, + _In_reads_(count * stride) uint8_t* vertex, + _In_ uint32_t stride, + _In_ uint32_t gameContext); + + virtual void OnTexDownloadTable( + _In_ GrTexTable_t type, + _In_reads_bytes_(256 * 4) void* data); + + virtual void OnLoadGammaTable( + _In_ uint32_t nentries, + _In_reads_(nentries) uint32_t* red, + _In_reads_(nentries) uint32_t* green, + _In_reads_(nentries) uint32_t* blue); + + virtual void OnChromakeyMode( + _In_ GrChromakeyMode_t mode); + + virtual void OnLfbUnlock( + _In_reads_bytes_(strideInBytes * 480) const uint32_t* lfbPtr, + _In_ uint32_t strideInBytes); + + virtual void OnGammaCorrectionRGB( + _In_ float red, + _In_ float green, + _In_ float blue); + + virtual void OnBufferSwap(); + + virtual void OnBufferClear(); + +#pragma endregion IGlide3x + +#pragma region ID2DXContext + + virtual void SetCustomResolution( + _In_ Size size) override; + + virtual Size GetSuggestedCustomResolution() override; + + virtual GameVersion GetGameVersion() const override; + + virtual void DisableBuiltinResMod() override; + + virtual const Options& GetOptions() const override; + + virtual bool IsFeatureEnabled( + _In_ Feature feature) override; + +#pragma endregion ID2DXContext + +#pragma region IWin32InterceptionHandler + + virtual Offset OnSetCursorPos( + _In_ Offset pos) override; + + virtual Offset OnMouseMoveMessage( + _In_ Offset pos) override; + + virtual int32_t OnSleep( + _In_ int32_t ms) override; + +#pragma endregion IWin32InterceptionHandler + +#pragma region ID2InterceptionHandler + + virtual Offset BeginDrawText( + _Inout_z_ wchar_t* str, + _In_ Offset pos, + _In_ uint32_t returnAddress, + _In_ D2Function d2Function) override; + + virtual void EndDrawText() override; + + virtual Offset BeginDrawImage( + _In_ const D2::CellContext* cellContext, + _In_ uint32_t drawMode, + _In_ Offset pos, + _In_ D2Function d2Function) override; + + virtual void EndDrawImage() override; + +#pragma endregion ID2InterceptionHandler + + private: void CheckMajorGameState(); + void PrepareLogoTextureBatch(); + void InsertLogoOnTitleScreen(); - void DrawBatches(); - const Batch PrepareBatchForSubmit(Batch batch, PrimitiveType primitiveType, uint32_t vertexCount, uint32_t gameContext) const; - Vertex ReadVertex(const uint8_t* vertex, uint32_t vertexLayout, const Batch& batch); - void FixIngameMousePosition(); - uint32_t _renderFilter; - bool _capturingFrame; + void DrawBatches( + _In_ uint32_t startVertexLocation); + + const Batch PrepareBatchForSubmit( + _In_ Batch batch, + _In_ PrimitiveType primitiveType, + _In_ uint32_t vertexCount, + _In_ uint32_t gameContext) const; + + void EnsureReadVertexStateUpdated( + _In_ const Batch& batch); + + struct GlideState + { + Buffer tmuMemory{ D2DX_TMU_MEMORY_SIZE }; + Buffer sideTmuMemory{ D2DX_SIDE_TMU_MEMORY_SIZE }; + Buffer palettes{ D2DX_MAX_PALETTES * 256 }; + Buffer gammaTable{ 256 }; + uint32_t constantColor{ 0xFFFFFFFF }; + int32_t stShift{ 0 }; + }; + + struct ReadVertexState + { + Vertex templateVertex; + uint32_t constantColorMask{ 0 }; + uint32_t iteratedColorMask{ 0 }; + uint32_t maskedConstantColor{ 0 }; + bool isDirty{ false }; + }; + + GlideState _glideState; + ReadVertexState _readVertexState; Batch _scratchBatch; int32_t _frame; - std::unique_ptr _d3d11Context; + std::shared_ptr _renderContext; + std::shared_ptr _gameHelper; + std::shared_ptr _simd; + std::unique_ptr _builtinResMod; + std::shared_ptr _compatibilityModeDisabler; + TextureHasher _textureHasher; + UnitMotionPredictor _unitMotionPredictor; + TextMotionPredictor _textMotionPredictor; + WeatherMotionPredictor _weatherMotionPredictor; + SurfaceIdTracker _surfaceIdTracker; + MajorGameState _majorGameState; Buffer _paletteKeys; - float _gamma[3]; - Buffer _gammaTable; - - uint32_t _constantColor; - - uint32_t _vertexLayout; - - int32_t _batchCount; + uint32_t _batchCount; Buffer _batches; - int32_t _vertexCount; + uint32_t _vertexCount; Buffer _vertices; - GameHelper _gameHelper; Options _options; Batch _logoTextureBatch; + + Size _customGameSize; + Size _suggestedGameSize; + + uint32_t _lastScreenOpenMode; + + Size _gameSize; + + bool _isDrawingText = false; + Offset _playerScreenPos = { 0,0 }; + + uint32_t _lastWeatherParticleIndex = 0xFFFFFFFF; + + OffsetF _avgDir = { 0.0f, 0.0f }; - Buffer _tmuMemory; - Buffer _sideTmuMemory; + bool _areFeatureFlagsInitialized = false; + uint32_t _featureFlags; - int32_t _mouseX; - int32_t _mouseY; - int32_t _customWidth; - int32_t _customHeight; + bool _skipCountingSleep = false; + int32_t _sleeps = 0; + uint32_t _threadId = 0; }; } diff --git a/src/d2dx/D2DXContextFactory.cpp b/src/d2dx/D2DXContextFactory.cpp new file mode 100644 index 0000000..977e14a --- /dev/null +++ b/src/d2dx/D2DXContextFactory.cpp @@ -0,0 +1,51 @@ +/* + This file is part of D2DX. + + Copyright (C) 2021 Bolrog + + D2DX is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + D2DX is distributed in the hope that it will be 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 D2DX. If not, see . +*/ +#include "pch.h" +#include "D2DXContextFactory.h" +#include "GameHelper.h" +#include "SimdSse2.h" +#include "D2DXContext.h" +#include "CompatibilityModeDisabler.h" + +using namespace d2dx; + +static bool destroyed = false; +static std::shared_ptr instance; + +ID2DXContext* D2DXContextFactory::GetInstance( + bool createIfNeeded) +{ + /* The game is single threaded and there's no worry about synchronization. */ + + if (!instance && !destroyed && createIfNeeded) + { + auto gameHelper = std::make_shared(); + auto simd = std::make_shared(); + auto compatibilityModeDisabler = std::make_shared(); + instance = std::make_shared(gameHelper, simd, compatibilityModeDisabler); + } + + return instance.get(); +} + +void D2DXContextFactory::DestroyInstance() +{ + instance = nullptr; + destroyed = true; +} diff --git a/src/d2dx/Simd.h b/src/d2dx/D2DXContextFactory.h similarity index 78% rename from src/d2dx/Simd.h rename to src/d2dx/D2DXContextFactory.h index b93c245..2313fce 100644 --- a/src/d2dx/Simd.h +++ b/src/d2dx/D2DXContextFactory.h @@ -18,13 +18,14 @@ */ #pragma once +#include "ID2DXContext.h" + namespace d2dx { - class Simd abstract + class D2DXContextFactory { public: - static std::shared_ptr Create(); - - virtual int32_t IndexOfUInt32(_In_reads_(itemsCount) const uint32_t* __restrict items, uint32_t itemsCount, uint32_t item) = 0; + static ID2DXContext* GetInstance(bool createIfNeeded = true); + static void DestroyInstance(); }; } diff --git a/src/d2dx/D2DXDetours.cpp b/src/d2dx/D2DXDetours.cpp deleted file mode 100644 index 3809739..0000000 --- a/src/d2dx/D2DXDetours.cpp +++ /dev/null @@ -1,191 +0,0 @@ -/* - This file is part of D2DX. - - Copyright (C) 2021 Bolrog - - D2DX is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - D2DX is distributed in the hope that it will be 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 D2DX. If not, see . -*/ -#include "pch.h" -#include "D2DXDetours.h" - -#pragma comment(lib, "../../thirdparty/detours/detours.lib") - -bool hasDetoured = false; - -COLORREF(WINAPI* GetPixel_real)( - _In_ HDC hdc, - _In_ int x, - _In_ int y) = GetPixel; - -COLORREF WINAPI GetPixel_Hooked(_In_ HDC hdc, _In_ int x, _In_ int y) -{ - /* Gets rid of the long delay on startup and when switching between menus in < 1.14, - as the game is doing a ton of pixel readbacks... for some reason. */ - return 0; -} - -int (WINAPI* ShowCursor_Real)( - _In_ BOOL bShow) = ShowCursor; - -int WINAPI ShowCursor_Hooked( - _In_ BOOL bShow) -{ - /* Override how the game hides/shows the cursor. We will take care of that. */ - return bShow ? 1 : -1; -} - -BOOL(WINAPI* SetWindowPos_Real)( - _In_ HWND hWnd, - _In_opt_ HWND hWndInsertAfter, - _In_ int X, - _In_ int Y, - _In_ int cx, - _In_ int cy, - _In_ UINT uFlags) = SetWindowPos; - -BOOL -WINAPI -SetWindowPos_Hooked( - _In_ HWND hWnd, - _In_opt_ HWND hWndInsertAfter, - _In_ int X, - _In_ int Y, - _In_ int cx, - _In_ int cy, - _In_ UINT uFlags) -{ - /* Stop the game from moving/sizing the window. */ - return SetWindowPos_Real(hWnd, hWndInsertAfter, X, Y, cx, cy, uFlags | SWP_NOSIZE | SWP_NOMOVE); -} - -BOOL -(WINAPI* - SetCursorPos_Real)( - _In_ int X, - _In_ int Y) = SetCursorPos; - -BOOL -WINAPI -SetCursorPos_Hooked( - _In_ int X, - _In_ int Y) -{ - return FALSE; -} - -LRESULT -(WINAPI* - SendMessageA_Real)( - _In_ HWND hWnd, - _In_ UINT Msg, - _Pre_maybenull_ _Post_valid_ WPARAM wParam, - _Pre_maybenull_ _Post_valid_ LPARAM lParam) = SendMessageA; - -LRESULT -WINAPI -SendMessageA_Hooked( - _In_ HWND hWnd, - _In_ UINT Msg, - _Pre_maybenull_ _Post_valid_ WPARAM wParam, - _Pre_maybenull_ _Post_valid_ LPARAM lParam) -{ - if (Msg == WM_MOUSEMOVE) - { - /* The game tries to move the mouse pointer using a SendMessage call in some situations. - This screws up the cursor if windowscale != 1. So just drop the message. */ - return 0; - } - - return SendMessageA_Real(hWnd, Msg, wParam, lParam); -} - -_Success_(return) -int -WINAPI -DrawTextA_Hooked( - _In_ HDC hdc, - _When_((format & DT_MODIFYSTRING), _At_((LPSTR)lpchText, _Inout_grows_updates_bypassable_or_z_(cchText, 4))) - _When_((!(format & DT_MODIFYSTRING)), _In_bypassable_reads_or_z_(cchText)) - LPCSTR lpchText, - _In_ int cchText, - _Inout_ LPRECT lprc, - _In_ UINT format) -{ - /* This removes the "weird characters" being printed by the game in the top left corner. - There is still a delay but the GetPixel hook takes care of that... */ - return 0; -} - - -_Success_(return) -int( - WINAPI * - DrawTextA_Real)( - _In_ HDC hdc, - _When_((format & DT_MODIFYSTRING), _At_((LPSTR)lpchText, _Inout_grows_updates_bypassable_or_z_(cchText, 4))) - _When_((!(format & DT_MODIFYSTRING)), _In_bypassable_reads_or_z_(cchText)) - LPCSTR lpchText, - _In_ int cchText, - _Inout_ LPRECT lprc, - _In_ UINT format) = DrawTextA; - -void d2dx::AttachDetours() -{ - if (hasDetoured) - { - return; - } - - hasDetoured = true; - - DetourTransactionBegin(); - DetourUpdateThread(GetCurrentThread()); - DetourAttach(&(PVOID&)DrawTextA_Real, DrawTextA_Hooked); - DetourAttach(&(PVOID&)GetPixel_real, GetPixel_Hooked); - DetourAttach(&(PVOID&)SendMessageA_Real, SendMessageA_Hooked); - DetourAttach(&(PVOID&)ShowCursor_Real, ShowCursor_Hooked); - DetourAttach(&(PVOID&)SetCursorPos_Real, SetCursorPos_Hooked); - DetourAttach(&(PVOID&)SetWindowPos_Real, SetWindowPos_Hooked); - - LONG lError = DetourTransactionCommit(); - - if (lError != NO_ERROR) { - MessageBox(HWND_DESKTOP, L"Failed to detour", L"timb3r", MB_OK); - } -} - -void d2dx::DetachDetours() -{ - if (!hasDetoured) - { - return; - } - - hasDetoured = false; - - DetourTransactionBegin(); - DetourUpdateThread(GetCurrentThread()); - DetourDetach(&(PVOID&)DrawTextA_Real, DrawTextA_Hooked); - DetourDetach(&(PVOID&)GetPixel_real, GetPixel_Hooked); - DetourDetach(&(PVOID&)SendMessageA_Real, SendMessageA_Hooked); - DetourDetach(&(PVOID&)ShowCursor_Real, ShowCursor_Hooked); - DetourDetach(&(PVOID&)SetCursorPos_Real, SetCursorPos_Hooked); - DetourDetach(&(PVOID&)SetWindowPos_Real, SetWindowPos_Hooked); - - LONG lError = DetourTransactionCommit(); - - if (lError != NO_ERROR) { - MessageBox(HWND_DESKTOP, L"Failed to detour", L"timb3r", MB_OK); - } -} diff --git a/src/d2dx/D2Types.h b/src/d2dx/D2Types.h new file mode 100644 index 0000000..54d6013 --- /dev/null +++ b/src/d2dx/D2Types.h @@ -0,0 +1,245 @@ +/* + This file is part of D2DX. + + Copyright (C) 2021 Bolrog + + D2DX is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + D2DX is distributed in the hope that it will be 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 D2DX. If not, see . +*/ +#pragma once + +namespace d2dx +{ + namespace D2 + { + enum class UnitType + { + Player = 0, + Monster = 1, + Object = 2, + Missile = 3, + Item = 4, + VisTile = 5, + Count = 6, + }; + + struct CellContext //size 0x48 + { + uint32_t nCellNo; //0x00 + uint32_t _0a; //0x04 + uint32_t dwUnit; //0x08 + uint32_t dwClass; //0x0C + uint32_t dwMode; //0x10 + uint32_t _3; //0x14 + uint32_t dwPlayerType; //0x18 + BYTE _5; //0x1C + BYTE _5a; //0x1D + WORD _6; //0x1E + uint32_t _7; //0x20 + uint32_t _8; //0x24 + uint32_t _9; //0x28 + char* szName; //0x2C + uint32_t _11; //0x30 + void* pCellFile; //0x34 also pCellInit + uint32_t _12; //0x38 + void* pGfxCells; //0x3C + uint32_t direction; //0x40 + uint32_t _14; //0x44 + }; + + static_assert(sizeof(CellContext) == 0x48, "CellContext size"); + + struct UnitAny; + struct Room1; + + struct Path + { + DWORD x; //0x00 + DWORD y; //0x04 + DWORD xUnknown; //0x08 16 * (wInitX - wInitY) <- Mby AutomapX + DWORD yUnknown; //0x0C 8 * (wInitX + wInitY + 1) <- Mby AutoampY + short xTarget; //0x10 + short yTarget; //0x12 + DWORD _2[2]; //0x14 + Room1* pRoom1; //0x1C + Room1* pRoomUnk; //0x20 + DWORD _3[3]; //0x24 + UnitAny* pUnit; //0x30 + DWORD dwFlags; //0x34 0x40000 -> PATH_MISSILE_MASK + DWORD _4; //0x38 + DWORD dwPathType; //0x3C + DWORD dwPrevPathType; //0x40 + DWORD dwUnitSize; //0x44 + DWORD _5[2]; //0x48 + DWORD dwCollisionFlag; //0x50 0x1804 <- bFlying, 0x3401 <- bOpenDoors, 0x3C01 <- Cannot Open Doors, 0x804 <- Ghost, 0x1C09 <- Player + DWORD _5d; //0x54 + UnitAny* pTargetUnit; //0x58 + DWORD dwTargetType; //0x5C + DWORD dwTargetId; //0x60 + BYTE bDirection; //0x64 + }; + + static_assert(sizeof(Path) == 0x68, "Path size"); + + struct StaticPath // size 0x20 + { + Room1* pRoom1; //0x00 + DWORD xOffset; //0x04 + DWORD yOffset; //0x08 + DWORD xPos; //0x0C + DWORD yPos; //0x10 + DWORD _1[2]; //0x14 + DWORD dwFlags; //0x1C + }; + + static_assert(sizeof(StaticPath) == 0x20, "StaticPath size"); + + + struct UnitAny + { + union + { + struct + { + D2::UnitType dwType; // 0x00 + DWORD dwClassId; // 0x04 + DWORD dwUnitId; // 0x0C + void* pMemPool; // 0x08 + DWORD dwMode; // 0x10 + union { + void* pPlayerData; + void* pItemData; + void* pMonsterData; + void* pObjectData; + // TileData *pTileData doesn't appear to exist anymore + }; // 0x14 + DWORD dwAct; // 0x18 + void* pAct; // 0x1C + DWORD dwSeed[2]; // 0x20 + DWORD _2; // 0x28 + union { + Path* path; + StaticPath* staticPath; + }; // 0x2C + DWORD _3[2]; // 0x30 + union { + Path* path2; + StaticPath* staticPath2; + }; // 0x38 + } v109; + + struct + { + UnitType dwType; // 0x00 + DWORD dwTxtFileNo; // 0x04 + DWORD _1; // 0x08 + DWORD dwUnitId; // 0x0C + DWORD dwMode; // 0x10 + union { + void* pPlayerData; + void* pItemData; + void* pMonsterData; + void* pObjectData; + // TileData *pTileData doesn't appear to exist anymore + }; // 0x14 + DWORD dwAct; // 0x18 + void* pAct; // 0x1C + DWORD dwSeed[2]; // 0x20 + DWORD _2; // 0x28 + union { + Path* path; + StaticPath* staticPath; + }; // 0x2C + DWORD _3[2]; // 0x30 + union { + Path* path2; + StaticPath* staticPath2; + }; // 0x38 + DWORD unk[2]; + DWORD dwGfxFrame; // 0x44 + DWORD dwFrameRemain; // 0x48 + WORD wFrameRate; // 0x4C + WORD _4; // 0x4E + BYTE* pGfxUnk; // 0x50 + DWORD* pGfxInfo; // 0x54 + DWORD _5; // 0x58 + void* pStats; // 0x5C + void* pInventory; // 0x60 + void* ptLight; // 0x64 + DWORD dwStartLightRadius; // 0x68 + WORD nPl2ShiftIdx; // 0x6C + WORD nUpdateType; // 0x6E + UnitAny* pUpdateUnit; // 0x70 - Used when updating unit. + DWORD* pQuestRecord; // 0x74 + DWORD bSparklyChest; // 0x78 bool + DWORD* pTimerArgs; // 0x7C + DWORD dwSoundSync; // 0x80 + DWORD _6[2]; // 0x84 + WORD wX; // 0x8C + WORD wY; // 0x8E + DWORD _7; // 0x90 + DWORD dwOwnerType; // 0x94 + DWORD dwOwnerId; // 0x98 + DWORD _8[2]; // 0x9C + void* pOMsg; // 0xA4 + void* pInfo; // 0xA8 + DWORD _9[6]; // 0xAC + DWORD dwFlags; // 0xC4 + DWORD dwFlags2; // 0xC8 + DWORD _10[5]; // 0xCC + UnitAny* pChangedNext; // 0xE0 + UnitAny* pListNext; // 0xE4 -> 0xD8 + UnitAny* pRoomNext; // 0xE8 + } v112; + } u; + }; + + static_assert(sizeof(UnitAny) == 0xEC, "UnitAny size"); + + #pragma pack(push, 1) + struct Room1 { + Room1** pRoomsNear; //0x00 pptVisibleRooms + DWORD _1; //0x04 + void* _1s; //0x08 + DWORD _1b; //0x0C + void* pRoom2; //0x10 + DWORD _2[2]; //0x14 + UnitAny** pUnitChanged; //0x1C + void* Coll; //0x20 + DWORD dwRoomsNear; //0x24 dwVisibleRooms + DWORD nPlayerUnits; //0x28 + void* pAct; //0x2C + DWORD _4; //0x30 + DWORD nUnknown; //0x34 + DWORD _5[4]; //0x38 + void** pClients; //0x48 + uint8_t hCoords[0x20]; //0x4C + int64_t hSeed; //0x6C + UnitAny* pUnitFirst; //0x74 + DWORD nNumClients; //0x78 + Room1* pRoomNext; //0x7C + }; + #pragma pack(pop) + + static_assert(sizeof(Room1) == 0x80, "Room1 size"); + + struct Vertex + { + float x, y; + uint32_t color; + uint32_t padding; + float s, t; + uint32_t padding2; + }; + } +} diff --git a/src/d2dx/D3D11Context.cpp b/src/d2dx/D3D11Context.cpp deleted file mode 100644 index 956aa13..0000000 --- a/src/d2dx/D3D11Context.cpp +++ /dev/null @@ -1,1036 +0,0 @@ -/* - This file is part of D2DX. - - Copyright (C) 2021 Bolrog - - D2DX is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - D2DX is distributed in the hope that it will be 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 D2DX. If not, see . -*/ -#include "pch.h" -#include "D2DXContext.h" -#include "D3D11Context.h" -#include "GlideHelpers.h" -#include "GammaVS_cso.h" -#include "GammaPS_cso.h" -#include "PixelShader_cso.h" -#include "VertexShader_cso.h" -#include "VideoPS_cso.h" -#include "Utils.h" - -using namespace d2dx; -using namespace std; -using namespace Microsoft::WRL; - -extern unique_ptr g_d2dxContext; - -extern int (WINAPI* ShowCursor_Real)( - _In_ BOOL bShow); - -extern BOOL(WINAPI* SetWindowPos_Real)( - _In_ HWND hWnd, - _In_opt_ HWND hWndInsertAfter, - _In_ int X, - _In_ int Y, - _In_ int cx, - _In_ int cy, - _In_ UINT uFlags); - -static LRESULT CALLBACK d2dxSubclassWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, - LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData); - -D3D11Context::D3D11Context( - HWND hWnd, - int32_t renderWidth, - int32_t renderHeight, - int32_t gameWidth, - int32_t gameHeight, - Options& options, - shared_ptr simd, - shared_ptr textureProcessor) : - _hWnd{ hWnd }, - _vbWriteIndex{ 0 }, - _vbCapacity{ 0 }, - _options{ options }, - _constants{ 0 }, - _frameLatencyWaitableObject{ nullptr }, - _simd{ simd }, - _textureProcessor{ textureProcessor } -{ - memset(&_shadowState, 0, sizeof(_shadowState)); - - _metrics.desktopWidth = GetSystemMetrics(SM_CXSCREEN); - _metrics.desktopHeight = GetSystemMetrics(SM_CYSCREEN); - _metrics.desktopClientMaxHeight = GetSystemMetrics(SM_CYFULLSCREEN); - - _metrics.renderWidth = renderWidth; - _metrics.renderHeight = renderHeight; - _metrics.windowWidth = renderWidth; - _metrics.windowHeight = renderHeight; - _metrics.gameWidth = gameWidth; - _metrics.gameHeight = gameHeight; - -#ifndef NDEBUG - ShowCursor_Real(TRUE); -#endif - - RECT clientRect; - GetClientRect(hWnd, &clientRect); - - SetWindowSubclass((HWND)hWnd, d2dxSubclassWndProc, 1234, (DWORD_PTR)this); - - const int32_t widthFromClientRect = clientRect.right - clientRect.left; - const int32_t heightFromClientRect = clientRect.bottom - clientRect.top; - - _featureLevel = D3D_FEATURE_LEVEL_11_0; - D3D_FEATURE_LEVEL requestFeatureLevel = D3D_FEATURE_LEVEL_11_0; - - _dxgiAllowTearingFlagSupported = IsAllowTearingFlagSupported(); - - DWORD swapChainCreateFlags = 0; - - if (_options.noVSync && _dxgiAllowTearingFlagSupported) - { - swapChainCreateFlags |= DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING; - } - else if (!_options.noVSync) - { - swapChainCreateFlags |= DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT; - } - - DXGI_SWAP_CHAIN_DESC swapChainDesc; - ZeroMemory(&swapChainDesc, sizeof(swapChainDesc)); - swapChainDesc.BufferCount = 2; - swapChainDesc.BufferDesc.Width = _metrics.desktopWidth; - swapChainDesc.BufferDesc.Height = _metrics.desktopHeight; - swapChainDesc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; - swapChainDesc.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED; - swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; - swapChainDesc.OutputWindow = hWnd; - swapChainDesc.BufferDesc.RefreshRate.Numerator = 0; - swapChainDesc.BufferDesc.RefreshRate.Denominator = 0; - swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD; - swapChainDesc.Flags = swapChainCreateFlags; - swapChainDesc.SampleDesc.Count = 1; - swapChainDesc.SampleDesc.Quality = 0; - swapChainDesc.Windowed = TRUE; - -#ifdef NDEBUG - uint32_t flags = 0; -#else - uint32_t flags = D3D11_CREATE_DEVICE_DEBUG; -#endif - - D2DX_RELEASE_CHECK_HR(D3D11CreateDeviceAndSwapChain( - NULL, - D3D_DRIVER_TYPE_HARDWARE, - NULL, - flags, - &requestFeatureLevel, - 1, - D3D11_SDK_VERSION, - &swapChainDesc, - &_swapChain, - &_device, - &_featureLevel, - &_deviceContext)); - - ComPtr dxgiFactory; - _swapChain->GetParent(IID_PPV_ARGS(&dxgiFactory)); - if (dxgiFactory) - { - dxgiFactory->MakeWindowAssociation(hWnd, DXGI_MWA_NO_WINDOW_CHANGES); - } - - if (SUCCEEDED(_swapChain->QueryInterface(IID_PPV_ARGS(&_swapChain2)))) - { - ALWAYS_PRINT("Swap chain supports IDXGISwapChain2."); - - if (!_options.noVSync) - { - ALWAYS_PRINT("Will sync using IDXGISwapChain2::GetFrameLatencyWaitableObject."); - _frameLatencyWaitableObject = _swapChain2->GetFrameLatencyWaitableObject(); - } - } - else - { - ALWAYS_PRINT("Swap chain does not support IDXGISwapChain2."); - } - - if (SUCCEEDED(_deviceContext->QueryInterface(IID_PPV_ARGS(&_deviceContext1)))) - { - ALWAYS_PRINT("Device context supports ID3D11DeviceContext1. Will use this to discard resources and views."); - } - else - { - ALWAYS_PRINT("Device context does not support ID3D11DeviceContext1."); - } - - if (_swapChain2) - { - ComPtr dxgiDevice1; - if (SUCCEEDED(_swapChain->GetDevice(IID_PPV_ARGS(&dxgiDevice1)))) - { - ALWAYS_PRINT("Setting maximum frame latency to 2."); - D2DX_RELEASE_CHECK_HR(dxgiDevice1->SetMaximumFrameLatency(2)); - } - } - - // get a pointer directly to the back buffer - ComPtr backbuffer; - D2DX_RELEASE_CHECK_HR(_swapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), &backbuffer)); - - // create a render target pointing to the back buffer - D2DX_RELEASE_CHECK_HR(_device->CreateRenderTargetView(backbuffer.Get(), nullptr, &_backbufferRtv)); - - float color[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; - _deviceContext->ClearRenderTargetView(_backbufferRtv.Get(), color); - - CreateTextureCaches(); - CreateVertexAndIndexBuffers(); - CreateGammaTexture(); - CreatePaletteTexture(); - CreateVideoTextures(); - CreateShadersAndInputLayout(); - CreateSamplerStates(); - CreateConstantBuffers(); - CreateRenderTarget(); - - _deviceContext->VSSetConstantBuffers(0, 1, _cb.GetAddressOf()); - _deviceContext->PSSetConstantBuffers(0, 1, _cb.GetAddressOf()); - - CreateRasterizerState(); - - AdjustWindowPlacement(hWnd, false); -} - -D3D11Context::~D3D11Context() -{ - if (_frameLatencyWaitableObject) - { - CloseHandle(_frameLatencyWaitableObject); - _frameLatencyWaitableObject = nullptr; - } -} - -void D3D11Context::CreateRasterizerState() -{ - CD3D11_RASTERIZER_DESC rasterizerDesc{ CD3D11_DEFAULT() }; - - rasterizerDesc.CullMode = D3D11_CULL_NONE; - rasterizerDesc.DepthClipEnable = false; - rasterizerDesc.FillMode = D3D11_FILL_SOLID; - rasterizerDesc.ScissorEnable = FALSE; - D2DX_RELEASE_CHECK_HR(_device->CreateRasterizerState(&rasterizerDesc, &_rasterizerStateNoScissor)); - - rasterizerDesc.CullMode = D3D11_CULL_NONE; - rasterizerDesc.DepthClipEnable = false; - rasterizerDesc.FillMode = D3D11_FILL_SOLID; - rasterizerDesc.ScissorEnable = TRUE; - D2DX_RELEASE_CHECK_HR(_device->CreateRasterizerState(&rasterizerDesc, &_rasterizerState)); - - _deviceContext->RSSetState(_rasterizerState.Get()); -} - -void D3D11Context::CreateVertexAndIndexBuffers() -{ - _vbCapacity = 1024 * 1024; - - const CD3D11_BUFFER_DESC vbDesc - { - sizeof(Vertex) * _vbCapacity, - D3D11_BIND_VERTEX_BUFFER, - D3D11_USAGE_DYNAMIC, - D3D11_CPU_ACCESS_WRITE - }; - - D2DX_RELEASE_CHECK_HR(_device->CreateBuffer(&vbDesc, NULL, &_vb)); - - uint32_t stride = sizeof(Vertex); - uint32_t offset = 0; - _deviceContext->IASetVertexBuffers(0, 1, _vb.GetAddressOf(), &stride, &offset); -} - -void D3D11Context::CreateShadersAndInputLayout() -{ - D2DX_RELEASE_CHECK_HR(_device->CreateVertexShader(VertexShader_cso, ARRAYSIZE(VertexShader_cso), NULL, &_vs)); - D2DX_RELEASE_CHECK_HR(_device->CreatePixelShader(PixelShader_cso, ARRAYSIZE(PixelShader_cso), NULL, &_ps)); - D2DX_RELEASE_CHECK_HR(_device->CreatePixelShader(VideoPS_cso, ARRAYSIZE(VideoPS_cso), NULL, &_videoPS)); - D2DX_RELEASE_CHECK_HR(_device->CreateVertexShader(GammaVS_cso, ARRAYSIZE(GammaVS_cso), NULL, &_gammaVS)); - D2DX_RELEASE_CHECK_HR(_device->CreatePixelShader(GammaPS_cso, ARRAYSIZE(GammaPS_cso), NULL, &_gammaPS)); - - D3D11_INPUT_ELEMENT_DESC inputElementDescs[4] = - { - { "POSITION", 0, DXGI_FORMAT_R16G16_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 }, - { "TEXCOORD", 0, DXGI_FORMAT_R16G16_SINT, 0, 4, D3D11_INPUT_PER_VERTEX_DATA, 0 }, - { "COLOR", 0, DXGI_FORMAT_B8G8R8A8_UNORM, 0, 8, D3D11_INPUT_PER_VERTEX_DATA, 0 }, - { "TEXCOORD", 1, DXGI_FORMAT_R16G16_UINT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0 } - }; - - D2DX_RELEASE_CHECK_HR(_device->CreateInputLayout(inputElementDescs, ARRAYSIZE(inputElementDescs), VertexShader_cso, ARRAYSIZE(VertexShader_cso), &_inputLayout)); - - _deviceContext->IASetInputLayout(_inputLayout.Get()); -} - -void D3D11Context::CreateConstantBuffers() -{ - CD3D11_BUFFER_DESC cb1Desc - { - sizeof(Constants), - D3D11_BIND_CONSTANT_BUFFER, - D3D11_USAGE_DYNAMIC, - D3D11_CPU_ACCESS_WRITE - }; - - D2DX_RELEASE_CHECK_HR(_device->CreateBuffer(&cb1Desc, NULL, _cb.GetAddressOf())); -} - -void D3D11Context::CreateSamplerStates() -{ - CD3D11_SAMPLER_DESC samplerDesc{ CD3D11_DEFAULT() }; - - samplerDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_POINT; - D2DX_RELEASE_CHECK_HR(_device->CreateSamplerState(&samplerDesc, _samplerState[0].GetAddressOf())); - - samplerDesc.Filter = D3D11_FILTER_MIN_MAG_LINEAR_MIP_POINT; - D2DX_RELEASE_CHECK_HR(_device->CreateSamplerState(&samplerDesc, _samplerState[1].GetAddressOf())); - - ID3D11SamplerState* samplerState[2] = { _samplerState[0].Get(), _samplerState[1].Get() }; - _deviceContext->PSSetSamplers(0, 2, samplerState); -} - -void D3D11Context::Draw(const Batch& batch) -{ - SetVS(_vs.Get()); - SetPS(_ps.Get()); - - SetBlendState(batch.GetAlphaBlend()); - - TextureCache* atlas = GetTextureCache(batch); - ID3D11ShaderResourceView* srvs[2] = { atlas->GetSrv(batch.GetTextureAtlas()), _paletteTextureSrv.Get() }; - SetPSShaderResourceViews(srvs); - - switch (batch.GetPrimitiveType()) - { - case PrimitiveType::Triangles: - { - SetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST); - _deviceContext->Draw(batch.GetVertexCount(), batch.GetStartVertex()); - break; - } - case PrimitiveType::Lines: - { - SetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_LINELIST); - _deviceContext->Draw(batch.GetVertexCount(), batch.GetStartVertex()); - break; - } - default: - assert(false && "Unhandled primitive type."); - break; - } -} - -void D3D11Context::Present() -{ - _deviceContext->RSSetState(_rasterizerStateNoScissor.Get()); - - _deviceContext->OMSetRenderTargets(1, _backbufferRtv.GetAddressOf(), NULL); - UpdateViewport(_metrics.renderWidth, _metrics.renderHeight); - - ID3D11ShaderResourceView* srvs[2] = { _renderTargetTextureSrv.Get(), _gammaTextureSrv.Get() }; - SetPSShaderResourceViews(srvs); - - SetVS(_gammaVS.Get()); - SetPS(_gammaPS.Get()); - - SetBlendState(AlphaBlend::Opaque); - - SetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST); - - auto startVertexLocation = _vbWriteIndex; - const uint32_t vertexCount = UpdateVerticesWithFullScreenQuad(_metrics.renderWidth, _metrics.renderHeight); - - _deviceContext->Draw(vertexCount, startVertexLocation); - - srvs[0] = nullptr; - srvs[1] = nullptr; - SetPSShaderResourceViews(srvs); - - if (_options.noVSync) - { - D2DX_RELEASE_CHECK_HR(_swapChain->Present(0, _dxgiAllowTearingFlagSupported ? DXGI_PRESENT_ALLOW_TEARING : 0)); - } - else - { - D2DX_RELEASE_CHECK_HR(_swapChain->Present(0, 0)); - - if (_frameLatencyWaitableObject) - { - DWORD result = WaitForSingleObjectEx( - _frameLatencyWaitableObject, - 1000, // 1 second timeout (shouldn't ever occur) - true - ); - } - } - - if (_deviceContext1) - { - _deviceContext1->DiscardView(_renderTargetTextureSrv.Get()); - _deviceContext1->DiscardView(_renderTargetTextureRtv.Get()); - _deviceContext1->DiscardView(_backbufferRtv.Get()); - } - - for (int32_t i = 0; i < ARRAYSIZE(_textureCaches); ++i) - { - _textureCaches[i]->OnNewFrame(); - } - - _deviceContext->OMSetRenderTargets(1, _renderTargetTextureRtv.GetAddressOf(), NULL); - UpdateViewport(_metrics.renderWidth, _metrics.renderHeight); - - float color[] = { .0f, .0f, .0f, 1.0f }; - _deviceContext->ClearRenderTargetView(_renderTargetTextureRtv.Get(), color); - - _deviceContext->RSSetState(_rasterizerState.Get()); - - SetVS(_vs.Get()); - SetPS(_ps.Get()); -} - -void D3D11Context::CreateRenderTarget() -{ - DXGI_FORMAT renderTargetFormat = DXGI_FORMAT_R8G8B8A8_UNORM; - - UINT formatSupport; - D2DX_RELEASE_CHECK_HR(_device->CheckFormatSupport(DXGI_FORMAT_R10G10B10A2_UNORM, &formatSupport)); - if ((formatSupport & D3D11_FORMAT_SUPPORT_TEXTURE2D) && - (formatSupport & D3D11_FORMAT_SUPPORT_SHADER_LOAD) && - (formatSupport & D3D11_FORMAT_SUPPORT_RENDER_TARGET)) - { - ALWAYS_PRINT("Using DXGI_FORMAT_R10G10B10A2_UNORM for the render buffer."); - renderTargetFormat = DXGI_FORMAT_R10G10B10A2_UNORM; - } - else - { - ALWAYS_PRINT("Using DXGI_FORMAT_R8G8B8A8_UNORM for the render buffer."); - } - - CD3D11_TEXTURE2D_DESC desc{ - renderTargetFormat, - (UINT)_metrics.desktopWidth, - (UINT)_metrics.desktopHeight, - 1U, - 1U, - D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET, - D3D11_USAGE_DEFAULT - }; - - D2DX_RELEASE_CHECK_HR(_device->CreateTexture2D(&desc, NULL, &_renderTargetTexture)); - D2DX_RELEASE_CHECK_HR(_device->CreateShaderResourceView(_renderTargetTexture.Get(), NULL, &_renderTargetTextureSrv)); - - CD3D11_RENDER_TARGET_VIEW_DESC rtvDesc{ - D3D11_RTV_DIMENSION_TEXTURE2D, - renderTargetFormat - }; - - D2DX_RELEASE_CHECK_HR(_device->CreateRenderTargetView(_renderTargetTexture.Get(), &rtvDesc, &_renderTargetTextureRtv)); - - _deviceContext->OMSetRenderTargets(1, _renderTargetTextureRtv.GetAddressOf(), NULL); -} - -void D3D11Context::CreateGammaTexture() -{ - CD3D11_TEXTURE1D_DESC desc{ - DXGI_FORMAT_B8G8R8A8_UNORM, - (UINT)256, - 1U, - 1U, - D3D11_BIND_SHADER_RESOURCE, - D3D11_USAGE_DEFAULT - }; - - D2DX_RELEASE_CHECK_HR(_device->CreateTexture1D(&desc, nullptr, &_gammaTexture)); - D2DX_RELEASE_CHECK_HR(_device->CreateShaderResourceView(_gammaTexture.Get(), nullptr, &_gammaTextureSrv)); -} - -void D3D11Context::CreatePaletteTexture() -{ - CD3D11_TEXTURE1D_DESC desc{ - DXGI_FORMAT_B8G8R8A8_UNORM, - (UINT)256, - 32U, - 1U, - D3D11_BIND_SHADER_RESOURCE, - D3D11_USAGE_DEFAULT - }; - - D2DX_RELEASE_CHECK_HR(_device->CreateTexture1D(&desc, nullptr, &_paletteTexture)); - D2DX_RELEASE_CHECK_HR(_device->CreateShaderResourceView(_paletteTexture.Get(), nullptr, &_paletteTextureSrv)); -} - -void D3D11Context::LoadGammaTable(const uint32_t* gammaTable) -{ - _deviceContext->UpdateSubresource(_gammaTexture.Get(), 0, nullptr, gammaTable, 1024, 0); -} - -void D3D11Context::CreateVideoTextures() -{ - CD3D11_TEXTURE2D_DESC desc - { - DXGI_FORMAT_B8G8R8A8_UNORM, - 640, - 480, - 1U, - 1U, - D3D11_BIND_SHADER_RESOURCE, - D3D11_USAGE_DYNAMIC, - D3D11_CPU_ACCESS_WRITE - }; - - D2DX_RELEASE_CHECK_HR(_device->CreateTexture2D(&desc, NULL, &_videoTexture)); - D2DX_RELEASE_CHECK_HR(_device->CreateShaderResourceView(_videoTexture.Get(), NULL, &_videoTextureSrv)); -} - -void D3D11Context::WriteToScreen(const uint32_t* pixels, int32_t width, int32_t height) -{ - D3D11_MAPPED_SUBRESOURCE ms; - D2DX_RELEASE_CHECK_HR(_deviceContext->Map(_videoTexture.Get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &ms)); - memcpy(ms.pData, pixels, width * height * 4); - _deviceContext->Unmap(_videoTexture.Get(), 0); - - SetVS(_gammaVS.Get()); - SetPS(_videoPS.Get()); - - SetBlendState(AlphaBlend::Opaque); - - ID3D11ShaderResourceView* srvs[2] = { _videoTextureSrv.Get(), nullptr }; - SetPSShaderResourceViews(srvs); - - SetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST); - - uint32_t startVertexLocation = _vbWriteIndex; - uint32_t vertexCount = UpdateVerticesWithFullScreenQuad(_metrics.renderWidth, _metrics.renderHeight); - - _deviceContext->Draw(vertexCount, startVertexLocation); - - Present(); -} - -void D3D11Context::SetBlendState(AlphaBlend alphaBlend) -{ - ComPtr blendState = _blendStates[(int32_t)alphaBlend]; - - if (!blendState) - { - D3D11_BLEND_DESC blendDesc; - ZeroMemory(&blendDesc, sizeof(D3D11_BLEND_DESC)); - - if (alphaBlend == AlphaBlend::Opaque) - { - blendDesc.RenderTarget[0].BlendEnable = FALSE; - blendDesc.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL; - blendDesc.RenderTarget[0].SrcBlend = D3D11_BLEND_ONE; - blendDesc.RenderTarget[0].DestBlend = D3D11_BLEND_ZERO; - blendDesc.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ZERO; - blendDesc.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_ZERO; - blendDesc.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD; - blendDesc.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD; - } - else if (alphaBlend == AlphaBlend::Additive) - { - blendDesc.RenderTarget[0].BlendEnable = TRUE; - blendDesc.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL; - blendDesc.RenderTarget[0].SrcBlend = D3D11_BLEND_ONE; - blendDesc.RenderTarget[0].DestBlend = D3D11_BLEND_ONE; - blendDesc.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ZERO; - blendDesc.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_ZERO; - blendDesc.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD; - blendDesc.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD; - } - else if (alphaBlend == AlphaBlend::Multiplicative) - { - blendDesc.RenderTarget[0].BlendEnable = TRUE; - blendDesc.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL; - blendDesc.RenderTarget[0].SrcBlend = D3D11_BLEND_ZERO; - blendDesc.RenderTarget[0].DestBlend = D3D11_BLEND_SRC_COLOR; - blendDesc.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ZERO; - blendDesc.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_ZERO; - blendDesc.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD; - blendDesc.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD; - } - else if (alphaBlend == AlphaBlend::SrcAlphaInvSrcAlpha) - { - blendDesc.RenderTarget[0].BlendEnable = TRUE; - blendDesc.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL; - blendDesc.RenderTarget[0].SrcBlend = D3D11_BLEND_SRC_ALPHA; - blendDesc.RenderTarget[0].DestBlend = D3D11_BLEND_INV_SRC_ALPHA; - blendDesc.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ZERO; - blendDesc.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_ZERO; - blendDesc.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD; - blendDesc.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD; - } - else - { - assert(false && "Unhandled alphablend."); - } - - D2DX_RELEASE_CHECK_HR(_device->CreateBlendState(&blendDesc, &blendState)); - - _blendStates[(int32_t)alphaBlend] = blendState; - } - - SetBlendState(blendState.Get()); -} - -void D3D11Context::UpdateConstants() -{ - _constants.screenSize[0] = (float)_metrics.renderWidth; - _constants.screenSize[1] = (float)_metrics.renderHeight; - - if (_options.screenMode == ScreenMode::FullscreenDefault) - { - float scale = (float)_metrics.renderHeight / _metrics.gameHeight; - const uint32_t scaledWidth = (uint32_t)(scale * _metrics.gameWidth); - _constants.vertexOffset[0] = (float)(_metrics.desktopWidth / 2 - scaledWidth / 2); - _constants.vertexOffset[1] = 0.0f; - _constants.vertexScale[0] = scale; - _constants.vertexScale[1] = scale; - } - else - { - float scale = (float)_metrics.renderHeight / _metrics.gameHeight; - _constants.vertexOffset[0] = 0.0f; - _constants.vertexOffset[1] = 0.0f; - _constants.vertexScale[0] = scale; - _constants.vertexScale[1] = scale; - } - - if (memcmp(&_constants, &_shadowState._constants, sizeof(Constants)) != 0) - { - D3D11_MAPPED_SUBRESOURCE mappedSubResource = { 0 }; - D2DX_RELEASE_CHECK_HR(_deviceContext->Map(_cb.Get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedSubResource)); - memcpy(mappedSubResource.pData, &_constants, sizeof(Constants)); - _deviceContext->Unmap(_cb.Get(), 0); - _shadowState._constants = _constants; - } -} - -void D3D11Context::BulkWriteVertices(const Vertex* vertices, uint32_t vertexCount) -{ - assert(vertexCount <= _vbCapacity); - vertexCount = min(vertexCount, _vbCapacity); - - D3D11_MAP mapType = D3D11_MAP_WRITE_DISCARD; - _vbWriteIndex = 0; - - D3D11_MAPPED_SUBRESOURCE mappedSubResource = { 0 }; - D2DX_RELEASE_CHECK_HR(_deviceContext->Map(_vb.Get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedSubResource)); - Vertex* pMappedVertices = (Vertex*)mappedSubResource.pData + _vbWriteIndex; - - memcpy(pMappedVertices, vertices, sizeof(Vertex) * vertexCount); - - _deviceContext->Unmap(_vb.Get(), 0); - - _vbWriteIndex += vertexCount; -} - -uint32_t D3D11Context::UpdateVerticesWithFullScreenQuad(int32_t width, int32_t height) -{ - Vertex vertices[6] = { - Vertex{ 0.0f, 0.0f, 0, 0, 0xFFFFFFFF, RgbCombine::ColorMultipliedByTexture, AlphaCombine::One, false, 0, 0 }, - Vertex{ (float)width, 0.0f, (int16_t)width, 0, 0xFFFFFFFF, RgbCombine::ColorMultipliedByTexture, AlphaCombine::One, false, 0, 0 }, - Vertex{ (float)width, (float)height, (int16_t)width, (int16_t)height, 0xFFFFFFFF, RgbCombine::ColorMultipliedByTexture, AlphaCombine::One, false, 0, 0 }, - - Vertex{ 0.0f, 0.0f, 0, 0, 0xFFFFFFFF, RgbCombine::ColorMultipliedByTexture, AlphaCombine::One, false, 0, 0 }, - Vertex{ (float)width, (float)height, (int16_t)width, (int16_t)height, 0xFFFFFFFF, RgbCombine::ColorMultipliedByTexture, AlphaCombine::One, false, 0, 0 }, - Vertex{ 0.0f, (float)height, 0, (int16_t)height, 0xFFFFFFFF, RgbCombine::ColorMultipliedByTexture, AlphaCombine::One, false, 0, 0 } - }; - - D3D11_MAP mapType = D3D11_MAP_WRITE_NO_OVERWRITE; - if ((_vbWriteIndex + ARRAYSIZE(vertices)) > _vbCapacity) - { - _vbWriteIndex = 0; - mapType = D3D11_MAP_WRITE_DISCARD; - } - - D3D11_MAPPED_SUBRESOURCE mappedSubResource = { 0 }; - D2DX_RELEASE_CHECK_HR(_deviceContext->Map(_vb.Get(), 0, mapType, 0, &mappedSubResource)); - Vertex* pMappedVertices = (Vertex*)mappedSubResource.pData + _vbWriteIndex; - - memcpy(pMappedVertices, vertices, sizeof(Vertex) * ARRAYSIZE(vertices)); - - _deviceContext->Unmap(_vb.Get(), 0); - - _vbWriteIndex += ARRAYSIZE(vertices); - - return 6; -} - -TextureCacheLocation D3D11Context::UpdateTexture(const Batch& batch, const uint8_t* tmuData) -{ - assert(batch.IsValid() && "Batch has no texture set."); - const uint32_t contentKey = batch.GetHash() ^ batch.GetPaletteIndex(); - - TextureCache* atlas = GetTextureCache(batch); - auto tcl = atlas->FindTexture(contentKey, -1); - - if (tcl._textureAtlas < 0) - { - tcl = atlas->InsertTexture(contentKey, batch, tmuData); - } - - return tcl; -} - -void D3D11Context::UpdateViewport(int32_t width, int32_t height) -{ - CD3D11_VIEWPORT viewport{ 0.0f, 0.0f, (float)width, (float)height }; - _deviceContext->RSSetViewports(1, &viewport); -} - -void D3D11Context::SetGamma(float red, float green, float blue) -{ - uint32_t gammaTable[256]; - - for (int32_t i = 0; i < 256; ++i) - { - float v = i / 255.0f; - float r = powf(v, 1.0f / red); - float g = powf(v, 1.0f / green); - float b = powf(v, 1.0f / blue); - uint32_t ri = (uint32_t)(r * 255.0f); - uint32_t gi = (uint32_t)(g * 255.0f); - uint32_t bi = (uint32_t)(b * 255.0f); - gammaTable[i] = (ri << 16) | (gi << 8) | bi; - } - - LoadGammaTable(gammaTable); -} - -void D3D11Context::SetPalette(int32_t paletteIndex, const uint32_t* palette) -{ - _deviceContext->UpdateSubresource(_paletteTexture.Get(), paletteIndex, nullptr, palette, 1024, 0); -} - -const Options& D3D11Context::GetOptions() const -{ - return _options; -} - -LRESULT CALLBACK d2dxSubclassWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, - LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData) -{ - D3D11Context* d3d11Context = (D3D11Context*)dwRefData; - - if (uMsg == WM_SYSKEYDOWN || uMsg == WM_KEYDOWN) - { - if (wParam == VK_RETURN && (HIWORD(lParam) & KF_ALTDOWN)) - { - d3d11Context->ToggleFullscreen(); - return 0; - } - } - else if (uMsg == WM_DESTROY) - { - RemoveWindowSubclass(hWnd, d2dxSubclassWndProc, 1234); - g_d2dxContext = nullptr; - } - else if (uMsg == WM_NCMOUSEMOVE) - { - ShowCursor_Real(TRUE); - return 0; - } - else if (uMsg >= WM_MOUSEFIRST && uMsg <= WM_MOUSELAST) - { -#ifdef NDEBUG - ShowCursor_Real(FALSE); -#endif - uint32_t x = LOWORD(lParam); - uint32_t y = HIWORD(lParam); - - const D3D11Context::Metrics& metrics = d3d11Context->GetMetrics(); - - const bool isFullscreen = d3d11Context->GetOptions().screenMode == ScreenMode::FullscreenDefault; - const float scale = (float)metrics.renderHeight / metrics.gameHeight; - const uint32_t scaledWidth = (uint32_t)(scale * metrics.gameWidth); - const float mouseOffsetX = isFullscreen ? (float)(metrics.desktopWidth / 2 - scaledWidth / 2) : 0.0f; - - x = (uint32_t)(max(0, x - mouseOffsetX) / scale); - y = (uint32_t)(y / scale); - - g_d2dxContext->OnMousePosChanged(x, y); - - lParam = x; - lParam |= y << 16; - } - - return DefSubclassProc(hWnd, uMsg, wParam, lParam); -} - -uint32_t D3D11Context::DetermineMaxTextureArraySize() -{ - HRESULT hr = E_FAIL; - - for (uint32_t arraySize = 2048; arraySize > 512; arraySize /= 2) - { - CD3D11_TEXTURE2D_DESC desc{ - DXGI_FORMAT_R8G8B8A8_UNORM, - 16U, - 16U, - arraySize, - 1U, - D3D11_BIND_SHADER_RESOURCE, - D3D11_USAGE_DEFAULT - }; - - ComPtr tempTexture; - hr = _device->CreateTexture2D(&desc, nullptr, &tempTexture); - - if (SUCCEEDED(hr)) - { - return arraySize; - } - - } while (FAILED(hr)); - - return 512; -} - -void D3D11Context::CreateTextureCaches() -{ - static const uint32_t capacities[6] = { 2048, 2048, 2048, 2048, 2048, 2048 }; - - const uint32_t texturesPerAtlas = DetermineMaxTextureArraySize(); - ALWAYS_PRINT("The device supports %u textures per atlas.", texturesPerAtlas); - - uint32_t totalSize = 0; - for (int32_t i = 0; i < ARRAYSIZE(_textureCaches); ++i) - { - int32_t width = 1U << (i + 3); - - _textureCaches[i] = make_unique(width, width, capacities[i], texturesPerAtlas, _device.Get(), _simd, _textureProcessor); - - DEBUG_PRINT("Creating texture cache for %i x %i with capacity %u (%u kB).", width, width, capacities[i], _textureCaches[i]->GetMemoryFootprint() / 1024); - - totalSize += _textureCaches[i]->GetMemoryFootprint(); - } - - ALWAYS_PRINT("Total size of texture caches is %u kB.", totalSize / 1024); -} - -void D3D11Context::SetVS(ID3D11VertexShader* vs) -{ - if (vs != _shadowState._lastVS) - { - _deviceContext->VSSetShader(vs, NULL, 0); - _shadowState._lastVS = vs; - } -} - -void D3D11Context::SetPS(ID3D11PixelShader* ps) -{ - if (ps != _shadowState._lastPS) - { - _deviceContext->PSSetShader(ps, NULL, 0); - _shadowState._lastPS = ps; - } -} - -void D3D11Context::SetBlendState(ID3D11BlendState* blendState) -{ - if (blendState != _shadowState._lastBlendState) - { - float blendFactor[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; - UINT sampleMask = 0xffffffff; - _deviceContext->OMSetBlendState(blendState, blendFactor, sampleMask); - _shadowState._lastBlendState = blendState; - } -} - -void D3D11Context::SetPSShaderResourceViews(ID3D11ShaderResourceView* srvs[2]) -{ - if (srvs[0] != _shadowState._psSrvs[0] || - srvs[1] != _shadowState._psSrvs[1]) - { - _deviceContext->PSSetShaderResources(0, 2, srvs); - _shadowState._psSrvs[0] = srvs[0]; - _shadowState._psSrvs[1] = srvs[1]; - } -} - -void D3D11Context::SetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY pt) -{ - if (pt != _shadowState._primitiveTopology) - { - _deviceContext->IASetPrimitiveTopology(pt); - _shadowState._primitiveTopology = pt; - } -} - -#define MAX_DIMS 6 - -TextureCache* D3D11Context::GetTextureCache(const Batch& batch) const -{ - const int32_t longest = max(batch.GetWidth(), batch.GetHeight()); - assert(longest >= 8); - uint32_t log2Longest = 0; - BitScanForward((DWORD*)&log2Longest, (DWORD)longest); - log2Longest -= 3; - assert(log2Longest <= 5); - return _textureCaches[log2Longest].get(); -} - -void D3D11Context::AdjustWindowPlacement(HWND hWnd, bool centerOnCurrentPosition) -{ - const int32_t desktopCenterX = _metrics.desktopWidth / 2; - const int32_t desktopCenterY = _metrics.desktopHeight / 2; - - if (_options.screenMode == ScreenMode::Windowed) - { - RECT oldWindowRect; - RECT oldWindowClientRect; - GetWindowRect(hWnd, &oldWindowRect); - GetClientRect(hWnd, &oldWindowClientRect); - const int32_t oldWindowWidth = oldWindowRect.right - oldWindowRect.left; - const int32_t oldWindowHeight = oldWindowRect.bottom - oldWindowRect.top; - const int32_t oldWindowClientWidth = oldWindowClientRect.right - oldWindowClientRect.left; - const int32_t oldWindowClientHeight = oldWindowClientRect.bottom - oldWindowClientRect.top; - const int32_t oldWindowCenterX = (oldWindowRect.left + oldWindowRect.right) / 2; - const int32_t oldWindowCenterY = (oldWindowRect.top + oldWindowRect.bottom) / 2; - - _metrics.windowWidth = _metrics.renderWidth; - _metrics.windowHeight = _metrics.renderHeight; - - if (_metrics.windowHeight > _metrics.desktopClientMaxHeight) - { - _metrics.windowWidth *= (int32_t)((float)_metrics.desktopClientMaxHeight / _metrics.windowHeight); - _metrics.windowHeight = _metrics.desktopClientMaxHeight; - _metrics.renderWidth = _metrics.windowWidth; - _metrics.renderHeight = _metrics.windowHeight; - } - - const int32_t newWindowWidth = (oldWindowWidth - oldWindowClientWidth) + _metrics.windowWidth; - const int32_t newWindowHeight = (oldWindowHeight - oldWindowClientHeight) + _metrics.windowHeight; - const int32_t newWindowCenterX = centerOnCurrentPosition ? oldWindowCenterX : desktopCenterX; - const int32_t newWindowCenterY = centerOnCurrentPosition ? oldWindowCenterY : desktopCenterY; - const int32_t newWindowX = newWindowCenterX - newWindowWidth / 2; - const int32_t newWindowY = newWindowCenterY - newWindowHeight / 2; - - SetWindowLongPtr(hWnd, GWL_STYLE, WS_VISIBLE | WS_OVERLAPPEDWINDOW); - SetWindowPos_Real(hWnd, HWND_TOP, newWindowX, newWindowY, newWindowWidth, newWindowHeight, SWP_SHOWWINDOW | SWP_NOSENDCHANGING | SWP_FRAMECHANGED); - -#ifndef NDEBUG - RECT newWindowRect; - GetWindowRect(hWnd, &newWindowRect); - assert(newWindowWidth == (newWindowRect.right - newWindowRect.left)); - assert(newWindowHeight == (newWindowRect.bottom - newWindowRect.top)); -#endif - } - else if (_options.screenMode == ScreenMode::FullscreenDefault) - { - SetWindowLongPtr(hWnd, GWL_STYLE, WS_VISIBLE | WS_POPUP); - SetWindowPos_Real(hWnd, HWND_TOP, 0, 0, _metrics.desktopWidth, _metrics.desktopHeight, SWP_SHOWWINDOW | SWP_NOSENDCHANGING | SWP_FRAMECHANGED); - - _metrics.renderWidth = _metrics.desktopWidth; - _metrics.renderHeight = _metrics.desktopHeight; - } - - char newWindowText[256]; - sprintf_s(newWindowText, "Diablo II DX [%ix%i, scale %i%%]", - _metrics.gameWidth, _metrics.gameHeight, (int)(((float)_metrics.renderHeight / _metrics.gameHeight) * 100.0f)); - SetWindowTextA(hWnd, newWindowText); - - ALWAYS_PRINT("Desktop size %ix%i", _metrics.desktopWidth, _metrics.desktopHeight); - ALWAYS_PRINT("Window size %ix%i", _metrics.windowWidth, _metrics.windowHeight); - ALWAYS_PRINT("Game size %ix%i", _metrics.gameWidth, _metrics.gameHeight); - ALWAYS_PRINT("Render size %ix%i", _metrics.renderWidth, _metrics.renderHeight); - - UpdateConstants(); - - if (_swapChain2) - { - _swapChain2->SetSourceSize(_metrics.renderWidth, _metrics.renderHeight); - } - - CD3D11_RECT scissorRect{ (LONG)_constants.vertexOffset[0], 0, _metrics.renderWidth - (LONG)_constants.vertexOffset[0], _metrics.renderHeight }; - _deviceContext->RSSetScissorRects(1, &scissorRect); - - UpdateViewport(_metrics.renderWidth, _metrics.renderHeight); - - Present(); -} - -void D3D11Context::SetSizes(int32_t gameWidth, int32_t gameHeight, int32_t renderWidth, int32_t renderHeight) -{ - _metrics.gameWidth = gameWidth; - _metrics.gameHeight = gameHeight; - - if (renderWidth > 0 && renderHeight > 0) - { - _metrics.renderWidth = renderWidth; - _metrics.renderHeight = renderHeight; - } - - if (_metrics.gameWidth > _metrics.renderWidth || _metrics.gameHeight > _metrics.renderHeight) - { - _metrics.renderWidth = _metrics.gameWidth; - _metrics.renderHeight = _metrics.gameHeight; - } - - AdjustWindowPlacement(_hWnd, true); -} - -bool D3D11Context::IsAllowTearingFlagSupported() const -{ - ComPtr dxgiFactory4; - - if (FAILED(CreateDXGIFactory1(IID_PPV_ARGS(&dxgiFactory4)))) - { - return false; - } - - ComPtr dxgiFactory5; - - if (FAILED(dxgiFactory4.As(&dxgiFactory5))) - { - return false; - } - - BOOL allowTearing = FALSE; - - if (SUCCEEDED(dxgiFactory5->CheckFeatureSupport(DXGI_FEATURE_PRESENT_ALLOW_TEARING, &allowTearing, sizeof(allowTearing)))) - { - return allowTearing; - } - - return false; -} - -void D3D11Context::ToggleFullscreen() -{ - if (_options.screenMode == ScreenMode::FullscreenDefault) - { - _options.screenMode = ScreenMode::Windowed; - SetSizes(_metrics.gameWidth, _metrics.gameHeight, _metrics.windowWidth, _metrics.windowHeight); - } - else - { - _options.screenMode = ScreenMode::FullscreenDefault; - SetSizes(_metrics.gameWidth, _metrics.gameHeight, _metrics.desktopWidth, _metrics.desktopHeight); - } -} - -const D3D11Context::Metrics& D3D11Context::GetMetrics() const -{ - return _metrics; -} diff --git a/src/d2dx/D3D11Context.h b/src/d2dx/D3D11Context.h deleted file mode 100644 index 493df08..0000000 --- a/src/d2dx/D3D11Context.h +++ /dev/null @@ -1,173 +0,0 @@ -/* - This file is part of D2DX. - - Copyright (C) 2021 Bolrog - - D2DX is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - D2DX is distributed in the hope that it will be 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 D2DX. If not, see . -*/ -#pragma once - -#include "TextureCache.h" -#include "Types.h" - -namespace d2dx -{ - class Vertex; - class Batch; - class Simd; - - class D3D11Context final - { - public: - D3D11Context( - HWND hWnd, - int32_t windowWidth, - int32_t windowHeight, - int32_t gameWidth, - int32_t gameHeight, - Options& options, - std::shared_ptr simd, - std::shared_ptr textureProcessor); - - ~D3D11Context(); - - void LoadGammaTable(const uint32_t* paletteAndGamma); - void BulkWriteVertices(const Vertex* vertices, uint32_t vertexCount); - TextureCacheLocation UpdateTexture(const Batch& batch, const uint8_t* tmuData); - void Draw(const Batch& batch); - void Present(); - void WriteToScreen(const uint32_t* pixels, int32_t width, int32_t height); - void SetGamma(float red, float green, float blue); - void SetPalette(int32_t paletteIndex, const uint32_t* palette); - - const Options& GetOptions() const; - - TextureCache* GetTextureCache(const Batch& batch) const; - - void SetSizes(int32_t gameWidth, int32_t gameHeight, int32_t renderWidth, int32_t renderHeight); - - struct Metrics - { - int32_t desktopWidth; - int32_t desktopHeight; - int32_t desktopClientMaxHeight; - int32_t windowWidth; - int32_t windowHeight; - int32_t renderWidth; - int32_t renderHeight; - int32_t gameWidth; - int32_t gameHeight; - }; - - const Metrics& GetMetrics() const; - - void ToggleFullscreen(); - - private: - void CreateRasterizerState(); - void CreateVertexAndIndexBuffers(); - void CreateShadersAndInputLayout(); - void CreateConstantBuffers(); - void CreateGammaTexture(); - void CreatePaletteTexture(); - void CreateVideoTextures(); - void CreateRenderTarget(); - void CreateSamplerStates(); - void CreateTextureCaches(); - uint32_t DetermineMaxTextureArraySize(); - void UpdateViewport(int32_t width, int32_t height); - void SetBlendState(AlphaBlend alphaBlend); - void UpdateConstants(); - void AdjustWindowPlacement(HWND hWnd, bool centerOnCurrentPosition); - uint32_t UpdateVerticesWithFullScreenQuad(int32_t width, int32_t height); - bool IsAllowTearingFlagSupported() const; - void SetVS(ID3D11VertexShader* vs); - void SetPS(ID3D11PixelShader* ps); - void SetBlendState(ID3D11BlendState* blendState); - void SetPSShaderResourceViews(ID3D11ShaderResourceView* srvs[2]); - void SetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY pt); - - Metrics _metrics; - - struct Constants - { - float vertexOffset[2]; - float vertexScale[2]; - float screenSize[2]; - float dummy[2]; - }; - - static_assert(sizeof(Constants) == 8 * 4, "size of Constants"); - - uint32_t _vbWriteIndex; - uint32_t _vbCapacity; - - Constants _constants; - - bool _dxgiAllowTearingFlagSupported; - D3D_FEATURE_LEVEL _featureLevel; - Microsoft::WRL::ComPtr _device; - Microsoft::WRL::ComPtr _device3; - Microsoft::WRL::ComPtr _deviceContext; - Microsoft::WRL::ComPtr _deviceContext1; - Microsoft::WRL::ComPtr _swapChain; - Microsoft::WRL::ComPtr _swapChain2; - Microsoft::WRL::ComPtr _rasterizerStateNoScissor; - Microsoft::WRL::ComPtr _rasterizerState; - Microsoft::WRL::ComPtr _inputLayout; - Microsoft::WRL::ComPtr _vb; - Microsoft::WRL::ComPtr _cb; - Microsoft::WRL::ComPtr _vs; - Microsoft::WRL::ComPtr _ps; - Microsoft::WRL::ComPtr _videoPS; - Microsoft::WRL::ComPtr _gammaVS; - Microsoft::WRL::ComPtr _gammaPS; - Microsoft::WRL::ComPtr _backbufferRtv; - Microsoft::WRL::ComPtr _samplerState[2]; - - Microsoft::WRL::ComPtr _videoTexture; - Microsoft::WRL::ComPtr _videoTextureSrv; - - Microsoft::WRL::ComPtr _blendStates[(int32_t)AlphaBlend::Count]; - - Microsoft::WRL::ComPtr _gammaTexture; - Microsoft::WRL::ComPtr _gammaTextureSrv; - - Microsoft::WRL::ComPtr _paletteTexture; - Microsoft::WRL::ComPtr _paletteTextureSrv; - - Microsoft::WRL::ComPtr _renderTargetTexture; - Microsoft::WRL::ComPtr _renderTargetTextureRtv; - Microsoft::WRL::ComPtr _renderTargetTextureSrv; - - std::unique_ptr _textureCaches[6]; - - HWND _hWnd; - Options& _options; - - struct - { - ID3D11VertexShader* _lastVS; - ID3D11PixelShader* _lastPS; - ID3D11BlendState* _lastBlendState; - ID3D11ShaderResourceView* _psSrvs[2]; - D3D11_PRIMITIVE_TOPOLOGY _primitiveTopology; - Constants _constants; - } _shadowState; - - HANDLE _frameLatencyWaitableObject; - std::shared_ptr _simd; - std::shared_ptr _textureProcessor; - }; -} \ No newline at end of file diff --git a/src/d2dx/Detours.cpp b/src/d2dx/Detours.cpp new file mode 100644 index 0000000..659d6ba --- /dev/null +++ b/src/d2dx/Detours.cpp @@ -0,0 +1,877 @@ +/* + This file is part of D2DX. + + Copyright (C) 2021 Bolrog + + D2DX is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + D2DX is distributed in the hope that it will be 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 D2DX. If not, see . +*/ +#include "pch.h" +#include "Detours.h" +#include "Utils.h" +#include "D2DXContextFactory.h" +#include "IWin32InterceptionHandler.h" +#include "IGameHelper.h" + +using namespace d2dx; + +#pragma comment(lib, "../../thirdparty/detours/detours.lib") + +static bool hasDetoured = false; +static bool hasDetachedDetours = false; +static bool hasLateDetoured = false; +static bool hasDetachedLateDetours = false; + +D2::UnitAny* currentlyDrawingUnit = nullptr; +uint32_t currentlyDrawingWeatherParticles = 0; +uint32_t* currentlyDrawingWeatherParticleIndexPtr = nullptr; + +static IWin32InterceptionHandler* GetWin32InterceptionHandler() +{ + ID2DXContext* d2dxContext = D2DXContextFactory::GetInstance(false); + + if (!d2dxContext) + { + return nullptr; + } + + return static_cast(d2dxContext); +} + +static ID2InterceptionHandler* GetD2InterceptionHandler() +{ + return D2DXContextFactory::GetInstance(false); +} + +static thread_local bool isInSleepCall = false; + +static VOID +(WINAPI* +Sleep_Real)( + _In_ DWORD dwMilliseconds +) = Sleep; + +static VOID WINAPI Sleep_Hooked( + _In_ DWORD dwMilliseconds) +{ + auto win32InterceptionHandler = GetWin32InterceptionHandler(); + if (win32InterceptionHandler) + { + int32_t adjustedMs = win32InterceptionHandler->OnSleep((int32_t)dwMilliseconds); + + if (adjustedMs >= 0) + { + isInSleepCall = true; + Sleep_Real((DWORD)adjustedMs); + isInSleepCall = false; + } + } + else + { + isInSleepCall = true; + Sleep_Real(dwMilliseconds); + isInSleepCall = false; + } +} + +static DWORD +(WINAPI* + SleepEx_Real)( + _In_ DWORD dwMilliseconds, + _In_ BOOL bAlertable + ) = SleepEx; + +static DWORD WINAPI SleepEx_Hooked( + _In_ DWORD dwMilliseconds, + _In_ BOOL bAlertable) +{ + if (isInSleepCall) + { + return SleepEx_Real(dwMilliseconds, bAlertable); + } + + auto win32InterceptionHandler = GetWin32InterceptionHandler(); + if (win32InterceptionHandler) + { + int32_t adjustedMs = win32InterceptionHandler->OnSleep((int32_t)dwMilliseconds); + + if (adjustedMs >= 0) + { + return SleepEx_Real((DWORD)adjustedMs, bAlertable); + } + } + else + { + return SleepEx_Real(dwMilliseconds, bAlertable); + } + + return 0; +} + +COLORREF(WINAPI* GetPixel_real)( + _In_ HDC hdc, + _In_ int x, + _In_ int y) = GetPixel; + +COLORREF WINAPI GetPixel_Hooked(_In_ HDC hdc, _In_ int x, _In_ int y) +{ + /* Gets rid of the long delay on startup and when switching between menus in < 1.14, + as the game is doing a ton of pixel readbacks... for some reason. */ + return 0; +} + +int (WINAPI* ShowCursor_Real)( + _In_ BOOL bShow) = ShowCursor; + +int WINAPI ShowCursor_Hooked( + _In_ BOOL bShow) +{ + /* Override how the game hides/shows the cursor. We will take care of that. */ + return bShow ? 1 : -1; +} + +BOOL(WINAPI* SetWindowPos_Real)( + _In_ HWND hWnd, + _In_opt_ HWND hWndInsertAfter, + _In_ int X, + _In_ int Y, + _In_ int cx, + _In_ int cy, + _In_ UINT uFlags) = SetWindowPos; + +BOOL +WINAPI +SetWindowPos_Hooked( + _In_ HWND hWnd, + _In_opt_ HWND hWndInsertAfter, + _In_ int X, + _In_ int Y, + _In_ int cx, + _In_ int cy, + _In_ UINT uFlags) +{ + /* Stop the game from moving/sizing the window. */ + return SetWindowPos_Real(hWnd, hWndInsertAfter, X, Y, cx, cy, uFlags | SWP_NOSIZE | SWP_NOMOVE); +} + +BOOL +(WINAPI* + SetCursorPos_Real)( + _In_ int X, + _In_ int Y) = SetCursorPos; + +BOOL +WINAPI +SetCursorPos_Hooked( + _In_ int X, + _In_ int Y) +{ + auto win32InterceptionHandler = GetWin32InterceptionHandler(); + + if (!win32InterceptionHandler) + { + return FALSE; + } + + auto adjustedPos = win32InterceptionHandler->OnSetCursorPos({ X, Y }); + + if (adjustedPos.x < 0) + { + return FALSE; + } + + return SetCursorPos_Real(adjustedPos.x, adjustedPos.y); +} + +LRESULT +(WINAPI* + SendMessageA_Real)( + _In_ HWND hWnd, + _In_ UINT Msg, + _Pre_maybenull_ _Post_valid_ WPARAM wParam, + _Pre_maybenull_ _Post_valid_ LPARAM lParam) = SendMessageA; + +LRESULT +WINAPI +SendMessageA_Hooked( + _In_ HWND hWnd, + _In_ UINT Msg, + _Pre_maybenull_ _Post_valid_ WPARAM wParam, + _Pre_maybenull_ _Post_valid_ LPARAM lParam) +{ + if (Msg == WM_MOUSEMOVE) + { + auto win32InterceptionHandler = GetWin32InterceptionHandler(); + + if (!win32InterceptionHandler) + { + return 0; + } + + auto x = GET_X_LPARAM(lParam); + auto y = GET_Y_LPARAM(lParam); + + auto adjustedPos = win32InterceptionHandler->OnMouseMoveMessage({ x, y }); + + if (adjustedPos.x < 0) + { + return 0; + } + + lParam = ((uint16_t)adjustedPos.y << 16) | ((uint16_t)adjustedPos.x & 0xFFFF); + } + + return SendMessageA_Real(hWnd, Msg, wParam, lParam); +} + +_Success_(return) +int +WINAPI +DrawTextA_Hooked( + _In_ HDC hdc, + _When_((format & DT_MODIFYSTRING), _At_((LPSTR)lpchText, _Inout_grows_updates_bypassable_or_z_(cchText, 4))) + _When_((!(format & DT_MODIFYSTRING)), _In_bypassable_reads_or_z_(cchText)) + LPCSTR lpchText, + _In_ int cchText, + _Inout_ LPRECT lprc, + _In_ UINT format) +{ + /* This removes the "weird characters" being printed by the game in the top left corner. + There is still a delay but the GetPixel hook takes care of that... */ + return 0; +} + + +_Success_(return) +int( + WINAPI * + DrawTextA_Real)( + _In_ HDC hdc, + _When_((format & DT_MODIFYSTRING), _At_((LPSTR)lpchText, _Inout_grows_updates_bypassable_or_z_(cchText, 4))) + _When_((!(format & DT_MODIFYSTRING)), _In_bypassable_reads_or_z_(cchText)) + LPCSTR lpchText, + _In_ int cchText, + _Inout_ LPRECT lprc, + _In_ UINT format) = DrawTextA; + + +typedef void(__stdcall* D2Gfx_DrawImageFunc)(D2::CellContext* pData, int nXpos, int nYpos, DWORD dwGamma, int nDrawMode, BYTE* pPalette); +typedef void(__stdcall* D2Gfx_DrawShiftedImageFunc)(D2::CellContext* pData, int nXpos, int nYpos, DWORD dwGamma, int nDrawMode, int nGlobalPaletteShift); +typedef void(__stdcall* D2Gfx_DrawVerticalCropImageFunc)(D2::CellContext* pData, int nXpos, int nYpos, int nSkipLines, int nDrawLines, int nDrawMode); +typedef void(__stdcall* D2Gfx_DrawClippedImageFunc)(D2::CellContext* pData, int nXpos, int nYpos, void* pCropRect, int nDrawMode); +typedef void(__stdcall* D2Gfx_DrawImageFastFunc)(D2::CellContext* pData, int nXpos, int nYpos, BYTE nPaletteIndex); +typedef void(__stdcall* D2Gfx_DrawShadowFunc)(D2::CellContext* pData, int nXpos, int nYpos); +typedef void(__fastcall* D2Win_DrawTextFunc)(const wchar_t* wStr, int xPos, int yPos, DWORD dwColor, DWORD centered); +typedef void(__fastcall* D2Win_DrawTextExFunc)(const wchar_t* wStr, int xPos, int yPos, DWORD dwColor, DWORD centered, DWORD transparencyLevel); +typedef void(__fastcall* D2Win_DrawFramedTextFunc)(const wchar_t* wStr, int xPos, int yPos, DWORD dwColor, DWORD centered); +typedef void(__fastcall* D2Win_DrawRectangledTextFunc)(const wchar_t* wStr, int xPos, int yPos, DWORD rectColor, DWORD rectTransparency, DWORD color); +typedef uint32_t(__stdcall* D2Client_DrawUnitFunc)(D2::UnitAny* unit, uint32_t b, uint32_t c, uint32_t d, uint32_t e); +typedef void* NakedFunc; + +D2Gfx_DrawImageFunc D2Gfx_DrawImage_Real = nullptr; +D2Gfx_DrawShiftedImageFunc D2Gfx_DrawShiftedImage_Real = nullptr; +D2Gfx_DrawVerticalCropImageFunc D2Gfx_DrawVerticalCropImage_Real = nullptr; +D2Gfx_DrawClippedImageFunc D2Gfx_DrawClippedImage_Real = nullptr; +D2Gfx_DrawImageFastFunc D2Gfx_DrawImageFast_Real = nullptr; +D2Gfx_DrawShadowFunc D2Gfx_DrawShadow_Real = nullptr; +D2Win_DrawTextFunc D2Win_DrawText_Real = nullptr; +//D2Win_DrawTextExFunc D2Win_DrawTextEx_Real = nullptr; +D2Win_DrawFramedTextFunc D2Win_DrawFramedText_Real = nullptr; +D2Win_DrawRectangledTextFunc D2Win_DrawRectangledText_Real = nullptr; +D2Client_DrawUnitFunc D2Client_DrawUnit_Real = nullptr; +D2Client_DrawUnitFunc D2Client_DrawMissile_Real = nullptr; +NakedFunc D2Client_DrawWeatherParticles_Real = nullptr; + +void __stdcall D2Gfx_DrawImage_Hooked( + D2::CellContext * cellContext, + int nXpos, + int nYpos, + DWORD dwGamma, + int nDrawMode, + BYTE * pPalette) +{ + auto d2InterceptionHandler = GetD2InterceptionHandler(); + if (d2InterceptionHandler) + { + auto offset = d2InterceptionHandler->BeginDrawImage(cellContext, nDrawMode, { nXpos, nYpos }, D2Function::D2Gfx_DrawImage); + D2Gfx_DrawImage_Real(cellContext, nXpos + offset.x, nYpos + offset.y, dwGamma, nDrawMode, pPalette); + d2InterceptionHandler->EndDrawImage(); + } + else + { + D2Gfx_DrawImage_Real(cellContext, nXpos, nYpos, dwGamma, nDrawMode, pPalette); + } +} + +void __stdcall D2Gfx_DrawClippedImage_Hooked( + D2::CellContext * cellContext, + int nXpos, + int nYpos, + void* pCropRect, + int nDrawMode) +{ + auto d2InterceptionHandler = GetD2InterceptionHandler(); + if (d2InterceptionHandler) + { + d2InterceptionHandler->BeginDrawImage(cellContext, (uint32_t)nDrawMode, { nXpos, nYpos }, D2Function::D2Gfx_DrawClippedImage); + D2Gfx_DrawClippedImage_Real(cellContext, nXpos, nYpos, pCropRect, nDrawMode); + d2InterceptionHandler->EndDrawImage(); + } + else + { + D2Gfx_DrawClippedImage_Real(cellContext, nXpos, nYpos, pCropRect, nDrawMode); + } +} + +void __stdcall D2Gfx_DrawShiftedImage_Hooked( + D2::CellContext * cellContext, + int nXpos, + int nYpos, + DWORD dwGamma, + int nDrawMode, + int nGlobalPaletteShift) +{ + auto d2InterceptionHandler = GetD2InterceptionHandler(); + if (d2InterceptionHandler) + { + d2InterceptionHandler->BeginDrawImage(cellContext, (uint32_t)nDrawMode, { nXpos, nYpos }, D2Function::D2Gfx_DrawShiftedImage); + D2Gfx_DrawShiftedImage_Real(cellContext, nXpos, nYpos, dwGamma, nDrawMode, nGlobalPaletteShift); + d2InterceptionHandler->EndDrawImage(); + } + else + { + D2Gfx_DrawShiftedImage_Real(cellContext, nXpos, nYpos, dwGamma, nDrawMode, nGlobalPaletteShift); + } +} + +void __stdcall D2Gfx_DrawVerticalCropImage_Hooked( + D2::CellContext * cellContext, + int nXpos, + int nYpos, + int nSkipLines, + int nDrawLines, + int nDrawMode) +{ + auto d2InterceptionHandler = GetD2InterceptionHandler(); + if (d2InterceptionHandler) + { + d2InterceptionHandler->BeginDrawImage(cellContext, (uint32_t)nDrawMode, { nXpos, nYpos }, D2Function::D2Gfx_DrawVerticalCropImage); + D2Gfx_DrawVerticalCropImage_Real(cellContext, nXpos, nYpos, nSkipLines, nDrawLines, nDrawMode); + d2InterceptionHandler->EndDrawImage(); + } + else + { + D2Gfx_DrawVerticalCropImage_Real(cellContext, nXpos, nYpos, nSkipLines, nDrawLines, nDrawMode); + } +} + +void __stdcall D2Gfx_DrawImageFast_Hooked( + D2::CellContext * cellContext, + int nXpos, + int nYpos, + BYTE nPaletteIndex) +{ + auto d2InterceptionHandler = GetD2InterceptionHandler(); + if (d2InterceptionHandler) + { + d2InterceptionHandler->BeginDrawImage(cellContext, (uint32_t)-1, { nXpos, nYpos }, D2Function::D2Gfx_DrawImageFast); + D2Gfx_DrawImageFast_Real(cellContext, nXpos, nYpos, nPaletteIndex); + d2InterceptionHandler->EndDrawImage(); + } + else + { + D2Gfx_DrawImageFast_Real(cellContext, nXpos, nYpos, nPaletteIndex); + } +} + +void __stdcall D2Gfx_DrawShadow_Hooked( + D2::CellContext * cellContext, + int nXpos, + int nYpos) +{ + auto d2InterceptionHandler = GetD2InterceptionHandler(); + + if (d2InterceptionHandler) + { + auto offset = d2InterceptionHandler->BeginDrawImage(cellContext, (uint32_t)-1, { nXpos, nYpos }, D2Function::D2Gfx_DrawShadow); + D2Gfx_DrawShadow_Real(cellContext, nXpos + offset.x, nYpos + offset.y); + d2InterceptionHandler->EndDrawImage(); + } + else + { + D2Gfx_DrawShadow_Real(cellContext, nXpos, nYpos); + } +} + +void __fastcall D2Win_DrawText_Hooked( + wchar_t* wStr, + int xPos, + int yPos, + DWORD dwColor, + DWORD centered) +{ + auto d2InterceptionHandler = GetD2InterceptionHandler(); + if (d2InterceptionHandler) + { + d2InterceptionHandler->BeginDrawText(wStr, { 0,0 }, (uint32_t)(uintptr_t)_ReturnAddress(), D2Function::D2Win_DrawText); + D2Win_DrawText_Real(wStr, xPos, yPos, dwColor, centered); + d2InterceptionHandler->EndDrawText(); + } + else + { + D2Win_DrawText_Real(wStr, xPos, yPos, dwColor, centered); + } +} + +//void __fastcall D2Win_DrawTextEx_Hooked( +// const wchar_t* wStr, +// int xPos, +// int yPos, +// DWORD dwColor, +// DWORD centered, +// DWORD transparency) +//{ +// auto d2InterceptionHandler = GetD2InterceptionHandler(); +// +// if (d2InterceptionHandler) +// { +// d2InterceptionHandler->BeginDrawText(); +// } +// +// D2Win_DrawTextEx_Real(wStr, xPos, yPos, dwColor, centered, transparency); +// +// if (d2InterceptionHandler) +// { +// d2InterceptionHandler->EndDrawText(); +// } +//} + +void __fastcall D2Win_DrawFramedText_Hooked( + wchar_t* wStr, + int xPos, + int yPos, + DWORD dwColor, + DWORD centered) +{ + auto d2InterceptionHandler = GetD2InterceptionHandler(); + if (d2InterceptionHandler) + { + auto offset = d2InterceptionHandler->BeginDrawText(wStr, { xPos, yPos }, (uint32_t)(uintptr_t)_ReturnAddress(), D2Function::D2Win_DrawFramedText); + D2Win_DrawFramedText_Real(wStr, xPos + offset.x, yPos + offset.y, dwColor, centered); + d2InterceptionHandler->EndDrawText(); + } + else + { + D2Win_DrawFramedText_Real(wStr, xPos, yPos, dwColor, centered); + } +} + +void __fastcall D2Win_DrawRectangledText_Hooked( + wchar_t* wStr, + int xPos, + int yPos, + DWORD rectColor, + DWORD rectTransparency, + DWORD color) +{ + auto d2InterceptionHandler = GetD2InterceptionHandler(); + if (d2InterceptionHandler) + { + auto offset = d2InterceptionHandler->BeginDrawText(wStr, { xPos, yPos }, (uint32_t)(uintptr_t)_ReturnAddress(), D2Function::D2Win_DrawRectangledText); + D2Win_DrawRectangledText_Real(wStr, xPos + offset.x, yPos + offset.y, rectColor, rectTransparency, color); + d2InterceptionHandler->EndDrawText(); + } + else + { + D2Win_DrawRectangledText_Real(wStr, xPos, yPos, rectColor, rectTransparency, color); + } +} + +__declspec(naked) void D2Client_DrawUnit_Stack_Hooked() +{ + static void* origReturnAddr = nullptr; + + __asm + { + push eax + push edx + lea edx, origReturnAddr + mov eax, dword ptr[esp + 0x08] + mov dword ptr[edx], eax + lea eax, patchReturnAddr + mov dword ptr[esp + 0x08], eax + lea edx, currentlyDrawingUnit + mov eax, dword ptr[esp + 0x0c] + mov dword ptr[edx], eax + pop edx + pop eax + } + + __asm jmp D2Client_DrawUnit_Real + + patchReturnAddr : + __asm + { + push eax + push eax + push edx + lea edx, currentlyDrawingUnit + xor eax, eax + mov dword ptr[edx], eax + lea edx, origReturnAddr + mov eax, dword ptr[edx] + mov dword ptr[esp + 0x08], eax + pop edx + pop eax + ret + } +} + +__declspec(naked) void D2Client_DrawUnit_ESI_Hooked() +{ + static void* origReturnAddr = nullptr; + + __asm + { + push eax + push edx + lea edx, origReturnAddr + mov eax, dword ptr[esp + 0x08] + mov dword ptr[edx], eax + lea eax, patchReturnAddr + mov dword ptr[esp + 0x08], eax + lea edx, currentlyDrawingUnit + mov dword ptr[edx], esi + pop edx + pop eax + } + + __asm jmp D2Client_DrawUnit_Real + + patchReturnAddr : + __asm + { + push eax + push eax + push edx + lea edx, currentlyDrawingUnit + xor eax, eax + mov dword ptr[edx], eax + lea edx, origReturnAddr + mov eax, dword ptr[edx] + mov dword ptr[esp + 0x08], eax + pop edx + pop eax + ret + } +} + +__declspec(naked) void D2Client_DrawMissile_ESI_Hooked() +{ + static void* origReturnAddr = nullptr; + + __asm + { + push eax + push edx + lea edx, origReturnAddr + mov eax, dword ptr[esp + 0x08] + mov dword ptr[edx], eax + lea eax, patchReturnAddr + mov dword ptr[esp + 0x08], eax + lea edx, currentlyDrawingUnit + mov dword ptr[edx], esi + pop edx + pop eax + } + + __asm jmp D2Client_DrawMissile_Real + + patchReturnAddr : + __asm + { + push eax + push eax + push edx + lea edx, currentlyDrawingUnit + xor eax, eax + mov dword ptr[edx], eax + lea edx, origReturnAddr + mov eax, dword ptr[edx] + mov dword ptr[esp + 0x08], eax + pop edx + pop eax + ret + } +} + +__declspec(naked) void D2Client_DrawWeatherParticles_Hooked() +{ + static void* origReturnAddr = nullptr; + + __asm + { + push eax + push edx + lea edx, origReturnAddr + mov eax, dword ptr[esp + 0x08] + mov dword ptr[edx], eax + lea eax, patchReturnAddr + mov dword ptr[esp + 0x08], eax + mov eax, 1 + lea edx, currentlyDrawingWeatherParticles + mov dword ptr[edx], eax + lea edx, currentlyDrawingWeatherParticleIndexPtr + mov eax, esp + add eax, 4 + mov dword ptr[edx], eax + pop edx + pop eax + } + + __asm jmp D2Client_DrawWeatherParticles_Real + + patchReturnAddr : + __asm + { + push eax + push eax + push edx + lea edx, currentlyDrawingWeatherParticles + xor eax, eax + mov dword ptr[edx], eax + lea edx, origReturnAddr + mov eax, dword ptr[edx] + mov dword ptr[esp + 0x08], eax + pop edx + pop eax + ret + } +} + +__declspec(naked) void D2Client_DrawWeatherParticles114d_Hooked() +{ + static void* origReturnAddr = nullptr; + + __asm + { + push eax + push edx + lea edx, origReturnAddr + mov eax, dword ptr[esp + 0x08] + mov dword ptr[edx], eax + lea eax, patchReturnAddr + mov dword ptr[esp + 0x08], eax + mov eax, 1 + lea edx, currentlyDrawingWeatherParticles + mov dword ptr[edx], eax + lea edx, currentlyDrawingWeatherParticleIndexPtr + mov eax, esp + sub eax, 10h + mov dword ptr[edx], eax + pop edx + pop eax + } + + __asm jmp D2Client_DrawWeatherParticles_Real + + patchReturnAddr : + __asm + { + push eax + push eax + push edx + lea edx, currentlyDrawingWeatherParticles + xor eax, eax + mov dword ptr[edx], eax + lea edx, origReturnAddr + mov eax, dword ptr[edx] + mov dword ptr[esp + 0x08], eax + pop edx + pop eax + ret + } +} + +void d2dx::AttachDetours() +{ + if (hasDetoured) + { + return; + } + + hasDetoured = true; + + DetourTransactionBegin(); + DetourUpdateThread(GetCurrentThread()); + DetourAttach(&(PVOID&)DrawTextA_Real, DrawTextA_Hooked); + DetourAttach(&(PVOID&)GetPixel_real, GetPixel_Hooked); + DetourAttach(&(PVOID&)SendMessageA_Real, SendMessageA_Hooked); + DetourAttach(&(PVOID&)ShowCursor_Real, ShowCursor_Hooked); + DetourAttach(&(PVOID&)SetCursorPos_Real, SetCursorPos_Hooked); + auto r = DetourAttach(&(PVOID&)SetWindowPos_Real, SetWindowPos_Hooked); + + LONG lError = DetourTransactionCommit(); + + if (lError != NO_ERROR) { + D2DX_FATAL_ERROR("Failed to detour Win32 functions."); + } +} + +_Use_decl_annotations_ +void d2dx::AttachLateDetours( + IGameHelper* gameHelper, + ID2DXContext* d2dxContext) +{ + if (hasLateDetoured) + { + return; + } + + hasLateDetoured = true; + + D2Gfx_DrawImage_Real = (D2Gfx_DrawImageFunc)gameHelper->GetFunction(D2Function::D2Gfx_DrawImage); + D2Gfx_DrawShiftedImage_Real = (D2Gfx_DrawShiftedImageFunc)gameHelper->GetFunction(D2Function::D2Gfx_DrawShiftedImage); + D2Gfx_DrawVerticalCropImage_Real = (D2Gfx_DrawVerticalCropImageFunc)gameHelper->GetFunction(D2Function::D2Gfx_DrawVerticalCropImage); + D2Gfx_DrawClippedImage_Real = (D2Gfx_DrawClippedImageFunc)gameHelper->GetFunction(D2Function::D2Gfx_DrawClippedImage); + D2Gfx_DrawImageFast_Real = (D2Gfx_DrawImageFastFunc)gameHelper->GetFunction(D2Function::D2Gfx_DrawImageFast); + D2Gfx_DrawShadow_Real = (D2Gfx_DrawShadowFunc)gameHelper->GetFunction(D2Function::D2Gfx_DrawShadow); + D2Win_DrawText_Real = (D2Win_DrawTextFunc)gameHelper->GetFunction(D2Function::D2Win_DrawText); + //D2Win_DrawTextEx_Real = (D2Win_DrawTextExFunc)gameHelper->GetFunction(D2Function::D2Win_DrawTextEx); + D2Win_DrawFramedText_Real = (D2Win_DrawFramedTextFunc)gameHelper->GetFunction(D2Function::D2Win_DrawFramedText); + D2Win_DrawRectangledText_Real = (D2Win_DrawRectangledTextFunc)gameHelper->GetFunction(D2Function::D2Win_DrawRectangledText); + D2Client_DrawUnit_Real = (D2Client_DrawUnitFunc)gameHelper->GetFunction(D2Function::D2Client_DrawUnit); + D2Client_DrawMissile_Real = (D2Client_DrawUnitFunc)gameHelper->GetFunction(D2Function::D2Client_DrawMissile); + D2Client_DrawWeatherParticles_Real = (NakedFunc)gameHelper->GetFunction(D2Function::D2Client_DrawWeatherParticles); + + if (!D2Gfx_DrawImage_Real || + !D2Gfx_DrawShiftedImage_Real || + !D2Gfx_DrawVerticalCropImage_Real || + !D2Gfx_DrawClippedImage_Real || + !D2Gfx_DrawImageFast_Real || + !D2Gfx_DrawShadow_Real || + !D2Win_DrawText_Real || + !D2Client_DrawUnit_Real) + { + return; + } + + DetourTransactionBegin(); + DetourUpdateThread(GetCurrentThread()); + DetourAttach(&(PVOID&)Sleep_Real, Sleep_Hooked); + DetourAttach(&(PVOID&)SleepEx_Real, SleepEx_Hooked); + DetourAttach(&(PVOID&)D2Gfx_DrawImage_Real, D2Gfx_DrawImage_Hooked); + DetourAttach(&(PVOID&)D2Gfx_DrawShiftedImage_Real, D2Gfx_DrawShiftedImage_Hooked); + DetourAttach(&(PVOID&)D2Gfx_DrawVerticalCropImage_Real, D2Gfx_DrawVerticalCropImage_Hooked); + DetourAttach(&(PVOID&)D2Gfx_DrawClippedImage_Real, D2Gfx_DrawClippedImage_Hooked); + DetourAttach(&(PVOID&)D2Gfx_DrawImageFast_Real, D2Gfx_DrawImageFast_Hooked); + DetourAttach(&(PVOID&)D2Gfx_DrawShadow_Real, D2Gfx_DrawShadow_Hooked); + DetourAttach(&(PVOID&)D2Win_DrawText_Real, D2Win_DrawText_Hooked); + //DetourAttach(&(PVOID&)D2Win_DrawTextEx_Real, D2Win_DrawTextEx_Hooked); + + if (D2Win_DrawFramedText_Real) + { + DetourAttach(&(PVOID&)D2Win_DrawFramedText_Real, D2Win_DrawFramedText_Hooked); + } + + if (D2Win_DrawRectangledText_Real) + { + DetourAttach(&(PVOID&)D2Win_DrawRectangledText_Real, D2Win_DrawRectangledText_Hooked); + } + + if (d2dxContext->IsFeatureEnabled(Feature::UnitMotionPrediction)) + { + assert(D2Client_DrawUnit_Real); + DetourAttach(&(PVOID&)D2Client_DrawUnit_Real, + (gameHelper->GetVersion() == GameVersion::Lod109d || + gameHelper->GetVersion() == GameVersion::Lod110f || + gameHelper->GetVersion() == GameVersion::Lod114d) ? D2Client_DrawUnit_ESI_Hooked : D2Client_DrawUnit_Stack_Hooked); + + if (gameHelper->GetVersion() != GameVersion::Lod109d) + { + assert(D2Client_DrawMissile_Real); + DetourAttach(&(PVOID&)D2Client_DrawMissile_Real, D2Client_DrawMissile_ESI_Hooked); + } + } + + if (d2dxContext->IsFeatureEnabled(Feature::WeatherMotionPrediction)) + { + assert(D2Client_DrawWeatherParticles_Real); + DetourAttach(&(PVOID&)D2Client_DrawWeatherParticles_Real, + gameHelper->GetVersion() == GameVersion::Lod114d ? D2Client_DrawWeatherParticles114d_Hooked : D2Client_DrawWeatherParticles_Hooked); + } + + LONG lError = DetourTransactionCommit(); + + if (lError != NO_ERROR) { + D2DX_LOG("Failed to detour D2Gfx functions: %i.", lError); + D2DX_FATAL_ERROR("Failed to detour D2Gfx functions"); + } +} + +void d2dx::DetachLateDetours() +{ + if (!hasLateDetoured || hasDetachedLateDetours) + { + return; + } + + hasDetachedLateDetours = true; + + DetourTransactionBegin(); + DetourUpdateThread(GetCurrentThread()); + + DetourDetach(&(PVOID&)D2Gfx_DrawImage_Real, D2Gfx_DrawImage_Hooked); + DetourDetach(&(PVOID&)D2Gfx_DrawShiftedImage_Real, D2Gfx_DrawShiftedImage_Hooked); + DetourDetach(&(PVOID&)D2Gfx_DrawVerticalCropImage_Real, D2Gfx_DrawVerticalCropImage_Hooked); + DetourDetach(&(PVOID&)D2Gfx_DrawClippedImage_Real, D2Gfx_DrawClippedImage_Hooked); + DetourDetach(&(PVOID&)D2Gfx_DrawImageFast_Real, D2Gfx_DrawImageFast_Hooked); + DetourDetach(&(PVOID&)D2Gfx_DrawShadow_Real, D2Gfx_DrawShadow_Hooked); + DetourDetach(&(PVOID&)D2Win_DrawText_Real, D2Win_DrawText_Hooked); + DetourDetach(&(PVOID&)D2Win_DrawFramedText_Real, D2Win_DrawFramedText_Hooked); + DetourDetach(&(PVOID&)D2Win_DrawRectangledText_Real, D2Win_DrawRectangledText_Hooked); + DetourDetach(&(PVOID&)D2Client_DrawMissile_Real, D2Client_DrawMissile_ESI_Hooked); + DetourDetach(&(PVOID&)Sleep_Real, Sleep_Hooked); + DetourDetach(&(PVOID&)SleepEx_Real, SleepEx_Hooked); + + LONG lError = DetourTransactionCommit(); + + if (lError != NO_ERROR) { + /* An error here doesn't really matter. The process is going. */ + } +} + +void d2dx::DetachDetours() +{ + if (!hasDetoured || hasDetachedDetours) + { + return; + } + + hasDetachedDetours = true; + + DetourTransactionBegin(); + DetourUpdateThread(GetCurrentThread()); + DetourDetach(&(PVOID&)DrawTextA_Real, DrawTextA_Hooked); + DetourDetach(&(PVOID&)GetPixel_real, GetPixel_Hooked); + DetourDetach(&(PVOID&)SendMessageA_Real, SendMessageA_Hooked); + DetourDetach(&(PVOID&)ShowCursor_Real, ShowCursor_Hooked); + DetourDetach(&(PVOID&)SetCursorPos_Real, SetCursorPos_Hooked); + DetourDetach(&(PVOID&)SetWindowPos_Real, SetWindowPos_Hooked); + + LONG lError = DetourTransactionCommit(); + + if (lError != NO_ERROR) { + /* An error here doesn't really matter. The process is going. */ + } +} diff --git a/src/d2dx/D2DXDetours.h b/src/d2dx/Detours.h similarity index 81% rename from src/d2dx/D2DXDetours.h rename to src/d2dx/Detours.h index 9f7b8d2..c81fbb5 100644 --- a/src/d2dx/D2DXDetours.h +++ b/src/d2dx/Detours.h @@ -20,6 +20,16 @@ namespace d2dx { + struct IGameHelper; + struct ID2DXContext; + void AttachDetours(); + + void AttachLateDetours( + _In_ IGameHelper* gameHelper, + _In_ ID2DXContext* d2dxContext); + + void DetachLateDetours(); + void DetachDetours(); -} \ No newline at end of file +} diff --git a/src/d2dx/Display.hlsli b/src/d2dx/Display.hlsli new file mode 100644 index 0000000..b576219 --- /dev/null +++ b/src/d2dx/Display.hlsli @@ -0,0 +1,34 @@ +/* + This file is part of D2DX. + + Copyright (C) 2021 Bolrog + + D2DX is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + D2DX is distributed in the hope that it will be 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 D2DX. If not, see . +*/ + +struct DisplayVSInput +{ + int2 pos : POSITION; + int2 st : TEXCOORD0; + float4 color : COLOR; + uint2 misc : TEXCOORD1; +}; + +struct DisplayVSOutput +{ + noperspective float2 tc : TEXCOORD0; + nointerpolation float4 textureSize_invTextureSize : TEXCOORD1; +}; + +typedef DisplayVSOutput DisplayPSInput; diff --git a/src/d2dx/Simd.cpp b/src/d2dx/DisplayBilinearScalePS.hlsl similarity index 77% rename from src/d2dx/Simd.cpp rename to src/d2dx/DisplayBilinearScalePS.hlsl index 7d104bd..a8d6035 100644 --- a/src/d2dx/Simd.cpp +++ b/src/d2dx/DisplayBilinearScalePS.hlsl @@ -16,14 +16,13 @@ You should have received a copy of the GNU General Public License along with D2DX. If not, see . */ -#include "pch.h" -#include "Simd.h" -#include "SimdSse2.h" +#include "Constants.hlsli" +#include "Display.hlsli" -using namespace d2dx; -using namespace std; +Texture2D sceneTexture : register(t0); -shared_ptr Simd::Create() +float4 main( + in DisplayPSInput ps_in) : SV_TARGET { - return make_shared(); + return sceneTexture.Sample(BilinearSampler, ps_in.tc); } diff --git a/src/d2dx/DisplayCatmullRomScalePS.hlsl b/src/d2dx/DisplayCatmullRomScalePS.hlsl new file mode 100644 index 0000000..8fea029 --- /dev/null +++ b/src/d2dx/DisplayCatmullRomScalePS.hlsl @@ -0,0 +1,84 @@ +/* + This file is part of D2DX. + + Copyright (C) 2021 Bolrog + + D2DX is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + D2DX is distributed in the hope that it will be 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 D2DX. If not, see . +*/ +#include "Constants.hlsli" +#include "Display.hlsli" + +Texture2D sceneTexture : register(t0); + +// This filtering code under MIT license, taken from: https://gist.github.com/TheRealMJP/c83b8c0f46b63f3a88a5986f4fa982b1 + +// Samples a texture with Catmull-Rom filtering, using 9 texture fetches instead of 16. +// See http://vec3.ca/bicubic-filtering-in-fewer-taps/ for more details +float4 SampleTextureCatmullRom(in Texture2D tex, in SamplerState linearSampler, in float2 uv, in float4 texSize_invTexSize) +{ + // We're going to sample a a 4x4 grid of texels surrounding the target UV coordinate. We'll do this by rounding + // down the sample location to get the exact center of our "starting" texel. The starting texel will be at + // location [1, 1] in the grid, where [0, 0] is the top left corner. + float2 samplePos = uv * texSize_invTexSize.xy; + float2 texPos1 = floor(samplePos - 0.5f) + 0.5f; + + // Compute the fractional offset from our starting texel to our original sample location, which we'll + // feed into the Catmull-Rom spline function to get our filter weights. + float2 f = samplePos - texPos1; + + // Compute the Catmull-Rom weights using the fractional offset that we calculated earlier. + // These equations are pre-expanded based on our knowledge of where the texels will be located, + // which lets us avoid having to evaluate a piece-wise function. + float2 w0 = f * (-0.5f + f * (1.0f - 0.5f * f)); + float2 w1 = 1.0f + f * f * (-2.5f + 1.5f * f); + float2 w2 = f * (0.5f + f * (2.0f - 1.5f * f)); + float2 w3 = f * f * (-0.5f + 0.5f * f); + + // Work out weighting factors and sampling offsets that will let us use bilinear filtering to + // simultaneously evaluate the middle 2 samples from the 4x4 grid. + float2 w12 = w1 + w2; + float2 offset12 = w2 / (w1 + w2); + + // Compute the final UV coordinates we'll use for sampling the texture + float2 texPos0 = texPos1 - 1; + float2 texPos3 = texPos1 + 2; + float2 texPos12 = texPos1 + offset12; + + texPos0 *= texSize_invTexSize.zw; + texPos3 *= texSize_invTexSize.zw; + texPos12 *= texSize_invTexSize.zw; + + float3 result = + (tex.SampleLevel(linearSampler, float2(texPos0.x, texPos0.y), 0.0f).rgb * w0.x + + tex.SampleLevel(linearSampler, float2(texPos12.x, texPos0.y), 0.0f).rgb * w12.x + + tex.SampleLevel(linearSampler, float2(texPos3.x, texPos0.y), 0.0f).rgb * w3.x) * w0.y; + + result += + (tex.SampleLevel(linearSampler, float2(texPos0.x, texPos12.y), 0.0f).rgb * w0.x + + tex.SampleLevel(linearSampler, float2(texPos12.x, texPos12.y), 0.0f).rgb * w12.x + + tex.SampleLevel(linearSampler, float2(texPos3.x, texPos12.y), 0.0f).rgb * w3.x) * w12.y; + + result += + (tex.SampleLevel(linearSampler, float2(texPos0.x, texPos3.y), 0.0f).rgb * w0.x + + tex.SampleLevel(linearSampler, float2(texPos12.x, texPos3.y), 0.0f).rgb * w12.x + + tex.SampleLevel(linearSampler, float2(texPos3.x, texPos3.y), 0.0f).rgb * w3.x) * w3.y; + + return float4(result, 1); +} + +float4 main( + in DisplayPSInput ps_in) : SV_TARGET +{ + return SampleTextureCatmullRom(sceneTexture, BilinearSampler, ps_in.tc, ps_in.textureSize_invTextureSize); +} diff --git a/src/d2dx/GammaVS.hlsl b/src/d2dx/DisplayIntegerScalePS.hlsl similarity index 73% rename from src/d2dx/GammaVS.hlsl rename to src/d2dx/DisplayIntegerScalePS.hlsl index c931a6f..c9f1aa0 100644 --- a/src/d2dx/GammaVS.hlsl +++ b/src/d2dx/DisplayIntegerScalePS.hlsl @@ -17,16 +17,12 @@ along with D2DX. If not, see . */ #include "Constants.hlsli" +#include "Display.hlsli" -void main( - in float2 pos : POSITION, - in int2 tc : TEXCOORD, - in float4 color : COLOR, - out float2 o_tc : TEXCOORD0, - out float4 o_pos : SV_POSITION) +Texture2D sceneTexture : register(t0); + +float4 main( + in DisplayPSInput ps_in) : SV_TARGET { - float2 fpos = (2.0 * pos / screenSize) - 1.0; - - o_pos = float4(fpos.x, -fpos.y, 0.0, 1.0); - o_tc = tc; + return sceneTexture.SampleLevel(PointSampler, ps_in.tc, 0); } diff --git a/src/d2dx/DisplayNonintegerScalePS.hlsl b/src/d2dx/DisplayNonintegerScalePS.hlsl new file mode 100644 index 0000000..c543c11 --- /dev/null +++ b/src/d2dx/DisplayNonintegerScalePS.hlsl @@ -0,0 +1,40 @@ +/* + This file is part of D2DX. + + Copyright (C) 2021 Bolrog + + D2DX is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + D2DX is distributed in the hope that it will be 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 D2DX. If not, see . +*/ +#include "Constants.hlsli" +#include "Display.hlsli" + +Texture2D sceneTexture : register(t0); + +/* Anti-aliased nearest-neighbor sampling, taken from https://www.shadertoy.com/view/WtjyWy by user Amarcoli. */ +float2 nearestSampleUV_AA(float2 uv, float sharpness, float4 textureSize_invTextureSize) { + float2 tileUv = uv * textureSize_invTextureSize.xy; + float2 dxdy = float2(ddx(tileUv.x), ddy(tileUv.y)); + + float2 texelDelta = frac(tileUv) - 0.5; + float2 distFromEdge = 0.5 - abs(texelDelta); + float2 aa_factor = saturate(distFromEdge * sharpness / dxdy); + + return uv - texelDelta * aa_factor * textureSize_invTextureSize.zw; +} + +float4 main( + in DisplayPSInput ps_in) : SV_TARGET +{ + return sceneTexture.Sample(BilinearSampler, nearestSampleUV_AA(ps_in.tc, 2.0, ps_in.textureSize_invTextureSize)); +} diff --git a/src/d2dx/DisplayVS.hlsl b/src/d2dx/DisplayVS.hlsl new file mode 100644 index 0000000..9a6b746 --- /dev/null +++ b/src/d2dx/DisplayVS.hlsl @@ -0,0 +1,50 @@ +/* + This file is part of D2DX. + + Copyright (C) 2021 Bolrog + + D2DX is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + D2DX is distributed in the hope that it will be 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 D2DX. If not, see . +*/ +#include "Constants.hlsli" +#include "Display.hlsli" + +void main( + in DisplayVSInput vs_in, + uint vs_in_vertexId : SV_VertexID, + out DisplayVSOutput vs_out, + out noperspective float4 vs_out_pos : SV_POSITION) +{ + float2 fpos = float2(vs_in.pos) * c_invScreenSize - 0.5; + vs_out_pos = fpos.xyxx * float4(2, -2, 0, 0) + float4(0,0,0,1); + + float2 stf = vs_in.st; + vs_out.textureSize_invTextureSize = float4(stf, 1/stf); + + const float srcWidth = vs_in.misc.y & 16383; + const float srcHeight = vs_in.misc.x & 4095; + + switch (vs_in_vertexId) + { + default: + case 0: + vs_out.tc = float2(0, 0); + break; + case 1: + vs_out.tc = float2(srcWidth * 2 * vs_out.textureSize_invTextureSize.z, 0); + break; + case 2: + vs_out.tc = float2(0, srcHeight * 2 * vs_out.textureSize_invTextureSize.w); + break; + } +} diff --git a/src/d2dx/ErrorHandling.h b/src/d2dx/ErrorHandling.h new file mode 100644 index 0000000..f92a1f4 --- /dev/null +++ b/src/d2dx/ErrorHandling.h @@ -0,0 +1,50 @@ +/* + This file is part of D2DX. + + Copyright (C) 2021 Bolrog + + D2DX is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + D2DX is distributed in the hope that it will be 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 D2DX. If not, see . +*/ +#pragma once + +namespace d2dx +{ + namespace detail + { + __declspec(noinline) void ThrowFromHRESULT(HRESULT hr, _In_z_ const char* func, int32_t line); + __declspec(noinline) char* GetMessageForHRESULT(HRESULT hr, _In_z_ const char* func, int32_t line) noexcept; + [[noreturn]] __declspec(noinline) void FatalException() noexcept; + [[noreturn]] __declspec(noinline) void FatalError(_In_z_ const char* msg) noexcept; + } + + struct ComException final : public std::runtime_error + { + ComException(HRESULT hr_, const char* func_, int32_t line_) : + std::runtime_error(detail::GetMessageForHRESULT(hr, func_, line_)), + hr{ hr_ }, + func{ func_ }, + line{ line_ } + { + } + + HRESULT hr; + const char* func; + int32_t line; + }; + +#define D2DX_THROW_HR(hr) detail::ThrowFromHRESULT(hr, __FUNCTION__, __LINE__) +#define D2DX_CHECK_HR(expr) { HRESULT hr = (expr); if (FAILED(hr)) { detail::ThrowFromHRESULT((hr), __FUNCTION__, __LINE__); } } +#define D2DX_FATAL_EXCEPTION detail::FatalException() +#define D2DX_FATAL_ERROR(msg) detail::FatalError(msg) +} diff --git a/src/d2dx/FXAA.hlsli b/src/d2dx/FXAA.hlsli new file mode 100644 index 0000000..d8a1aef --- /dev/null +++ b/src/d2dx/FXAA.hlsli @@ -0,0 +1,1003 @@ +/*============================================================================ + + + NVIDIA FXAA 3.11 by TIMOTHY LOTTES + + +------------------------------------------------------------------------------ +COPYRIGHT (C) 2010, 2011 NVIDIA CORPORATION. ALL RIGHTS RESERVED. +------------------------------------------------------------------------------ +TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, THIS SOFTWARE IS PROVIDED +*AS IS* AND NVIDIA AND ITS SUPPLIERS DISCLAIM ALL WARRANTIES, EITHER EXPRESS +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT SHALL NVIDIA +OR ITS SUPPLIERS BE LIABLE FOR ANY SPECIAL, INCIDENTAL, INDIRECT, OR +CONSEQUENTIAL DAMAGES WHATSOEVER (INCLUDING, WITHOUT LIMITATION, DAMAGES FOR +LOSS OF BUSINESS PROFITS, BUSINESS INTERRUPTION, LOSS OF BUSINESS INFORMATION, +OR ANY OTHER PECUNIARY LOSS) ARISING OUT OF THE USE OF OR INABILITY TO USE +THIS SOFTWARE, EVEN IF NVIDIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + +------------------------------------------------------------------------------ + INTEGRATION CHECKLIST +------------------------------------------------------------------------------ +(1.) +In the shader source, setup defines for the desired configuration. +When providing multiple shaders (for different presets), +simply setup the defines differently in multiple files. +Example, + + #define FXAA_PC 1 + #define FXAA_HLSL_5 1 + #define FXAA_QUALITY__PRESET 12 + +Or, + + #define FXAA_360 1 + +Or, + + #define FXAA_PS3 1 + +Etc. + +(2.) +Then include this file, + + #include "Fxaa3_11.h" + +(3.) +Then call the FXAA pixel shader from within your desired shader. +Look at the FXAA Quality FxaaPixelShader() for docs on inputs. +As for FXAA 3.11 all inputs for all shaders are the same +to enable easy porting between platforms. + + return FxaaPixelShader(...); + +(4.) +Insure pass prior to FXAA outputs RGBL (see next section). +Or use, + + #define FXAA_GREEN_AS_LUMA 1 + +(5.) +Setup engine to provide the following constants +which are used in the FxaaPixelShader() inputs, + + FxaaFloat2 fxaaQualityRcpFrame, + FxaaFloat4 fxaaConsoleRcpFrameOpt, + FxaaFloat4 fxaaConsoleRcpFrameOpt2, + FxaaFloat4 fxaaConsole360RcpFrameOpt2, + FxaaFloat fxaaQualitySubpix, + FxaaFloat fxaaQualityEdgeThreshold, + FxaaFloat fxaaQualityEdgeThresholdMin, + FxaaFloat fxaaConsoleEdgeSharpness, + FxaaFloat fxaaConsoleEdgeThreshold, + FxaaFloat fxaaConsoleEdgeThresholdMin, + FxaaFloat4 fxaaConsole360ConstDir + +Look at the FXAA Quality FxaaPixelShader() for docs on inputs. + +(6.) +Have FXAA vertex shader run as a full screen triangle, +and output "pos" and "fxaaConsolePosPos" +such that inputs in the pixel shader provide, + + // {xy} = center of pixel + FxaaFloat2 pos, + + // {xy__} = upper left of pixel + // {__zw} = lower right of pixel + FxaaFloat4 fxaaConsolePosPos, + +(7.) +Insure the texture sampler(s) used by FXAA are set to bilinear filtering. + + +------------------------------------------------------------------------------ + INTEGRATION - RGBL AND COLORSPACE +------------------------------------------------------------------------------ +FXAA3 requires RGBL as input unless the following is set, + + #define FXAA_GREEN_AS_LUMA 1 + +In which case the engine uses green in place of luma, +and requires RGB input is in a non-linear colorspace. + +RGB should be LDR (low dynamic range). +Specifically do FXAA after tonemapping. + +RGB data as returned by a texture fetch can be non-linear, +or linear when FXAA_GREEN_AS_LUMA is not set. +Note an "sRGB format" texture counts as linear, +because the result of a texture fetch is linear data. +Regular "RGBA8" textures in the sRGB colorspace are non-linear. + +If FXAA_GREEN_AS_LUMA is not set, +luma must be stored in the alpha channel prior to running FXAA. +This luma should be in a perceptual space (could be gamma 2.0). +Example pass before FXAA where output is gamma 2.0 encoded, + + color.rgb = ToneMap(color.rgb); // linear color output + color.rgb = sqrt(color.rgb); // gamma 2.0 color output + return color; + +To use FXAA, + + color.rgb = ToneMap(color.rgb); // linear color output + color.rgb = sqrt(color.rgb); // gamma 2.0 color output + color.a = dot(color.rgb, FxaaFloat3(0.299, 0.587, 0.114)); // compute luma + return color; + +Another example where output is linear encoded, +say for instance writing to an sRGB formated render target, +where the render target does the conversion back to sRGB after blending, + + color.rgb = ToneMap(color.rgb); // linear color output + return color; + +To use FXAA, + + color.rgb = ToneMap(color.rgb); // linear color output + color.a = sqrt(dot(color.rgb, FxaaFloat3(0.299, 0.587, 0.114))); // compute luma + return color; + +Getting luma correct is required for the algorithm to work correctly. + + +------------------------------------------------------------------------------ + BEING LINEARLY CORRECT? +------------------------------------------------------------------------------ +Applying FXAA to a framebuffer with linear RGB color will look worse. +This is very counter intuitive, but happends to be true in this case. +The reason is because dithering artifacts will be more visiable +in a linear colorspace. + + +------------------------------------------------------------------------------ + COMPLEX INTEGRATION +------------------------------------------------------------------------------ +Q. What if the engine is blending into RGB before wanting to run FXAA? + +A. In the last opaque pass prior to FXAA, + have the pass write out luma into alpha. + Then blend into RGB only. + FXAA should be able to run ok + assuming the blending pass did not any add aliasing. + This should be the common case for particles and common blending passes. + +A. Or use FXAA_GREEN_AS_LUMA. + +============================================================================*/ + +/*============================================================================ + + INTEGRATION KNOBS + +============================================================================*/ +// +// FXAA_PS3 and FXAA_360 choose the console algorithm (FXAA3 CONSOLE). +// FXAA_360_OPT is a prototype for the new optimized 360 version. +// +// 1 = Use API. +// 0 = Don't use API. +// +/*--------------------------------------------------------------------------*/ +#ifndef FXAA_PS3 +#define FXAA_PS3 0 +#endif +/*--------------------------------------------------------------------------*/ +#ifndef FXAA_360 +#define FXAA_360 0 +#endif +/*--------------------------------------------------------------------------*/ +#ifndef FXAA_360_OPT +#define FXAA_360_OPT 0 +#endif +/*==========================================================================*/ +#ifndef FXAA_PC + // + // FXAA Quality + // The high quality PC algorithm. + // +#define FXAA_PC 0 +#endif +/*--------------------------------------------------------------------------*/ +#ifndef FXAA_PC_CONSOLE + // + // The console algorithm for PC is included + // for developers targeting really low spec machines. + // Likely better to just run FXAA_PC, and use a really low preset. + // +#define FXAA_PC_CONSOLE 0 +#endif +/*--------------------------------------------------------------------------*/ +#ifndef FXAA_GLSL_120 +#define FXAA_GLSL_120 0 +#endif +/*--------------------------------------------------------------------------*/ +#ifndef FXAA_GLSL_130 +#define FXAA_GLSL_130 0 +#endif +/*--------------------------------------------------------------------------*/ +#ifndef FXAA_HLSL_3 +#define FXAA_HLSL_3 0 +#endif +/*--------------------------------------------------------------------------*/ +#ifndef FXAA_HLSL_4 +#define FXAA_HLSL_4 0 +#endif +/*--------------------------------------------------------------------------*/ +#ifndef FXAA_HLSL_5 +#define FXAA_HLSL_5 0 +#endif +/*==========================================================================*/ +#ifndef FXAA_GREEN_AS_LUMA + // + // For those using non-linear color, + // and either not able to get luma in alpha, or not wanting to, + // this enables FXAA to run using green as a proxy for luma. + // So with this enabled, no need to pack luma in alpha. + // + // This will turn off AA on anything which lacks some amount of green. + // Pure red and blue or combination of only R and B, will get no AA. + // + // Might want to lower the settings for both, + // fxaaConsoleEdgeThresholdMin + // fxaaQualityEdgeThresholdMin + // In order to insure AA does not get turned off on colors + // which contain a minor amount of green. + // + // 1 = On. + // 0 = Off. + // +#define FXAA_GREEN_AS_LUMA 0 +#endif +/*--------------------------------------------------------------------------*/ +#ifndef FXAA_EARLY_EXIT + // + // Controls algorithm's early exit path. + // On PS3 turning this ON adds 2 cycles to the shader. + // On 360 turning this OFF adds 10ths of a millisecond to the shader. + // Turning this off on console will result in a more blurry image. + // So this defaults to on. + // + // 1 = On. + // 0 = Off. + // +#define FXAA_EARLY_EXIT 1 +#endif +/*--------------------------------------------------------------------------*/ +#ifndef FXAA_DISCARD + // + // Only valid for PC OpenGL currently. + // Probably will not work when FXAA_GREEN_AS_LUMA = 1. + // + // 1 = Use discard on pixels which don't need AA. + // For APIs which enable concurrent TEX+ROP from same surface. + // 0 = Return unchanged color on pixels which don't need AA. + // +#define FXAA_DISCARD 0 +#endif +/*--------------------------------------------------------------------------*/ +#ifndef FXAA_GATHER4_ALPHA + // + // 1 = API supports gather4 on alpha channel. + // 0 = API does not support gather4 on alpha channel. + // +#if (FXAA_HLSL_5 == 1) +#define FXAA_GATHER4_ALPHA 1 +#endif +#ifdef GL_ARB_gpu_shader5 +#define FXAA_GATHER4_ALPHA 1 +#endif +#ifdef GL_NV_gpu_shader5 +#define FXAA_GATHER4_ALPHA 1 +#endif +#ifndef FXAA_GATHER4_ALPHA +#define FXAA_GATHER4_ALPHA 0 +#endif +#endif + +/*============================================================================ + FXAA QUALITY - TUNING KNOBS +------------------------------------------------------------------------------ +NOTE the other tuning knobs are now in the shader function inputs! +============================================================================*/ +#ifndef FXAA_QUALITY__PRESET +// +// Choose the quality preset. +// This needs to be compiled into the shader as it effects code. +// Best option to include multiple presets is to +// in each shader define the preset, then include this file. +// +// OPTIONS +// ----------------------------------------------------------------------- +// 10 to 15 - default medium dither (10=fastest, 15=highest quality) +// 20 to 29 - less dither, more expensive (20=fastest, 29=highest quality) +// 39 - no dither, very expensive +// +// NOTES +// ----------------------------------------------------------------------- +// 12 = slightly faster then FXAA 3.9 and higher edge quality (default) +// 13 = about same speed as FXAA 3.9 and better than 12 +// 23 = closest to FXAA 3.9 visually and performance wise +// _ = the lowest digit is directly related to performance +// _ = the highest digit is directly related to style +// +#define FXAA_QUALITY__PRESET 12 +#endif + + +/*============================================================================ + + FXAA QUALITY - PRESETS + +============================================================================*/ + +/*============================================================================ + FXAA QUALITY - MEDIUM DITHER PRESETS +============================================================================*/ +#if (FXAA_QUALITY__PRESET == 10) +#define FXAA_QUALITY__PS 3 +#define FXAA_QUALITY__P0 1.5 +#define FXAA_QUALITY__P1 3.0 +#define FXAA_QUALITY__P2 12.0 +#endif +/*--------------------------------------------------------------------------*/ +#if (FXAA_QUALITY__PRESET == 11) +#define FXAA_QUALITY__PS 4 +#define FXAA_QUALITY__P0 1.0 +#define FXAA_QUALITY__P1 1.5 +#define FXAA_QUALITY__P2 3.0 +#define FXAA_QUALITY__P3 12.0 +#endif +/*--------------------------------------------------------------------------*/ +#if (FXAA_QUALITY__PRESET == 12) +#define FXAA_QUALITY__PS 5 +#define FXAA_QUALITY__P0 1.0 +#define FXAA_QUALITY__P1 1.5 +#define FXAA_QUALITY__P2 2.0 +#define FXAA_QUALITY__P3 4.0 +#define FXAA_QUALITY__P4 12.0 +#endif +/*--------------------------------------------------------------------------*/ +#if (FXAA_QUALITY__PRESET == 13) +#define FXAA_QUALITY__PS 6 +#define FXAA_QUALITY__P0 1.0 +#define FXAA_QUALITY__P1 1.5 +#define FXAA_QUALITY__P2 2.0 +#define FXAA_QUALITY__P3 2.0 +#define FXAA_QUALITY__P4 4.0 +#define FXAA_QUALITY__P5 12.0 +#endif +/*--------------------------------------------------------------------------*/ +#if (FXAA_QUALITY__PRESET == 14) +#define FXAA_QUALITY__PS 7 +#define FXAA_QUALITY__P0 1.0 +#define FXAA_QUALITY__P1 1.5 +#define FXAA_QUALITY__P2 2.0 +#define FXAA_QUALITY__P3 2.0 +#define FXAA_QUALITY__P4 2.0 +#define FXAA_QUALITY__P5 4.0 +#define FXAA_QUALITY__P6 12.0 +#endif +/*--------------------------------------------------------------------------*/ +#if (FXAA_QUALITY__PRESET == 15) +#define FXAA_QUALITY__PS 8 +#define FXAA_QUALITY__P0 1.0 +#define FXAA_QUALITY__P1 1.5 +#define FXAA_QUALITY__P2 2.0 +#define FXAA_QUALITY__P3 2.0 +#define FXAA_QUALITY__P4 2.0 +#define FXAA_QUALITY__P5 2.0 +#define FXAA_QUALITY__P6 4.0 +#define FXAA_QUALITY__P7 12.0 +#endif + +/*============================================================================ + FXAA QUALITY - LOW DITHER PRESETS +============================================================================*/ +#if (FXAA_QUALITY__PRESET == 20) +#define FXAA_QUALITY__PS 3 +#define FXAA_QUALITY__P0 1.5 +#define FXAA_QUALITY__P1 2.0 +#define FXAA_QUALITY__P2 8.0 +#endif +/*--------------------------------------------------------------------------*/ +#if (FXAA_QUALITY__PRESET == 21) +#define FXAA_QUALITY__PS 4 +#define FXAA_QUALITY__P0 1.0 +#define FXAA_QUALITY__P1 1.5 +#define FXAA_QUALITY__P2 2.0 +#define FXAA_QUALITY__P3 8.0 +#endif +/*--------------------------------------------------------------------------*/ +#if (FXAA_QUALITY__PRESET == 22) +#define FXAA_QUALITY__PS 5 +#define FXAA_QUALITY__P0 1.0 +#define FXAA_QUALITY__P1 1.5 +#define FXAA_QUALITY__P2 2.0 +#define FXAA_QUALITY__P3 2.0 +#define FXAA_QUALITY__P4 8.0 +#endif +/*--------------------------------------------------------------------------*/ +#if (FXAA_QUALITY__PRESET == 23) +#define FXAA_QUALITY__PS 6 +#define FXAA_QUALITY__P0 1.0 +#define FXAA_QUALITY__P1 1.5 +#define FXAA_QUALITY__P2 2.0 +#define FXAA_QUALITY__P3 2.0 +#define FXAA_QUALITY__P4 2.0 +#define FXAA_QUALITY__P5 8.0 +#endif +/*--------------------------------------------------------------------------*/ +#if (FXAA_QUALITY__PRESET == 24) +#define FXAA_QUALITY__PS 7 +#define FXAA_QUALITY__P0 1.0 +#define FXAA_QUALITY__P1 1.5 +#define FXAA_QUALITY__P2 2.0 +#define FXAA_QUALITY__P3 2.0 +#define FXAA_QUALITY__P4 2.0 +#define FXAA_QUALITY__P5 3.0 +#define FXAA_QUALITY__P6 8.0 +#endif +/*--------------------------------------------------------------------------*/ +#if (FXAA_QUALITY__PRESET == 25) +#define FXAA_QUALITY__PS 8 +#define FXAA_QUALITY__P0 1.0 +#define FXAA_QUALITY__P1 1.5 +#define FXAA_QUALITY__P2 2.0 +#define FXAA_QUALITY__P3 2.0 +#define FXAA_QUALITY__P4 2.0 +#define FXAA_QUALITY__P5 2.0 +#define FXAA_QUALITY__P6 4.0 +#define FXAA_QUALITY__P7 8.0 +#endif +/*--------------------------------------------------------------------------*/ +#if (FXAA_QUALITY__PRESET == 26) +#define FXAA_QUALITY__PS 9 +#define FXAA_QUALITY__P0 1.0 +#define FXAA_QUALITY__P1 1.5 +#define FXAA_QUALITY__P2 2.0 +#define FXAA_QUALITY__P3 2.0 +#define FXAA_QUALITY__P4 2.0 +#define FXAA_QUALITY__P5 2.0 +#define FXAA_QUALITY__P6 2.0 +#define FXAA_QUALITY__P7 4.0 +#define FXAA_QUALITY__P8 8.0 +#endif +/*--------------------------------------------------------------------------*/ +#if (FXAA_QUALITY__PRESET == 27) +#define FXAA_QUALITY__PS 10 +#define FXAA_QUALITY__P0 1.0 +#define FXAA_QUALITY__P1 1.5 +#define FXAA_QUALITY__P2 2.0 +#define FXAA_QUALITY__P3 2.0 +#define FXAA_QUALITY__P4 2.0 +#define FXAA_QUALITY__P5 2.0 +#define FXAA_QUALITY__P6 2.0 +#define FXAA_QUALITY__P7 2.0 +#define FXAA_QUALITY__P8 4.0 +#define FXAA_QUALITY__P9 8.0 +#endif +/*--------------------------------------------------------------------------*/ +#if (FXAA_QUALITY__PRESET == 28) +#define FXAA_QUALITY__PS 11 +#define FXAA_QUALITY__P0 1.0 +#define FXAA_QUALITY__P1 1.5 +#define FXAA_QUALITY__P2 2.0 +#define FXAA_QUALITY__P3 2.0 +#define FXAA_QUALITY__P4 2.0 +#define FXAA_QUALITY__P5 2.0 +#define FXAA_QUALITY__P6 2.0 +#define FXAA_QUALITY__P7 2.0 +#define FXAA_QUALITY__P8 2.0 +#define FXAA_QUALITY__P9 4.0 +#define FXAA_QUALITY__P10 8.0 +#endif +/*--------------------------------------------------------------------------*/ +#if (FXAA_QUALITY__PRESET == 29) +#define FXAA_QUALITY__PS 12 +#define FXAA_QUALITY__P0 1.0 +#define FXAA_QUALITY__P1 1.5 +#define FXAA_QUALITY__P2 2.0 +#define FXAA_QUALITY__P3 2.0 +#define FXAA_QUALITY__P4 2.0 +#define FXAA_QUALITY__P5 2.0 +#define FXAA_QUALITY__P6 2.0 +#define FXAA_QUALITY__P7 2.0 +#define FXAA_QUALITY__P8 2.0 +#define FXAA_QUALITY__P9 2.0 +#define FXAA_QUALITY__P10 4.0 +#define FXAA_QUALITY__P11 8.0 +#endif + +/*============================================================================ + FXAA QUALITY - EXTREME QUALITY +============================================================================*/ +#if (FXAA_QUALITY__PRESET == 39) +#define FXAA_QUALITY__PS 12 +#define FXAA_QUALITY__P0 1.0 +#define FXAA_QUALITY__P1 1.0 +#define FXAA_QUALITY__P2 1.0 +#define FXAA_QUALITY__P3 1.0 +#define FXAA_QUALITY__P4 1.0 +#define FXAA_QUALITY__P5 1.5 +#define FXAA_QUALITY__P6 2.0 +#define FXAA_QUALITY__P7 2.0 +#define FXAA_QUALITY__P8 2.0 +#define FXAA_QUALITY__P9 2.0 +#define FXAA_QUALITY__P10 4.0 +#define FXAA_QUALITY__P11 8.0 +#endif + + + +/*============================================================================ + + API PORTING + +============================================================================*/ +#define FxaaBool bool +#define FxaaDiscard clip(-1) +#define FxaaFloat float +#define FxaaFloat2 float2 +#define FxaaFloat3 float3 +#define FxaaFloat4 float4 +#define FxaaHalf half +#define FxaaHalf2 half2 +#define FxaaHalf3 half3 +#define FxaaHalf4 half4 +#define FxaaSat(x) saturate(x) +/*--------------------------------------------------------------------------*/ +#if (FXAA_HLSL_4 == 1) +#define FxaaInt2 int2 +struct FxaaTex { SamplerState smpl; Texture2D tex; }; +#define FxaaTexTop(t, p) t.tex.SampleLevel(t.smpl, p, 0.0) +#define FxaaTexOff(t, p, o) t.tex.SampleLevel(t.smpl, p, 0.0, o) +#endif +/*--------------------------------------------------------------------------*/ +#if (FXAA_HLSL_5 == 1) +#define FxaaInt2 int2 +struct FxaaTex { SamplerState smpl; Texture2D tex; }; +#define FxaaTexTop(t, p) t.tex.SampleLevel(t.smpl, p, 0.0) +#define FxaaTexOff(t, p, o) t.tex.SampleLevel(t.smpl, p, 0.0, o) +#define FxaaTexAlpha4(t, p) t.tex.GatherAlpha(t.smpl, p) +#define FxaaTexOffAlpha4(t, p, o) t.tex.GatherAlpha(t.smpl, p, o) +#endif + + +/*============================================================================ + GREEN AS LUMA OPTION SUPPORT FUNCTION +============================================================================*/ +#if (FXAA_GREEN_AS_LUMA == 0) +FxaaFloat FxaaLuma(FxaaFloat4 rgba) { return rgba.w; } +#else +FxaaFloat FxaaLuma(FxaaFloat4 rgba) { return rgba.y; } +#endif + +/*============================================================================ + + FXAA3 QUALITY - PC + +============================================================================*/ +/*--------------------------------------------------------------------------*/ +FxaaFloat4 FxaaPixelShader( + FxaaFloat4 rgbyM, + // + // Use noperspective interpolation here (turn off perspective interpolation). + // {xy} = center of pixel + FxaaFloat2 pos, + // + // Input color texture. + // {rgb_} = color in linear or perceptual color space + // if (FXAA_GREEN_AS_LUMA == 0) + // {___a} = luma in perceptual color space (not linear) + FxaaTex tex, + // + // Only used on FXAA Quality. + // This must be from a constant/uniform. + // {x_} = 1.0/screenWidthInPixels + // {_y} = 1.0/screenHeightInPixels + FxaaFloat2 fxaaQualityRcpFrame, + // + // Only used on FXAA Quality. + // This used to be the FXAA_QUALITY__SUBPIX define. + // It is here now to allow easier tuning. + // Choose the amount of sub-pixel aliasing removal. + // This can effect sharpness. + // 1.00 - upper limit (softer) + // 0.75 - default amount of filtering + // 0.50 - lower limit (sharper, less sub-pixel aliasing removal) + // 0.25 - almost off + // 0.00 - completely off + FxaaFloat fxaaQualitySubpix, + // + // Only used on FXAA Quality. + // This used to be the FXAA_QUALITY__EDGE_THRESHOLD define. + // It is here now to allow easier tuning. + // The minimum amount of local contrast required to apply algorithm. + // 0.333 - too little (faster) + // 0.250 - low quality + // 0.166 - default + // 0.125 - high quality + // 0.063 - overkill (slower) + FxaaFloat fxaaQualityEdgeThreshold, + // + // Only used on FXAA Quality. + // This used to be the FXAA_QUALITY__EDGE_THRESHOLD_MIN define. + // It is here now to allow easier tuning. + // Trims the algorithm from processing darks. + // 0.0833 - upper limit (default, the start of visible unfiltered edges) + // 0.0625 - high quality (faster) + // 0.0312 - visible limit (slower) + // Special notes when using FXAA_GREEN_AS_LUMA, + // Likely want to set this to zero. + // As colors that are mostly not-green + // will appear very dark in the green channel! + // Tune by looking at mostly non-green content, + // then start at zero and increase until aliasing is a problem. + FxaaFloat fxaaQualityEdgeThresholdMin +) { + /*--------------------------------------------------------------------------*/ + FxaaFloat2 posM; + posM.x = pos.x; + posM.y = pos.y; +#if (FXAA_GATHER4_ALPHA == 1) +#if (FXAA_DISCARD == 0) + //FxaaFloat4 rgbyM = FxaaTexTop(tex, posM); +#if (FXAA_GREEN_AS_LUMA == 0) +#define lumaM rgbyM.w +#else +#define lumaM rgbyM.y +#endif +#endif +#if (FXAA_GREEN_AS_LUMA == 0) + FxaaFloat4 luma4A = FxaaTexAlpha4(tex, posM); + FxaaFloat4 luma4B = FxaaTexOffAlpha4(tex, posM, FxaaInt2(-1, -1)); +#else + FxaaFloat4 luma4A = FxaaTexGreen4(tex, posM); + FxaaFloat4 luma4B = FxaaTexOffGreen4(tex, posM, FxaaInt2(-1, -1)); +#endif +#if (FXAA_DISCARD == 1) +#define lumaM luma4A.w +#endif +#define lumaE luma4A.z +#define lumaS luma4A.x +#define lumaSE luma4A.y +#define lumaNW luma4B.w +#define lumaN luma4B.z +#define lumaW luma4B.x +#else + //FxaaFloat4 rgbyM = FxaaTexTop(tex, posM); +#if (FXAA_GREEN_AS_LUMA == 0) +#define lumaM rgbyM.w +#else +#define lumaM rgbyM.y +#endif + FxaaFloat lumaS = FxaaLuma(FxaaTexOff(tex, posM, FxaaInt2(0, 1))); + FxaaFloat lumaE = FxaaLuma(FxaaTexOff(tex, posM, FxaaInt2(1, 0))); + FxaaFloat lumaN = FxaaLuma(FxaaTexOff(tex, posM, FxaaInt2(0, -1))); + FxaaFloat lumaW = FxaaLuma(FxaaTexOff(tex, posM, FxaaInt2(-1, 0))); +#endif + /*--------------------------------------------------------------------------*/ + FxaaFloat maxSM = max(lumaS, lumaM); + FxaaFloat minSM = min(lumaS, lumaM); + FxaaFloat maxESM = max(lumaE, maxSM); + FxaaFloat minESM = min(lumaE, minSM); + FxaaFloat maxWN = max(lumaN, lumaW); + FxaaFloat minWN = min(lumaN, lumaW); + FxaaFloat rangeMax = max(maxWN, maxESM); + FxaaFloat rangeMin = min(minWN, minESM); + FxaaFloat rangeMaxScaled = rangeMax * fxaaQualityEdgeThreshold; + FxaaFloat range = rangeMax - rangeMin; + FxaaFloat rangeMaxClamped = max(fxaaQualityEdgeThresholdMin, rangeMaxScaled); + FxaaBool earlyExit = range < rangeMaxClamped; + /*--------------------------------------------------------------------------*/ + if (earlyExit) +#if (FXAA_DISCARD == 1) + FxaaDiscard; +#else + return rgbyM; +#endif + /*--------------------------------------------------------------------------*/ +#if (FXAA_GATHER4_ALPHA == 0) + FxaaFloat lumaNW = FxaaLuma(FxaaTexOff(tex, posM, FxaaInt2(-1, -1))); + FxaaFloat lumaSE = FxaaLuma(FxaaTexOff(tex, posM, FxaaInt2(1, 1))); + FxaaFloat lumaNE = FxaaLuma(FxaaTexOff(tex, posM, FxaaInt2(1, -1))); + FxaaFloat lumaSW = FxaaLuma(FxaaTexOff(tex, posM, FxaaInt2(-1, 1))); +#else + FxaaFloat lumaNE = FxaaLuma(FxaaTexOff(tex, posM, FxaaInt2(1, -1))); + FxaaFloat lumaSW = FxaaLuma(FxaaTexOff(tex, posM, FxaaInt2(-1, 1))); +#endif + /*--------------------------------------------------------------------------*/ + FxaaFloat lumaNS = lumaN + lumaS; + FxaaFloat lumaWE = lumaW + lumaE; + FxaaFloat subpixRcpRange = 1.0 / range; + FxaaFloat subpixNSWE = lumaNS + lumaWE; + FxaaFloat edgeHorz1 = (-2.0 * lumaM) + lumaNS; + FxaaFloat edgeVert1 = (-2.0 * lumaM) + lumaWE; + /*--------------------------------------------------------------------------*/ + FxaaFloat lumaNESE = lumaNE + lumaSE; + FxaaFloat lumaNWNE = lumaNW + lumaNE; + FxaaFloat edgeHorz2 = (-2.0 * lumaE) + lumaNESE; + FxaaFloat edgeVert2 = (-2.0 * lumaN) + lumaNWNE; + /*--------------------------------------------------------------------------*/ + FxaaFloat lumaNWSW = lumaNW + lumaSW; + FxaaFloat lumaSWSE = lumaSW + lumaSE; + FxaaFloat edgeHorz4 = (abs(edgeHorz1) * 2.0) + abs(edgeHorz2); + FxaaFloat edgeVert4 = (abs(edgeVert1) * 2.0) + abs(edgeVert2); + FxaaFloat edgeHorz3 = (-2.0 * lumaW) + lumaNWSW; + FxaaFloat edgeVert3 = (-2.0 * lumaS) + lumaSWSE; + FxaaFloat edgeHorz = abs(edgeHorz3) + edgeHorz4; + FxaaFloat edgeVert = abs(edgeVert3) + edgeVert4; + /*--------------------------------------------------------------------------*/ + FxaaFloat subpixNWSWNESE = lumaNWSW + lumaNESE; + FxaaFloat lengthSign = fxaaQualityRcpFrame.x; + FxaaBool horzSpan = edgeHorz >= edgeVert; + FxaaFloat subpixA = subpixNSWE * 2.0 + subpixNWSWNESE; + /*--------------------------------------------------------------------------*/ + if (!horzSpan) lumaN = lumaW; + if (!horzSpan) lumaS = lumaE; + if (horzSpan) lengthSign = fxaaQualityRcpFrame.y; + FxaaFloat subpixB = (subpixA * (1.0 / 12.0)) - lumaM; + /*--------------------------------------------------------------------------*/ + FxaaFloat gradientN = lumaN - lumaM; + FxaaFloat gradientS = lumaS - lumaM; + FxaaFloat lumaNN = lumaN + lumaM; + FxaaFloat lumaSS = lumaS + lumaM; + FxaaBool pairN = abs(gradientN) >= abs(gradientS); + FxaaFloat gradient = max(abs(gradientN), abs(gradientS)); + if (pairN) lengthSign = -lengthSign; + FxaaFloat subpixC = FxaaSat(abs(subpixB) * subpixRcpRange); + /*--------------------------------------------------------------------------*/ + FxaaFloat2 posB; + posB.x = posM.x; + posB.y = posM.y; + FxaaFloat2 offNP; + offNP.x = (!horzSpan) ? 0.0 : fxaaQualityRcpFrame.x; + offNP.y = (horzSpan) ? 0.0 : fxaaQualityRcpFrame.y; + if (!horzSpan) posB.x += lengthSign * 0.5; + if (horzSpan) posB.y += lengthSign * 0.5; + /*--------------------------------------------------------------------------*/ + FxaaFloat2 posN; + posN.x = posB.x - offNP.x * FXAA_QUALITY__P0; + posN.y = posB.y - offNP.y * FXAA_QUALITY__P0; + FxaaFloat2 posP; + posP.x = posB.x + offNP.x * FXAA_QUALITY__P0; + posP.y = posB.y + offNP.y * FXAA_QUALITY__P0; + FxaaFloat subpixD = ((-2.0) * subpixC) + 3.0; + FxaaFloat lumaEndN = FxaaLuma(FxaaTexTop(tex, posN)); + FxaaFloat subpixE = subpixC * subpixC; + FxaaFloat lumaEndP = FxaaLuma(FxaaTexTop(tex, posP)); + /*--------------------------------------------------------------------------*/ + if (!pairN) lumaNN = lumaSS; + FxaaFloat gradientScaled = gradient * 1.0 / 4.0; + FxaaFloat lumaMM = lumaM - lumaNN * 0.5; + FxaaFloat subpixF = subpixD * subpixE; + FxaaBool lumaMLTZero = lumaMM < 0.0; + /*--------------------------------------------------------------------------*/ + lumaEndN -= lumaNN * 0.5; + lumaEndP -= lumaNN * 0.5; + FxaaBool doneN = abs(lumaEndN) >= gradientScaled; + FxaaBool doneP = abs(lumaEndP) >= gradientScaled; + if (!doneN) posN.x -= offNP.x * FXAA_QUALITY__P1; + if (!doneN) posN.y -= offNP.y * FXAA_QUALITY__P1; + FxaaBool doneNP = (!doneN) || (!doneP); + if (!doneP) posP.x += offNP.x * FXAA_QUALITY__P1; + if (!doneP) posP.y += offNP.y * FXAA_QUALITY__P1; + /*--------------------------------------------------------------------------*/ + if (doneNP) { + if (!doneN) lumaEndN = FxaaLuma(FxaaTexTop(tex, posN.xy)); + if (!doneP) lumaEndP = FxaaLuma(FxaaTexTop(tex, posP.xy)); + if (!doneN) lumaEndN = lumaEndN - lumaNN * 0.5; + if (!doneP) lumaEndP = lumaEndP - lumaNN * 0.5; + doneN = abs(lumaEndN) >= gradientScaled; + doneP = abs(lumaEndP) >= gradientScaled; + if (!doneN) posN.x -= offNP.x * FXAA_QUALITY__P2; + if (!doneN) posN.y -= offNP.y * FXAA_QUALITY__P2; + doneNP = (!doneN) || (!doneP); + if (!doneP) posP.x += offNP.x * FXAA_QUALITY__P2; + if (!doneP) posP.y += offNP.y * FXAA_QUALITY__P2; + /*--------------------------------------------------------------------------*/ +#if (FXAA_QUALITY__PS > 3) + if (doneNP) { + if (!doneN) lumaEndN = FxaaLuma(FxaaTexTop(tex, posN.xy)); + if (!doneP) lumaEndP = FxaaLuma(FxaaTexTop(tex, posP.xy)); + if (!doneN) lumaEndN = lumaEndN - lumaNN * 0.5; + if (!doneP) lumaEndP = lumaEndP - lumaNN * 0.5; + doneN = abs(lumaEndN) >= gradientScaled; + doneP = abs(lumaEndP) >= gradientScaled; + if (!doneN) posN.x -= offNP.x * FXAA_QUALITY__P3; + if (!doneN) posN.y -= offNP.y * FXAA_QUALITY__P3; + doneNP = (!doneN) || (!doneP); + if (!doneP) posP.x += offNP.x * FXAA_QUALITY__P3; + if (!doneP) posP.y += offNP.y * FXAA_QUALITY__P3; + /*--------------------------------------------------------------------------*/ +#if (FXAA_QUALITY__PS > 4) + if (doneNP) { + if (!doneN) lumaEndN = FxaaLuma(FxaaTexTop(tex, posN.xy)); + if (!doneP) lumaEndP = FxaaLuma(FxaaTexTop(tex, posP.xy)); + if (!doneN) lumaEndN = lumaEndN - lumaNN * 0.5; + if (!doneP) lumaEndP = lumaEndP - lumaNN * 0.5; + doneN = abs(lumaEndN) >= gradientScaled; + doneP = abs(lumaEndP) >= gradientScaled; + if (!doneN) posN.x -= offNP.x * FXAA_QUALITY__P4; + if (!doneN) posN.y -= offNP.y * FXAA_QUALITY__P4; + doneNP = (!doneN) || (!doneP); + if (!doneP) posP.x += offNP.x * FXAA_QUALITY__P4; + if (!doneP) posP.y += offNP.y * FXAA_QUALITY__P4; + /*--------------------------------------------------------------------------*/ +#if (FXAA_QUALITY__PS > 5) + if (doneNP) { + if (!doneN) lumaEndN = FxaaLuma(FxaaTexTop(tex, posN.xy)); + if (!doneP) lumaEndP = FxaaLuma(FxaaTexTop(tex, posP.xy)); + if (!doneN) lumaEndN = lumaEndN - lumaNN * 0.5; + if (!doneP) lumaEndP = lumaEndP - lumaNN * 0.5; + doneN = abs(lumaEndN) >= gradientScaled; + doneP = abs(lumaEndP) >= gradientScaled; + if (!doneN) posN.x -= offNP.x * FXAA_QUALITY__P5; + if (!doneN) posN.y -= offNP.y * FXAA_QUALITY__P5; + doneNP = (!doneN) || (!doneP); + if (!doneP) posP.x += offNP.x * FXAA_QUALITY__P5; + if (!doneP) posP.y += offNP.y * FXAA_QUALITY__P5; + /*--------------------------------------------------------------------------*/ +#if (FXAA_QUALITY__PS > 6) + if (doneNP) { + if (!doneN) lumaEndN = FxaaLuma(FxaaTexTop(tex, posN.xy)); + if (!doneP) lumaEndP = FxaaLuma(FxaaTexTop(tex, posP.xy)); + if (!doneN) lumaEndN = lumaEndN - lumaNN * 0.5; + if (!doneP) lumaEndP = lumaEndP - lumaNN * 0.5; + doneN = abs(lumaEndN) >= gradientScaled; + doneP = abs(lumaEndP) >= gradientScaled; + if (!doneN) posN.x -= offNP.x * FXAA_QUALITY__P6; + if (!doneN) posN.y -= offNP.y * FXAA_QUALITY__P6; + doneNP = (!doneN) || (!doneP); + if (!doneP) posP.x += offNP.x * FXAA_QUALITY__P6; + if (!doneP) posP.y += offNP.y * FXAA_QUALITY__P6; + /*--------------------------------------------------------------------------*/ +#if (FXAA_QUALITY__PS > 7) + if (doneNP) { + if (!doneN) lumaEndN = FxaaLuma(FxaaTexTop(tex, posN.xy)); + if (!doneP) lumaEndP = FxaaLuma(FxaaTexTop(tex, posP.xy)); + if (!doneN) lumaEndN = lumaEndN - lumaNN * 0.5; + if (!doneP) lumaEndP = lumaEndP - lumaNN * 0.5; + doneN = abs(lumaEndN) >= gradientScaled; + doneP = abs(lumaEndP) >= gradientScaled; + if (!doneN) posN.x -= offNP.x * FXAA_QUALITY__P7; + if (!doneN) posN.y -= offNP.y * FXAA_QUALITY__P7; + doneNP = (!doneN) || (!doneP); + if (!doneP) posP.x += offNP.x * FXAA_QUALITY__P7; + if (!doneP) posP.y += offNP.y * FXAA_QUALITY__P7; + /*--------------------------------------------------------------------------*/ +#if (FXAA_QUALITY__PS > 8) + if (doneNP) { + if (!doneN) lumaEndN = FxaaLuma(FxaaTexTop(tex, posN.xy)); + if (!doneP) lumaEndP = FxaaLuma(FxaaTexTop(tex, posP.xy)); + if (!doneN) lumaEndN = lumaEndN - lumaNN * 0.5; + if (!doneP) lumaEndP = lumaEndP - lumaNN * 0.5; + doneN = abs(lumaEndN) >= gradientScaled; + doneP = abs(lumaEndP) >= gradientScaled; + if (!doneN) posN.x -= offNP.x * FXAA_QUALITY__P8; + if (!doneN) posN.y -= offNP.y * FXAA_QUALITY__P8; + doneNP = (!doneN) || (!doneP); + if (!doneP) posP.x += offNP.x * FXAA_QUALITY__P8; + if (!doneP) posP.y += offNP.y * FXAA_QUALITY__P8; + /*--------------------------------------------------------------------------*/ +#if (FXAA_QUALITY__PS > 9) + if (doneNP) { + if (!doneN) lumaEndN = FxaaLuma(FxaaTexTop(tex, posN.xy)); + if (!doneP) lumaEndP = FxaaLuma(FxaaTexTop(tex, posP.xy)); + if (!doneN) lumaEndN = lumaEndN - lumaNN * 0.5; + if (!doneP) lumaEndP = lumaEndP - lumaNN * 0.5; + doneN = abs(lumaEndN) >= gradientScaled; + doneP = abs(lumaEndP) >= gradientScaled; + if (!doneN) posN.x -= offNP.x * FXAA_QUALITY__P9; + if (!doneN) posN.y -= offNP.y * FXAA_QUALITY__P9; + doneNP = (!doneN) || (!doneP); + if (!doneP) posP.x += offNP.x * FXAA_QUALITY__P9; + if (!doneP) posP.y += offNP.y * FXAA_QUALITY__P9; + /*--------------------------------------------------------------------------*/ +#if (FXAA_QUALITY__PS > 10) + if (doneNP) { + if (!doneN) lumaEndN = FxaaLuma(FxaaTexTop(tex, posN.xy)); + if (!doneP) lumaEndP = FxaaLuma(FxaaTexTop(tex, posP.xy)); + if (!doneN) lumaEndN = lumaEndN - lumaNN * 0.5; + if (!doneP) lumaEndP = lumaEndP - lumaNN * 0.5; + doneN = abs(lumaEndN) >= gradientScaled; + doneP = abs(lumaEndP) >= gradientScaled; + if (!doneN) posN.x -= offNP.x * FXAA_QUALITY__P10; + if (!doneN) posN.y -= offNP.y * FXAA_QUALITY__P10; + doneNP = (!doneN) || (!doneP); + if (!doneP) posP.x += offNP.x * FXAA_QUALITY__P10; + if (!doneP) posP.y += offNP.y * FXAA_QUALITY__P10; + /*--------------------------------------------------------------------------*/ +#if (FXAA_QUALITY__PS > 11) + if (doneNP) { + if (!doneN) lumaEndN = FxaaLuma(FxaaTexTop(tex, posN.xy)); + if (!doneP) lumaEndP = FxaaLuma(FxaaTexTop(tex, posP.xy)); + if (!doneN) lumaEndN = lumaEndN - lumaNN * 0.5; + if (!doneP) lumaEndP = lumaEndP - lumaNN * 0.5; + doneN = abs(lumaEndN) >= gradientScaled; + doneP = abs(lumaEndP) >= gradientScaled; + if (!doneN) posN.x -= offNP.x * FXAA_QUALITY__P11; + if (!doneN) posN.y -= offNP.y * FXAA_QUALITY__P11; + doneNP = (!doneN) || (!doneP); + if (!doneP) posP.x += offNP.x * FXAA_QUALITY__P11; + if (!doneP) posP.y += offNP.y * FXAA_QUALITY__P11; + /*--------------------------------------------------------------------------*/ +#if (FXAA_QUALITY__PS > 12) + if (doneNP) { + if (!doneN) lumaEndN = FxaaLuma(FxaaTexTop(tex, posN.xy)); + if (!doneP) lumaEndP = FxaaLuma(FxaaTexTop(tex, posP.xy)); + if (!doneN) lumaEndN = lumaEndN - lumaNN * 0.5; + if (!doneP) lumaEndP = lumaEndP - lumaNN * 0.5; + doneN = abs(lumaEndN) >= gradientScaled; + doneP = abs(lumaEndP) >= gradientScaled; + if (!doneN) posN.x -= offNP.x * FXAA_QUALITY__P12; + if (!doneN) posN.y -= offNP.y * FXAA_QUALITY__P12; + doneNP = (!doneN) || (!doneP); + if (!doneP) posP.x += offNP.x * FXAA_QUALITY__P12; + if (!doneP) posP.y += offNP.y * FXAA_QUALITY__P12; + /*--------------------------------------------------------------------------*/ + } +#endif + /*--------------------------------------------------------------------------*/ + } +#endif + /*--------------------------------------------------------------------------*/ + } +#endif + /*--------------------------------------------------------------------------*/ + } +#endif + /*--------------------------------------------------------------------------*/ + } +#endif + /*--------------------------------------------------------------------------*/ + } +#endif + /*--------------------------------------------------------------------------*/ + } +#endif + /*--------------------------------------------------------------------------*/ + } +#endif + /*--------------------------------------------------------------------------*/ + } +#endif + /*--------------------------------------------------------------------------*/ + } +#endif + /*--------------------------------------------------------------------------*/ + } + /*--------------------------------------------------------------------------*/ + FxaaFloat dstN = posM.x - posN.x; + FxaaFloat dstP = posP.x - posM.x; + if (!horzSpan) dstN = posM.y - posN.y; + if (!horzSpan) dstP = posP.y - posM.y; + /*--------------------------------------------------------------------------*/ + FxaaBool goodSpanN = (lumaEndN < 0.0) != lumaMLTZero; + FxaaFloat spanLength = (dstP + dstN); + FxaaBool goodSpanP = (lumaEndP < 0.0) != lumaMLTZero; + FxaaFloat spanLengthRcp = 1.0 / spanLength; + /*--------------------------------------------------------------------------*/ + FxaaBool directionN = dstN < dstP; + FxaaFloat dst = min(dstN, dstP); + FxaaBool goodSpan = directionN ? goodSpanN : goodSpanP; + FxaaFloat subpixG = subpixF * subpixF; + FxaaFloat pixelOffset = (dst * (-spanLengthRcp)) + 0.5; + FxaaFloat subpixH = subpixG * fxaaQualitySubpix; + /*--------------------------------------------------------------------------*/ + FxaaFloat pixelOffsetGood = goodSpan ? pixelOffset : 0.0; + FxaaFloat pixelOffsetSubpix = max(pixelOffsetGood, subpixH); + if (!horzSpan) posM.x += pixelOffsetSubpix * lengthSign; + if (horzSpan) posM.y += pixelOffsetSubpix * lengthSign; +#if (FXAA_DISCARD == 1) + return FxaaTexTop(tex, posM); +#else + return FxaaFloat4(FxaaTexTop(tex, posM).xyz, lumaM); +#endif +} +/*==========================================================================*/ diff --git a/src/d2dx/Game.hlsli b/src/d2dx/Game.hlsli new file mode 100644 index 0000000..d9ee004 --- /dev/null +++ b/src/d2dx/Game.hlsli @@ -0,0 +1,42 @@ +/* + This file is part of D2DX. + + Copyright (C) 2021 Bolrog + + D2DX is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + D2DX is distributed in the hope that it will be 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 D2DX. If not, see . +*/ + +struct GameVSInput +{ + int2 pos : POSITION; + int2 texCoord : TEXCOORD0; + float4 color : COLOR0; + uint2 misc : TEXCOORD1; +}; + +struct GameVSOutput +{ + noperspective float4 pos : SV_POSITION; + noperspective float2 tc : TEXCOORD0; + noperspective float4 color : COLOR0; + nointerpolation uint4 atlasIndex_paletteIndex_surfaceId_flags : TEXCOORD1; +}; + +typedef GameVSOutput GamePSInput; + +struct GamePSOutput +{ + float4 color : SV_TARGET0; + float surfaceId : SV_TARGET1; +}; diff --git a/src/d2dx/GameHelper.cpp b/src/d2dx/GameHelper.cpp index 30e01f6..6f5bd89 100644 --- a/src/d2dx/GameHelper.cpp +++ b/src/d2dx/GameHelper.cpp @@ -28,16 +28,17 @@ GameHelper::GameHelper() : _hProcess(GetCurrentProcess()), _hGameExe(GetModuleHandleA("game.exe")), _hD2ClientDll(LoadLibraryA("D2Client.dll")), - _isPd2(LoadLibraryA("PD2_EXT.dll") != nullptr) + _hD2CommonDll(LoadLibraryA("D2Common.dll")), + _hD2GfxDll(LoadLibraryA("D2Gfx.dll")), + _hD2WinDll(LoadLibraryA("D2Win.dll")), + _isProjectDiablo2(GetModuleHandleA("PD2_EXT.dll") != nullptr) { InitializeTextureHashPrefixTable(); -} -GameHelper::~GameHelper() -{ - //CloseHandle(_hProcess); - //CloseHandle(_hGameExe); - //CloseHandle(_hD2ClientDll); + if (_isProjectDiablo2) + { + D2DX_LOG("Detected Project Diablo 2."); + } } GameVersion GameHelper::GetVersion() const @@ -45,13 +46,14 @@ GameVersion GameHelper::GetVersion() const return _version; } +_Use_decl_annotations_ const char* GameHelper::GetVersionString() const { switch (_version) { case GameVersion::Lod109d: return "Lod109d"; - case GameVersion::Lod110: + case GameVersion::Lod110f: return "Lod110"; case GameVersion::Lod112: return "Lod112"; @@ -71,99 +73,50 @@ uint32_t GameHelper::ScreenOpenMode() const switch (_version) { case GameVersion::Lod109d: - return ReadU32(_hD2ClientDll, 0x115C10); - case GameVersion::Lod110: - return ReadU32(_hD2ClientDll, 0x10B9C4); + return *(const uint32_t*)((uint32_t)_hD2ClientDll + 0x115C10); + case GameVersion::Lod110f: + return *(const uint32_t*)((uint32_t)_hD2ClientDll + 0x10B9C4); case GameVersion::Lod112: - return ReadU32(_hD2ClientDll, 0x11C1D0); + return *(const uint32_t*)((uint32_t)_hD2ClientDll + 0x11C1D0); case GameVersion::Lod113c: - return ReadU32(_hD2ClientDll, 0x11C414); + return *(const uint32_t*)((uint32_t)_hD2ClientDll + 0x11C414); case GameVersion::Lod113d: - return ReadU32(_hD2ClientDll, 0x11D070); + return *(const uint32_t*)((uint32_t)_hD2ClientDll + 0x11D070); case GameVersion::Lod114d: - return ReadU32(_hGameExe, 0x3A5210); + return *(const uint32_t*)((uint32_t)_hGameExe + 0x3A5210); default: return 0; } } -void GameHelper::GetConfiguredGameSize(int32_t* width, int32_t* height) const +Size GameHelper::GetConfiguredGameSize() const { HKEY hKey; LPCTSTR diablo2Key = TEXT("SOFTWARE\\Blizzard Entertainment\\Diablo II"); LONG openRes = RegOpenKeyEx(HKEY_CURRENT_USER, diablo2Key, 0, KEY_READ, &hKey); - assert(openRes == ERROR_SUCCESS); - if (openRes == ERROR_SUCCESS) + if (openRes != ERROR_SUCCESS) { - DWORD type = REG_DWORD; - DWORD size = 4; - DWORD value; - auto queryRes = RegQueryValueExA(hKey, "Resolution", NULL, &type, (LPBYTE)&value, &size); - assert(queryRes == ERROR_SUCCESS || queryRes == ERROR_MORE_DATA); + return { 800, 600 }; + } - if (value == 0) - { - *width = 640; - *height = 480; - } - else - { - *width = 800; - *height = 600; - } + DWORD type = REG_DWORD; + DWORD size = 4; + DWORD value = 0; + auto queryRes = RegQueryValueExA(hKey, "Resolution", NULL, &type, (LPBYTE)&value, &size); + assert(queryRes == ERROR_SUCCESS || queryRes == ERROR_MORE_DATA); - if (RegCloseKey(hKey) == ERROR_SUCCESS) - { + RegCloseKey(hKey); - } + if (value == 0) + { + return { 640, 480 }; } -} - -void GameHelper::SetIngameMousePos(int32_t x, int32_t y) -{ - switch (_version) + else { - case GameVersion::Lod109d: - WriteU32(_hD2ClientDll, 0x12B168, (uint32_t)x); - WriteU32(_hD2ClientDll, 0x12B16C, (uint32_t)y); - break; - case GameVersion::Lod110: - WriteU32(_hD2ClientDll, 0x121AE4, (uint32_t)x); - WriteU32(_hD2ClientDll, 0x121AE8, (uint32_t)y); - break; - case GameVersion::Lod112: - WriteU32(_hD2ClientDll, 0x101638, (uint32_t)x); - WriteU32(_hD2ClientDll, 0x101634, (uint32_t)y); - break; - case GameVersion::Lod113c: - WriteU32(_hD2ClientDll, 0x11B828, (uint32_t)x); - WriteU32(_hD2ClientDll, 0x11B824, (uint32_t)y); - break; - case GameVersion::Lod113d: - WriteU32(_hD2ClientDll, 0x11C950, (uint32_t)x); - WriteU32(_hD2ClientDll, 0x11C94C, (uint32_t)y); - break; - case GameVersion::Lod114d: - WriteU32(_hGameExe, 0x3A6AB0, (uint32_t)x); - WriteU32(_hGameExe, 0x3A6AAC, (uint32_t)y); - break; + return { 800, 600 }; } } -uint32_t GameHelper::ReadU32(HANDLE hModule, uint32_t offset) const -{ - uint32_t result; - DWORD bytesRead; - ReadProcessMemory(_hProcess, (LPCVOID)((uintptr_t)hModule + offset), &result, sizeof(DWORD), &bytesRead); - return result; -} - -void GameHelper::WriteU32(HANDLE hModule, uint32_t offset, uint32_t value) -{ - DWORD bytesWritten; - WriteProcessMemory(_hProcess, (LPVOID)((uintptr_t)hModule + offset), &value, 4, &bytesWritten); -} - static const uint32_t gameAddresses_109d[] = { 0xFFFFFFFF, @@ -218,7 +171,7 @@ static const uint32_t gameAddresses_113d[] = 0x6f857199, /* DrawWall1 */ 0x6f85718b, /* DrawWall2 */ 0x6f85c17c, /* DrawFloor */ - 0x50a995, /* DrawShadow */ + 0x6f859ef5, /* DrawShadow */ 0x6f859ce4, /* DrawDynamic */ 0x0050c38d, /* DrawSomething1 */ 0x0050c0de, /* DrawSomething2 */ @@ -227,16 +180,18 @@ static const uint32_t gameAddresses_113d[] = static const uint32_t gameAddresses_114d[] = { 0xFFFFFFFF, - 0x0050d39f, /* DrawWall1 */ - 0x0050d3ae, /* DrawWall2 */ + 0x50d39f, /* DrawWall1 */ + 0x50d3ae, /* DrawWall2 */ 0x50db03, /* DrawFloor */ 0x50a995, /* DrawShadow */ 0x50abdc, /* DrawDynamic */ - 0x0050c38d, /* DrawSomething1 */ - 0x0050c0de, /* DrawSomething2 */ + 0x50c38d, /* DrawSomething1 */ + 0x50c0de, /* DrawSomething2 */ }; -GameAddress GameHelper::IdentifyGameAddress(uint32_t returnAddress) const +_Use_decl_annotations_ +GameAddress GameHelper::IdentifyGameAddress( + uint32_t returnAddress) const { const uint32_t* gameAddresses = nullptr; uint32_t gameAddressCount = 0; @@ -247,7 +202,7 @@ GameAddress GameHelper::IdentifyGameAddress(uint32_t returnAddress) const gameAddresses = gameAddresses_109d; gameAddressCount = ARRAYSIZE(gameAddresses_109d); break; - case GameVersion::Lod110: + case GameVersion::Lod110f: gameAddresses = gameAddresses_110; gameAddressCount = ARRAYSIZE(gameAddresses_110); break; @@ -303,52 +258,19 @@ static const uint32_t mousePointerHashes[] = { 4264884407 }; -static const uint32_t fontHashes[] = { - 0xff9d7cf8, 0xf7c4ad96, 0xd6af9583, 0xd029a5d5, 0xa90311b2, 0xadea0b9e, 0xa57e1ec0, 0xcf7af660, 0x00aab565, - 0x0c65ba68, 0x0e977727, 0x1f445ffa, 0x2d08bd33, 0x2ca97e69, 0x3c724844, 0x3caef328, 0x3f18b5f3, 0x3ff9c768, - 0x4a819824, 0x4c738a3c, 0x4d25e783, 0x05bf54f3, 0x5a64ee53, 0x5c4804e8, 0x5ce69df3, 0x5ce768fb, 0x5ea96f40, - 0x7dbd6631, 0x8a06f474, 0x9e8e6f10, 0x13cb47b5, 0x24f62232, 0x47b5fbd0, 0x51ede2ae, 0x52c7c4f6, 0x60d9ac94, - 0x62c3ffa3, 0x65bfca36, 0x68dcd701, 0x80fb0c0d, 0x85b3c815, 0x94faf8ad, 0x96c426dc, 0x155b3bb1, 0x245c21b8, - 0x356ee855, 0x460cfabb, 0x855e9a3b, 0x1087bb49, 0x2727c6d9, 0x06347dbb, 0x8835bc6c, 0x15188ed5, 0x19665c92, - 0x30993a13, 0x62656aab, 0x76727e83, 0x102836dc, 0x620706d6, 0x722324f8, 0x885069da, 0x3329672c, 0x4543469f, - 0x8480460c, 0x14180660, 0x25649726, 0x65314024, 0xab719489, 0xaf9ffed3, 0xaf349ec7, 0xb8b73c0a, 0xb10a6b9a, - 0xb38f7483, 0xbed68f8e, 0xc1cdab68, 0xc07afb8b, 0xc460bb6b, 0xc566bc8a, 0xc1570ebc, 0xc205717c, 0xcc8840fe, - 0xd2cd74fc, 0xd9088161, 0xdb69ea24, 0xdc15a3ed, 0xddfc5520, 0xdff6bcd4, 0xe24ba0a5, 0xe091efa4, 0xe286be37, - 0xeae6fa1f, 0xee0b8133, 0xebe9615c, 0xf9ab5f62, 0xf97d8adc, 0xf418ef8b, 0xf82003be, 0xf958391b, 0xfb690655, - 0xfee0039e, 0x928ff333, 0xea69c070, 0x0d947578, 0x1cbd91ad, 0x1fa526bc, 0x1fd46ccf, 0x1fdf5ea8, 0x3b2a1ce3, - 0x3c8de02e, 0x3d2ad5b5, 0x3d60f2dc, 0x3fa20cb4, 0x3fce911b, 0x4bea7b80, 0x4dc94979, 0x4fbf1544, 0x5d0174e2, - 0x6aa87c97, 0x6d14c7af, 0x6d6036ee, 0x7aaa18cc, 0x7ce199c3, 0x7d0461f1, 0x8d3f1a73, 0x09cbde64, 0x9a4b2631, - 0x9ef02de8, 0x23b1de7d, 0x25a23e43, 0x31f5175a, 0x37af945a, 0x38f29bc0, 0x55f63b97, 0x62f52414, 0x66ca38dd, - 0x92ec24b0, 0x387b8aee, 0x428e026c, 0x690bed2b, 0x692e4a4a, 0x847c91ef, 0x877a3b9b, 0x3167ee35, 0x3323bf9e, - 0x36869e78, 0x411028ed, 0x446142ed, 0x848018c0, 0xaf8d3a5c, 0xbf500226, 0xc4eb73ba, 0xcf13c17a, 0xde96ce8a, - 0xe2b0c0d3, 0xe3c5cef7, 0xe6111a39, 0xf1554ddc, 0xfd8f0259, 0xfd00624b, 0x1f383eca, 0x1fd7911a, 0x6aeda9b7, - 0x7ad97a63, 0x7b55503e, 0x9fcbd6fc, 0x73a4d426, 0x77cfbf47, 0x292f9384, 0x0643d1f1, 0x4781b657, 0x122498bf, - 0xc947ac10, 0x3328df8c, 0x3349e905, 0x7437f47f, 0x8045d474, 0xb6e04288, 0xb6fa9b81, 0xdd4cd45e, 0x1c8454a2, - 0x3d79266d, 0x5ca112e7, 0x5db2cc26, 0x7c418268, 0x8a12f6bb, 0x8c6f28ab, 0x57b0ca11, 0x87f9b907, 0x960a9d44, - 0x5226af30, 0x35194776, 0xe5886490, 0x23c5e864, 0xccd53ac2, 0x6b6126f3, 0x8d4a3260, 0x71c5c663, 0x3dac9128, - 0x3dfc33ce, 0x04d447b9, 0x4afc2cf3, 0x4baac923, 0x4c5a4e9f, 0x7f342730, 0x8d761f96, 0x9fa17a31, 0x22b0435d, - 0x43d5debe, 0x46f04050, 0x71f96193, 0x81b64156, 0x094eb2d1, 0x95cabf5d, 0x729fc87a, 0x660724cf, 0x684355a9, - 0x89536279, 0xb2198727, 0xbd566762, 0xc182ea8d, 0xcb569296, 0xd701749f, 0xda33a8e3, 0xebcfc5e7, 0xef6827d7, - 0xf2b185b8, 0xf8787f41, 0xf682657c, 0xf538790e, 0xf6cc1be3, 0xe992347d, 0xe489be84, 0xd5446f30, 0xd190fe55, - 0xc42b6fe3, 0xc7bbf5eb, 0xbe471694, 0xbe5cbd2e, 0xbc628684, 0xaec57ae9, 0xadf284ff, 0xacf053d5, 0xa38462c1, - 0xa253e919, 0xa19d55b1, 0x49091212, 0x16932965, 0x11925128, 0x521153b5, 0x275935d8, 0x331599b9, 0x195237ef, - 0x84975db9, 0x5485b7dd, 0x04496dfc, 0x3390f1cc, 0x0990bc41, 0x1079a332, 0x757f3830, 0x539ff448, 0x248be1d0, - 0x167b8dd1, 0x153ed279, 0x75ce2c45, 0x73d1e193, 0x72dbbd11, 0x72c3b9d1, 0x48fcc13c, 0x23a9a88b, 0x22bc1bc3, - 0x15c78e77, 0x9ff38c9c, 0x9b5464cd, 0x08b7ea6f, 0x6da5fdd9, 0x6c7fc8b2, 0x5cbacb93, 0x3f6d8396, 0x2f421fa3, - 0x2bb13649, 0x1c37c7af, 0x1b09d728, 0x0e107166, 0x01ea580e, 0x2ec3b8ce, 0x2c13208e, 0x9cca88da, 0x9ef9292c, - 0x13d447f4, 0x39b8cc8d, 0x035f2ee3, 0x57bdbc1b, 0x63ecc03a, 0x97ae8499, 0x877de3e8, 0x5376a8fd, 0xac2fa415, - 0xad00702c, 0xae8a3926, 0xcbce092d, 0xd456f540, 0xd0f2af58, 0xdcd00ab8, 0xdc6d8f42, 0xe42e180a, 0xee4e139f, - 0xe7836ebb, 0xe7015e8c, 0xfbd9771a, 0xfdce42e3, 0x4f75b1f9 -}; - static const uint32_t uiHashes[] = { - 0x2ff1fd61, 0x54cc8b72, 0xfc253c88, 0xabe12614, 0xa22f5459, 0xa0d8fb2a, 0x20526487, 0x8a3b7d58, 0x2ff1fd61, + 0x2ff1fd61, 0x54cc8b72, 0xfc253c88, 0xabe12614, 0xa22f5459, 0xa0d8fb2a, 0x20526487, 0x8a3b7d58, 0x2ff1fd61, 0x54cc8b72, 0x76aa9aac, 0xef8d8978, 0x45e0af79, 0x9a008b35, 0x2a53bd89, 0x13d2c082, 0xab6ab811, 0xee7d31ba, 0x6d1e37cf, 0xa4e86125, 0xa769824b, 0xb4119f58, 0xc2da4379, 0xdfbf045f, 0x88021112, 0x726eeaa0, 0x49e4e24e, 0x3b50f3b6, 0x1e623206, 0xae502740, 0xd16d7f9a, 0xf6ec6116, 0x56acd7e4, 0x7656c190, 0xb0d15023, 0xb2c6e5fb, 0x27d5991a, 0x21d8d615, 0x2bbf74be, 0x9ab19e53, 0x9ba9eeb2, 0x109348c9, 0x0f37086a, 0x10ac28d0, 0x5c121175, 0x5c4d1125, 0xa1990293, 0xae25bff7, 0xb5855728, 0xc8f9d3f1, 0x2172d939, 0x0bd8d550, 0x62cfb0b8, 0x93e92b00, - 0x815a6925, 0x135190af, 0x3408446d, 0xaa265b2e + 0x815a6925, 0x135190af, 0x3408446d, 0xaa265b2e, 0x316149fe, 0x63556155, 0xa9ba1eb0, 0xa9e34142, 0xa0564010, + 0xb0a058c2, 0xb037844a, 0xbbfee318, 0xc95d3136, 0xceadb1cd, 0xcef62ab8, 0xcfd7f4dd, 0xd8a1f81b, 0xd8df8f4b, + 0xd9dc1bdd, 0xdfe365f3, 0xee4f10d9, 0x4c389b09, 0x4c049e57, 0x4c8bda35, 0x4d234ffb, 0x4b2e9d5b, 0x1ffb1615, + 0x0a90d031, 0x5ed2fc41, 0x6b7e62ef, 0x6d05de67, 0x7acfc435, 0x7a742b36, 0x8d3366ec, 0x9ab19e5e, 0x933ba45c, + 0x977c13be, 0x7820ea79, 0x9643d531, 0x7111312a, 0x25534537, 0xc723c18e, 0xfa170b3f, 0x97c7e7f4, 0x8ce7ef63, + 0x45c78147, 0x5ca62551, 0xf8d429fb, 0xfee40e62, }; struct Hashes @@ -361,7 +283,7 @@ static const Hashes hashesPerCategory[(int)TextureCategory::Count] = { { 0, nullptr }, { ARRAYSIZE(mousePointerHashes), mousePointerHashes }, - { ARRAYSIZE(fontHashes), fontHashes }, + { 0, nullptr }, { ARRAYSIZE(loadingScreenHashes), loadingScreenHashes }, { /* Floor: don't bother keeping a list of hashes */ 0, nullptr }, { ARRAYSIZE(titleScreenHashes), titleScreenHashes }, @@ -371,30 +293,27 @@ static const Hashes hashesPerCategory[(int)TextureCategory::Count] = static bool isInitialized = false; static Buffer prefixTable[256]; -static uint32_t prefixCounts[256]; +static Buffer prefixCounts(256, true); void GameHelper::InitializeTextureHashPrefixTable() { - memset(prefixCounts, 0, sizeof(uint32_t) * 256); - for (int32_t category = 0; category < ARRAYSIZE(hashesPerCategory); ++category) { const Hashes& hashes = hashesPerCategory[category]; for (int32_t hashIndex = 0; hashIndex < hashes.count; ++hashIndex) { - ++prefixCounts[(hashes.hashes[hashIndex] >> 24)&0xFF]; + ++prefixCounts.items[(hashes.hashes[hashIndex] >> 24) & 0xFF]; } } for (int32_t prefix = 0; prefix < 256; ++prefix) { - const uint32_t count = prefixCounts[prefix]; + const uint32_t count = prefixCounts.items[prefix]; if (count > 0) { - prefixTable[prefix] = Buffer(count); - memset(prefixTable[prefix].items, 0, sizeof(uint32_t) * count); + prefixTable[prefix] = Buffer(count, true); } else { @@ -409,13 +328,15 @@ void GameHelper::InitializeTextureHashPrefixTable() { const uint32_t hash = hashes.hashes[j]; const uint32_t prefix = hash >> 24; - const uint32_t position = --prefixCounts[prefix]; + const uint32_t position = --prefixCounts.items[prefix]; prefixTable[prefix].items[position] = (((uint32_t)category & 0xFF) << 24) | (hash & 0x00FFFFFF); } } } -TextureCategory GameHelper::GetTextureCategoryFromHash(uint32_t textureHash) const +_Use_decl_annotations_ +TextureCategory GameHelper::GetTextureCategoryFromHash( + uint32_t textureHash) const { Buffer& table = prefixTable[textureHash >> 24]; @@ -435,8 +356,16 @@ TextureCategory GameHelper::GetTextureCategoryFromHash(uint32_t textureHash) con return TextureCategory::Unknown; } -TextureCategory GameHelper::RefineTextureCategoryFromGameAddress(TextureCategory previousCategory, GameAddress gameAddress) const +_Use_decl_annotations_ +TextureCategory GameHelper::RefineTextureCategoryFromGameAddress( + TextureCategory previousCategory, + GameAddress gameAddress) const { + if (previousCategory != TextureCategory::Unknown) + { + return previousCategory; + } + switch (gameAddress) { case GameAddress::DrawFloor: @@ -455,34 +384,29 @@ GameVersion GameHelper::GetGameVersion() { GameVersion version = GameVersion::Unsupported; - if (_isPd2) - { - return version; - } - auto versionSize = GetFileVersionInfoSizeA("game.exe", nullptr); Buffer verData(versionSize); if (!GetFileVersionInfoA("game.exe", NULL, verData.capacity, verData.items)) { - ALWAYS_PRINT("Failed to get file version for game.exe."); + D2DX_LOG("Failed to get file version for game.exe."); return GameVersion::Unsupported; } uint32_t size = 0; const uint8_t* lpBuffer = nullptr; - bool success = VerQueryValueA(verData.items, "\\", (VOID FAR* FAR*) &lpBuffer, &size); + bool success = VerQueryValueA(verData.items, "\\", (VOID FAR * FAR*) & lpBuffer, &size); if (!(success && size > 0)) { - ALWAYS_PRINT("Failed to query version info for game.exe."); + D2DX_LOG("Failed to query version info for game.exe."); return GameVersion::Unsupported; } VS_FIXEDFILEINFO* vsFixedFileInfo = (VS_FIXEDFILEINFO*)lpBuffer; if (vsFixedFileInfo->dwSignature != 0xfeef04bd) { - ALWAYS_PRINT("Unexpected signature in version info for game.exe."); + D2DX_LOG("Unexpected signature in version info for game.exe."); return GameVersion::Unsupported; } @@ -497,7 +421,7 @@ GameVersion GameHelper::GetGameVersion() } else if (a == 1 && b == 0 && c == 10 && d == 9) { - version = GameVersion::Lod110; + version = GameVersion::Lod110f; } else if (a == 1 && b == 0 && c == 12 && d == 49) { @@ -513,8 +437,7 @@ GameVersion GameHelper::GetGameVersion() } else if (a == 1 && b == 14 && c == 3 && d == 68) { - MessageBoxA(NULL, "This version (1.14b) of Diablo II will not work with D2DX. Please upgrade to version 1.14d.", "D2DX", MB_OK); - PostQuitMessage(1); + D2DX_FATAL_ERROR("This version (1.14b) of Diablo II will not work with D2DX. Please upgrade to version 1.14d."); } else if (a == 1 && b == 14 && c == 3 && d == 71) { @@ -526,8 +449,787 @@ GameVersion GameHelper::GetGameVersion() MessageBoxA(NULL, "This version of Diablo II is not supported by D2DX. Please upgrade or downgrade to a supported version.", "D2DX", MB_OK); } - ALWAYS_PRINT("Game version: %d.%d.%d.%d (%s)\n", a, b, c, d, version == GameVersion::Unsupported ? "unsupported" : "supported"); + D2DX_LOG("Game version: %d.%d.%d.%d (%s)\n", a, b, c, d, version == GameVersion::Unsupported ? "unsupported" : "supported"); return version; } +bool GameHelper::TryApplyInGameFpsFix() +{ + /* The offsets taken from The Phrozen Keep: https://d2mods.info/forum/viewtopic.php?t=65239. */ + + switch (_version) + { + case GameVersion::Lod109d: + if (ProbeUInt32(_hD2ClientDll, 0x9B63, 0x2B756FBB)) + { + PatchUInt32(_hD2ClientDll, 0x9B5F, 0x90909090); + PatchUInt32(_hD2ClientDll, 0x9B63, 0x90909090); + } + break; + case GameVersion::Lod110f: + if (ProbeUInt32(_hD2ClientDll, 0xA2C9, 0x2B75C085)) + { + PatchUInt32(_hD2ClientDll, 0xA2C9, 0x90909090); + } + break; + case GameVersion::Lod112: + if (ProbeUInt32(_hD2ClientDll, 0x7D1E5, 0x35756FBD)) + { + PatchUInt32(_hD2ClientDll, 0x7D1E1, 0x90909090); + PatchUInt32(_hD2ClientDll, 0x7D1E5, 0x90909090); + } + break; + case GameVersion::Lod113c: + if (ProbeUInt32(_hD2ClientDll, 0x44E4D, 0xFFFC8455)) + { + PatchUInt32(_hD2ClientDll, 0x44E51, 0x90909090); + PatchUInt32(_hD2ClientDll, 0x44E55, 0x90909090); + } + break; + case GameVersion::Lod113d: + if (ProbeUInt32(_hD2ClientDll, 0x45E9D, 0xFFFC738B)) + { + PatchUInt32(_hD2ClientDll, 0x45EA1, 0x90909090); + PatchUInt32(_hD2ClientDll, 0x45EA5, 0x90909090); + } + break; + case GameVersion::Lod114d: + if (ProbeUInt32(_hGameExe, 0x4F274, 0x000C6A68)) + { + PatchUInt32(_hGameExe, 0x4F278, 0x90909090); + PatchUInt32(_hGameExe, 0x4F27C, 0x90909090); + } + break; + default: + D2DX_LOG("Fps fix aborted: unsupported game version."); + return false; + } + + D2DX_LOG("Fps fix applied."); + return true; +} + +bool GameHelper::TryApplyMenuFpsFix() +{ + /* Patches found using 1.10 lead from D2Tweaks: https://github.com/Revan600/d2tweaks/. */ + + switch (_version) + { + case GameVersion::Lod109d: + if (ProbeUInt32(_hD2WinDll, 0xEC0C, 0x5051196A)) + { + PatchUInt32(_hD2WinDll, 0xEC0C, 0x50517F6A); + } + break; + case GameVersion::Lod110f: + if (ProbeUInt32(_hD2WinDll, 0xD029, 0x8128C783)) + { + PatchUInt32(_hD2WinDll, 0xD029, 0x81909090); + } + break; + case GameVersion::Lod112: + if (ProbeUInt32(_hD2WinDll, 0xD949, 0x8128C783)) + { + PatchUInt32(_hD2WinDll, 0xD949, 0x81909090); + } + break; + case GameVersion::Lod113c: + if (ProbeUInt32(_hD2WinDll, 0x18A19, 0x8128C783)) + { + PatchUInt32(_hD2WinDll, 0x18A19, 0x81909090); + } + break; + case GameVersion::Lod113d: + if (ProbeUInt32(_hD2WinDll, 0xED69, 0x8128C783)) + { + PatchUInt32(_hD2WinDll, 0xED69, 0x81909090); + } + break; + case GameVersion::Lod114d: + if (ProbeUInt32(_hGameExe, 0xFA62B, 0x8128C783)) + { + PatchUInt32(_hGameExe, 0xFA62B, 0x81909090); + } + break; + default: + D2DX_LOG("Menu fps fix aborted: unsupported game version."); + return false; + } + + D2DX_LOG("Menu fps fix applied."); + return true; +} + +bool GameHelper::TryApplyInGameSleepFixes() +{ + switch (_version) + { + case GameVersion::Lod110f: + if (ProbeUInt32(_hD2ClientDll, 0x2684, 0x15FF0A6A)) + { + PatchUInt32(_hD2ClientDll, 0x2684, 0x90909090); + PatchUInt32(_hD2ClientDll, 0x2688, 0x90909090); + } + if (ProbeUInt32(_hD2ClientDll, 0x9E68, 0x83D7FF53)) + { + PatchUInt32(_hD2ClientDll, 0x9E68, 0x83909090); + } + if (ProbeUInt32(_hD2ClientDll, 0x9E8C, 0x83D7FF53)) + { + PatchUInt32(_hD2ClientDll, 0x9E8C, 0x83909090); + } + break; + case GameVersion::Lod112: + if (ProbeUInt32(_hD2ClientDll, 0x6CFD4, 0x15FF0A6A)) + { + PatchUInt32(_hD2ClientDll, 0x6CFD4, 0x90909090); + } + + if (ProbeUInt32(_hD2ClientDll, 0x6CFD8, 0x6FB7EF7C)) + { + PatchUInt32(_hD2ClientDll, 0x6CFD8, 0x90909090); + } + + if (ProbeUInt32(_hD2ClientDll, 0x7BD18, 0xD3FF006A)) + { + PatchUInt32(_hD2ClientDll, 0x7BD18, 0x90909090); + } + + if (ProbeUInt32(_hD2ClientDll, 0x7BD3D, 0xD3FF006A)) + { + PatchUInt32(_hD2ClientDll, 0x7BD3D, 0x90909090); + } + break; + case GameVersion::Lod113c: + if (ProbeUInt32(_hD2WinDll, 0x18A63, 0xC815FF50) && + ProbeUInt32(_hD2WinDll, 0x18A67, 0xA16F8FB2)) + { + PatchUInt32(_hD2WinDll, 0x18A63, 0x90909090); + PatchUInt32(_hD2WinDll, 0x18A67, 0xA1909090); + } + + if (ProbeUInt32(_hD2ClientDll, 0x3CB92, 0x0A6A0874)) + { + PatchUInt32(_hD2ClientDll, 0x3CB92, 0x0A6A08EB); + } + + if (ProbeUInt32(_hD2ClientDll, 0x43988, 0xD3FF006A)) + { + PatchUInt32(_hD2ClientDll, 0x43988, 0x90909090); + } + + if (ProbeUInt32(_hD2ClientDll, 0x439AD, 0xD3FF006A)) + { + PatchUInt32(_hD2ClientDll, 0x439AD, 0x90909090); + } + + break; + case GameVersion::Lod113d: + if (ProbeUInt32(_hD2WinDll, 0xEDB3, 0xB815FF50)) + { + PatchUInt32(_hD2WinDll, 0xEDB3, 0x90909090); + } + + if (ProbeUInt32(_hD2WinDll, 0xEDB7, 0xA16F8FB2)) + { + PatchUInt32(_hD2WinDll, 0xEDB7, 0xA1909090); + } + + if (ProbeUInt32(_hD2ClientDll, 0x27724, 0x15FF0A6A)) + { + PatchUInt32(_hD2ClientDll, 0x27724, 0x90909090); + } + + if (ProbeUInt32(_hD2ClientDll, 0x27728, 0x6FB7FF6C)) + { + PatchUInt32(_hD2ClientDll, 0x27728, 0x90909090); + } + + if (ProbeUInt32(_hD2ClientDll, 0x4494D, 0xD3FF006A)) + { + PatchUInt32(_hD2ClientDll, 0x4494D, 0x90909090); + } + + if (ProbeUInt32(_hD2ClientDll, 0x44928, 0xD3FF006A)) + { + PatchUInt32(_hD2ClientDll, 0x44928, 0x90909090); + } + + break; + case GameVersion::Lod114d: + if (ProbeUInt32(_hGameExe, 0x51C42, 0x15FF0A6A)) + { + PatchUInt32(_hGameExe, 0x51C42, 0x90909090); + } + + if (ProbeUInt32(_hGameExe, 0x51C46, 0x006CC258)) + { + PatchUInt32(_hGameExe, 0x51C46, 0x90909090); + } + + if (ProbeUInt32(_hGameExe, 0x4C711, 0xD7FF006A)) + { + PatchUInt32(_hGameExe, 0x4C711, 0x90909090); + } + + if (ProbeUInt32(_hGameExe, 0x4C740, 0xD7FF006A)) + { + PatchUInt32(_hGameExe, 0x4C740, 0x90909090); + } + break; + default: + D2DX_LOG("In-game sleep fixes aborted: unsupported game version."); + return false; + } + + D2DX_LOG("In-game sleep fixes applied."); + return true; +} + +typedef D2::UnitAny* (__stdcall* GetClientPlayerFunc)(); + +D2::UnitAny* GameHelper::GetPlayerUnit() const +{ + GetClientPlayerFunc getClientPlayerFunc; + + switch (_version) + { + case GameVersion::Lod109d: + getClientPlayerFunc = (GetClientPlayerFunc)((uintptr_t)_hD2ClientDll + 0x8CFC0); + return getClientPlayerFunc(); + case GameVersion::Lod110f: + getClientPlayerFunc = (GetClientPlayerFunc)((uintptr_t)_hD2ClientDll + 0x883D0); + return getClientPlayerFunc(); + case GameVersion::Lod112: + return (D2::UnitAny*)*(const uint32_t*)((uint32_t)_hD2ClientDll + 0x11C3D0); + case GameVersion::Lod113c: + return (D2::UnitAny*)*(const uint32_t*)((uint32_t)_hD2ClientDll + 0x11BBFC); + case GameVersion::Lod113d: + return (D2::UnitAny*)*(const uint32_t*)((uint32_t)_hD2ClientDll + 0x11D050); + case GameVersion::Lod114d: + return (D2::UnitAny*)*(const uint32_t*)((uint32_t)_hGameExe + 0x3A6A70); + default: + return nullptr; + } +} + +_Use_decl_annotations_ +bool GameHelper::ProbeUInt32( + HANDLE hModule, + uint32_t offset, + uint32_t probeValue) +{ + uint32_t* patchLocation = (uint32_t*)((uint32_t)hModule + offset); + + if (*patchLocation != probeValue) + { + //D2DX_LOG("Probe failed at %#010x, expected %#010x but found %#010x.", offset, probeValue, *patchLocation); + return false; + } + + return true; +} + +_Use_decl_annotations_ +void GameHelper::PatchUInt32( + HANDLE hModule, + uint32_t offset, + uint32_t value) +{ + uint32_t* patchLocation = (uint32_t*)((uint32_t)hModule + offset); + + DWORD dwOldPage; + VirtualProtect(patchLocation, 4, PAGE_EXECUTE_READWRITE, &dwOldPage); + + *patchLocation = value; + + VirtualProtect(patchLocation, 4, dwOldPage, &dwOldPage); +} + +_Use_decl_annotations_ +D2::UnitType GameHelper::GetUnitType( + const D2::UnitAny* unit) const +{ + if (_version == GameVersion::Lod109d) + { + return unit->u.v109.dwType; + } + else + { + return unit->u.v112.dwType; + } +} + +_Use_decl_annotations_ +uint32_t GameHelper::GetUnitId( + const D2::UnitAny* unit) const +{ + if (_version == GameVersion::Lod109d) + { + return unit->u.v109.dwUnitId; + } + else + { + return unit->u.v112.dwUnitId; + } +} + +_Use_decl_annotations_ +Offset GameHelper::GetUnitPos( + const D2::UnitAny* unit) const +{ + auto unitType = GetUnitType(unit); + + if (unitType == D2::UnitType::Player || + unitType == D2::UnitType::Monster || + unitType == D2::UnitType::Missile) + { + D2::Path* path = _version == GameVersion::Lod109d ? unit->u.v109.path2 : unit->u.v112.path; + return { (int32_t)path->x, (int32_t)path->y }; + } + else + { + D2::StaticPath* path = _version == GameVersion::Lod109d ? unit->u.v109.staticPath2 : unit->u.v112.staticPath; + return { (int32_t)path->xPos * 65536 + (int32_t)path->xOffset, (int32_t)path->yPos * 65536 + (int32_t)path->yOffset }; + } +} + +typedef D2::UnitAny* (__fastcall* FindUnitFunc)(DWORD dwId, DWORD dwType); + +static D2::UnitAny* __fastcall FindClientSideUnit109d(DWORD unitId, DWORD unitType) +{ + uint32_t** unitPtrTable = (uint32_t**)0x6FBC4BF8; + uint32_t* unit = unitPtrTable[unitType * 128 + (unitId & 127)]; + + while (unit) + { + if (unit[0] == unitType && unit[2] == unitId) + { + return (D2::UnitAny*)unit; + } + + unit = (uint32_t*)unit[66]; + } + + return nullptr; +} + +static D2::UnitAny* __fastcall FindServerSideUnit109d(DWORD unitId, DWORD unitType) +{ + uint32_t** unitPtrTable = (uint32_t**)0x6FBC57F8; + uint32_t* unit = unitPtrTable[unitType * 128 + (unitId & 127)]; + + while (unit) + { + if (unit[0] == unitType && unit[2] == unitId) + { + return (D2::UnitAny*)unit; + } + + unit = (uint32_t*)unit[66]; + } + + return nullptr; +} + +_Use_decl_annotations_ +D2::UnitAny* GameHelper::FindUnit( + uint32_t unitId, + D2::UnitType unitType) const +{ + FindUnitFunc findClientSideUnit = (FindUnitFunc)GetFunction(D2Function::D2Client_FindClientSideUnit); + FindUnitFunc findServerSideUnit = (FindUnitFunc)GetFunction(D2Function::D2Client_FindServerSideUnit); + + if (findClientSideUnit) + { + auto unit = findClientSideUnit((DWORD)unitId, (DWORD)unitType); + + if (unit) + { + return unit; + } + } + + return findServerSideUnit ? findServerSideUnit((DWORD)unitId, (DWORD)unitType) : nullptr; +} + +_Use_decl_annotations_ +void* GameHelper::GetFunction( + D2Function function) const +{ + HANDLE hModule = nullptr; + int32_t ordinal = 0; + + switch (_version) + { + case GameVersion::Lod109d: + switch (function) + { + case D2Function::D2Gfx_DrawImage: + hModule = _hD2GfxDll; + ordinal = 10072; + break; + case D2Function::D2Gfx_DrawShiftedImage: + hModule = _hD2GfxDll; + ordinal = 10073; + break; + case D2Function::D2Gfx_DrawVerticalCropImage: + hModule = _hD2GfxDll; + ordinal = 10074; + break; + case D2Function::D2Gfx_DrawClippedImage: + hModule = _hD2GfxDll; + ordinal = 10077; + break; + case D2Function::D2Gfx_DrawImageFast: + hModule = _hD2GfxDll; + ordinal = 10076; + break; + case D2Function::D2Gfx_DrawShadow: + hModule = _hD2GfxDll; + ordinal = 10075; + break; + case D2Function::D2Win_DrawText: + hModule = _hD2WinDll; + ordinal = 10117; + break; + case D2Function::D2Client_DrawUnit: + return (void*)((uintptr_t)_hD2ClientDll + 0xB8350); + case D2Function::D2Client_FindClientSideUnit: + return (void*)FindClientSideUnit109d; + case D2Function::D2Client_DrawWeatherParticles: + return (void*)((uintptr_t)_hD2ClientDll + 0x07BC0); + case D2Function::D2Client_FindServerSideUnit: + return (void*)FindServerSideUnit109d; + default: + break; + } + break; + case GameVersion::Lod110f: + switch (function) + { + case D2Function::D2Gfx_DrawImage: + hModule = _hD2GfxDll; + ordinal = 10072; + break; + case D2Function::D2Gfx_DrawShiftedImage: + hModule = _hD2GfxDll; + ordinal = 10073; + break; + case D2Function::D2Gfx_DrawVerticalCropImage: + hModule = _hD2GfxDll; + ordinal = 10074; + break; + case D2Function::D2Gfx_DrawClippedImage: + hModule = _hD2GfxDll; + ordinal = 10077; + break; + case D2Function::D2Gfx_DrawImageFast: + hModule = _hD2GfxDll; + ordinal = 10076; + break; + case D2Function::D2Gfx_DrawShadow: + hModule = _hD2GfxDll; + ordinal = 10075; + break; + case D2Function::D2Win_DrawText: + hModule = _hD2WinDll; + ordinal = 10117; + break; + case D2Function::D2Client_DrawUnit: + return (void*)((uintptr_t)_hD2ClientDll + 0xBA720); + case D2Function::D2Client_FindClientSideUnit: + return (void*)((uintptr_t)_hD2ClientDll + 0x86BE0); + case D2Function::D2Client_DrawWeatherParticles: + return (void*)((uintptr_t)_hD2ClientDll + 0x08690); + case D2Function::D2Client_FindServerSideUnit: + return (void*)((uintptr_t)_hD2ClientDll + 0x86C70); + default: + break; + } + break; + case GameVersion::Lod112: + switch (function) + { + case D2Function::D2Gfx_DrawImage: + hModule = _hD2GfxDll; + ordinal = 10024; + break; + case D2Function::D2Gfx_DrawShiftedImage: + hModule = _hD2GfxDll; + ordinal = 10044; + break; + case D2Function::D2Gfx_DrawVerticalCropImage: + hModule = _hD2GfxDll; + ordinal = 10046; + break; + case D2Function::D2Gfx_DrawClippedImage: + hModule = _hD2GfxDll; + ordinal = 10061; + break; + case D2Function::D2Gfx_DrawImageFast: + hModule = _hD2GfxDll; + ordinal = 10012; + break; + case D2Function::D2Gfx_DrawShadow: + hModule = _hD2GfxDll; + ordinal = 10030; + break; + case D2Function::D2Win_DrawText: + hModule = _hD2WinDll; + ordinal = 10001; + break; + //case D2Function::D2Win_DrawFramedText: + // hModule = _hD2WinDll; + // ordinal = 10137; + // break; + //case D2Function::D2Win_DrawRectangledText: + // hModule = _hD2WinDll; + // ordinal = 10078; + // break; + case D2Function::D2Client_DrawUnit: + return (void*)((uintptr_t)_hD2ClientDll + 0x94250); + case D2Function::D2Client_DrawMissile: + return (void*)((uintptr_t)_hD2ClientDll + 0x949C0); + case D2Function::D2Client_DrawWeatherParticles: + return (void*)((uintptr_t)_hD2ClientDll + 0x14210); + case D2Function::D2Client_FindClientSideUnit: + return (void*)((uintptr_t)_hD2ClientDll + 0x1F1A0); + case D2Function::D2Client_FindServerSideUnit: + return (void*)((uintptr_t)_hD2ClientDll + 0x1F1C0); + default: + break; + } + break; + case GameVersion::Lod113c: + switch (function) + { + case D2Function::D2Gfx_DrawImage: + hModule = _hD2GfxDll; + ordinal = 10041; + break; + case D2Function::D2Gfx_DrawShiftedImage: + hModule = _hD2GfxDll; + ordinal = 10019; + break; + case D2Function::D2Gfx_DrawVerticalCropImage: + hModule = _hD2GfxDll; + ordinal = 10074; + break; + case D2Function::D2Gfx_DrawClippedImage: + hModule = _hD2GfxDll; + ordinal = 10079; + break; + case D2Function::D2Gfx_DrawImageFast: + hModule = _hD2GfxDll; + ordinal = 10046; + break; + case D2Function::D2Gfx_DrawShadow: + hModule = _hD2GfxDll; + ordinal = 10011; + break; + case D2Function::D2Win_DrawText: + hModule = _hD2WinDll; + ordinal = 10096; + break; + case D2Function::D2Win_DrawFramedText: + hModule = _hD2WinDll; + ordinal = 10085; + break; + case D2Function::D2Win_DrawRectangledText: + hModule = _hD2WinDll; + ordinal = 10013; + break; + case D2Function::D2Client_DrawUnit: + return (void*)((uintptr_t)_hD2ClientDll + 0x6C490); + case D2Function::D2Client_DrawMissile: + return (void*)((uintptr_t)_hD2ClientDll + 0x6CC00); + case D2Function::D2Client_DrawWeatherParticles: + return (void*)((uintptr_t)_hD2ClientDll + 0x7FE80); + case D2Function::D2Client_FindClientSideUnit: + return (void*)((uintptr_t)_hD2ClientDll + 0xA5B20); + case D2Function::D2Client_FindServerSideUnit: + return (void*)((uintptr_t)_hD2ClientDll + 0xA5B40); + default: + break; + } + break; + case GameVersion::Lod113d: + switch (function) + { + case D2Function::D2Gfx_DrawImage: + hModule = _hD2GfxDll; + ordinal = 10042; + break; + case D2Function::D2Gfx_DrawShiftedImage: + hModule = _hD2GfxDll; + ordinal = 10067; + break; + case D2Function::D2Gfx_DrawVerticalCropImage: + hModule = _hD2GfxDll; + ordinal = 10082; + break; + case D2Function::D2Gfx_DrawClippedImage: + hModule = _hD2GfxDll; + ordinal = 10015; + break; + case D2Function::D2Gfx_DrawImageFast: + hModule = _hD2GfxDll; + ordinal = 10006; + break; + case D2Function::D2Gfx_DrawShadow: + hModule = _hD2GfxDll; + ordinal = 10084; + break; + case D2Function::D2Win_DrawText: + hModule = _hD2WinDll; + ordinal = 10076; + break; + case D2Function::D2Win_DrawTextEx: + hModule = _hD2WinDll; + ordinal = 10084; + break; + case D2Function::D2Win_DrawFramedText: + hModule = _hD2WinDll; + ordinal = 10137; + break; + case D2Function::D2Win_DrawRectangledText: + hModule = _hD2WinDll; + ordinal = 10078; + break; + case D2Function::D2Client_DrawUnit: + return (void*)((uintptr_t)_hD2ClientDll + 0x605b0); + case D2Function::D2Client_DrawMissile: + return (void*)((uintptr_t)_hD2ClientDll + 0x60C70); + case D2Function::D2Client_DrawWeatherParticles: + return (void*)((uintptr_t)_hD2ClientDll + 0x4AD90); + case D2Function::D2Client_FindClientSideUnit: + return (void*)((uintptr_t)_hD2ClientDll + 0x620B0); + case D2Function::D2Client_FindServerSideUnit: + return (void*)((uintptr_t)_hD2ClientDll + 0x620D0); + default: + break; + } + break; + case GameVersion::Lod114d: + switch (function) + { + case D2Function::D2Gfx_DrawImage: + return (void*)((uintptr_t)_hGameExe + 0xF6480); + case D2Function::D2Gfx_DrawShiftedImage: + return (void*)((uintptr_t)_hGameExe + 0xF64B0); + case D2Function::D2Gfx_DrawVerticalCropImage: + return (void*)((uintptr_t)_hGameExe + 0xF64E0); + case D2Function::D2Gfx_DrawClippedImage: + return (void*)((uintptr_t)_hGameExe + 0xF6510); + case D2Function::D2Gfx_DrawImageFast: + return (void*)((uintptr_t)_hGameExe + 0xF6570); + case D2Function::D2Gfx_DrawShadow: + return (void*)((uintptr_t)_hGameExe + 0xF6540); + case D2Function::D2Win_DrawText: + return (void*)((uintptr_t)_hGameExe + 0x102320); + case D2Function::D2Win_DrawTextEx: + return (void*)((uintptr_t)_hGameExe + 0x102360); + case D2Function::D2Win_DrawFramedText: + return (void*)((uintptr_t)_hGameExe + 0x102280); + case D2Function::D2Win_DrawRectangledText: + return (void*)((uintptr_t)_hGameExe + 0x1023B0); + case D2Function::D2Client_DrawUnit: + return (void*)((uintptr_t)_hGameExe + 0x70EC0); + case D2Function::D2Client_DrawMissile: + return (void*)((uintptr_t)_hGameExe + 0x71EC0); + case D2Function::D2Client_DrawWeatherParticles: + return (void*)((uintptr_t)_hGameExe + 0x73470); + case D2Function::D2Client_FindClientSideUnit: + return (void*)((uintptr_t)_hGameExe + 0x63990); + case D2Function::D2Client_FindServerSideUnit: + return (void*)((uintptr_t)_hGameExe + 0x639B0); + default: + break; + } + break; + default: + break; + } + + if (!hModule || !ordinal) + { + return nullptr; + } + + return GetProcAddress((HMODULE)hModule, MAKEINTRESOURCEA(ordinal)); +} + +_Use_decl_annotations_ +DrawParameters GameHelper::GetDrawParameters( + const D2::CellContext* cellContext) const +{ + return + { + .unitId = GetVersion() == GameVersion::Lod109d || GetVersion() == GameVersion::Lod110f ? cellContext->_8 : cellContext->dwClass, + .unitType = GetVersion() == GameVersion::Lod109d || GetVersion() == GameVersion::Lod110f ? cellContext->_9 : cellContext->dwUnit, + .unitToken = GetVersion() == GameVersion::Lod109d || GetVersion() == GameVersion::Lod110f ? cellContext->_11 : cellContext->dwPlayerType + }; +} + +int32_t GameHelper::GetCurrentAct() const +{ + auto unit = GetPlayerUnit(); + + if (!unit) + { + return -1; + } + + return (int32_t)unit->u.v112.dwAct; +} + +bool GameHelper::IsGameMenuOpen() const +{ + switch (_version) + { + case GameVersion::Lod109d: + return *((uint32_t*)((uint32_t)_hD2ClientDll + 0x1248D8)) != 0; + case GameVersion::Lod110f: + return *((uint32_t*)((uint32_t)_hD2ClientDll + 0x11A6CC)) != 0; + case GameVersion::Lod112: + return *((uint32_t*)((uint32_t)_hD2ClientDll + 0x102B7C)) != 0; + case GameVersion::Lod113c: + return *((uint32_t*)((uint32_t)_hD2ClientDll + 0xFADA4)) != 0; + case GameVersion::Lod113d: + return *((uint32_t*)((uint32_t)_hD2ClientDll + 0x11C8B4)) != 0; + case GameVersion::Lod114d: + return *((uint32_t*)((uint32_t)_hGameExe + 0x3A27E4)) != 0; + default: + return false; + } +} + +bool GameHelper::IsInGame() const +{ + auto playerUnit = GetPlayerUnit(); + + switch (_version) + { + case GameVersion::Lod109d: + return *((uint32_t*)((uint32_t)_hD2ClientDll + 0x1109FC)) != 0 && playerUnit != 0 && playerUnit->u.v109.path != 0; + case GameVersion::Lod110f: + return *((uint32_t*)((uint32_t)_hD2ClientDll + 0x1077C4)) != 0 && playerUnit != 0 && playerUnit->u.v109.path != 0; + case GameVersion::Lod112: + return *((uint32_t*)((uint32_t)_hD2ClientDll + 0x11BCC4)) != 0 && playerUnit != 0 && playerUnit->u.v112.path != 0; + case GameVersion::Lod113c: + return *((uint32_t*)((uint32_t)_hD2ClientDll + 0xF8C9C)) != 0 && playerUnit != 0 && playerUnit->u.v112.path != 0; + case GameVersion::Lod113d: + return *((uint32_t*)((uint32_t)_hD2ClientDll + 0xF79E0)) != 0 && playerUnit != 0 && playerUnit->u.v112.path != 0; + case GameVersion::Lod114d: + return *((uint32_t*)((uint32_t)_hGameExe + 0x3A27C0)) != 0 && playerUnit != 0 && playerUnit->u.v112.path != 0; + default: + return false; + } +} + +bool GameHelper::IsProjectDiablo2() const +{ + return _isProjectDiablo2; +} diff --git a/src/d2dx/GameHelper.h b/src/d2dx/GameHelper.h index 4aa7860..5ae241f 100644 --- a/src/d2dx/GameHelper.h +++ b/src/d2dx/GameHelper.h @@ -18,38 +18,92 @@ */ #pragma once +#include "IGameHelper.h" #include "Types.h" namespace d2dx { - class GameHelper final + class GameHelper final : public IGameHelper { public: GameHelper(); - ~GameHelper(); + virtual ~GameHelper() noexcept {} - GameVersion GetVersion() const; - const char* GetVersionString() const; + virtual GameVersion GetVersion() const override; + + virtual _Ret_z_ const char* GetVersionString() const override; - uint32_t ScreenOpenMode() const; - void GetConfiguredGameSize(int32_t* width, int32_t* height) const; - void SetIngameMousePos(int32_t x, int32_t y); + virtual uint32_t ScreenOpenMode() const override; + + virtual Size GetConfiguredGameSize() const override; + + virtual GameAddress IdentifyGameAddress( + _In_ uint32_t returnAddress) const override; - GameAddress IdentifyGameAddress(uint32_t returnAddress) const; + virtual TextureCategory GetTextureCategoryFromHash( + _In_ uint32_t textureHash) const override; + + virtual TextureCategory RefineTextureCategoryFromGameAddress( + _In_ TextureCategory previousCategory, + _In_ GameAddress gameAddress) const override; - TextureCategory GetTextureCategoryFromHash(uint32_t textureHash) const; - TextureCategory RefineTextureCategoryFromGameAddress(TextureCategory previousCategory, GameAddress gameAddress) const; + virtual bool TryApplyInGameFpsFix() override; + + virtual bool TryApplyMenuFpsFix() override; + + virtual bool TryApplyInGameSleepFixes() override; + + virtual void* GetFunction( + _In_ D2Function function) const override; + + virtual DrawParameters GetDrawParameters( + _In_ const D2::CellContext* cellContext) const override; + + virtual D2::UnitAny* GetPlayerUnit() const override; + + virtual Offset GetUnitPos( + _In_ const D2::UnitAny* unit) const override; + + virtual D2::UnitType GetUnitType( + _In_ const D2::UnitAny* unit) const override; + + virtual uint32_t GetUnitId( + _In_ const D2::UnitAny* unit) const override; + + virtual D2::UnitAny* FindUnit( + _In_ uint32_t unitId, + _In_ D2::UnitType unitType) const override; + + virtual int32_t GetCurrentAct() const override; + + virtual bool IsGameMenuOpen() const override; + + virtual bool IsInGame() const override; + + virtual bool IsProjectDiablo2() const override; private: - uint32_t ReadU32(HANDLE module, uint32_t offset) const; - void WriteU32(HANDLE module, uint32_t offset, uint32_t value); GameVersion GetGameVersion(); + void InitializeTextureHashPrefixTable(); + + bool ProbeUInt32( + _In_ HANDLE hModule, + _In_ uint32_t offset, + _In_ uint32_t expectedValue); + + void PatchUInt32( + _In_ HANDLE hModule, + _In_ uint32_t offset, + _In_ uint32_t value); HANDLE _hProcess; HANDLE _hGameExe; HANDLE _hD2ClientDll; + HANDLE _hD2CommonDll; + HANDLE _hD2GfxDll; + HANDLE _hD2WinDll; GameVersion _version; - bool _isPd2; + bool _isProjectDiablo2; }; } diff --git a/src/d2dx/PixelShader.hlsl b/src/d2dx/GamePS.hlsl similarity index 50% rename from src/d2dx/PixelShader.hlsl rename to src/d2dx/GamePS.hlsl index 9106b45..a9eecc4 100644 --- a/src/d2dx/PixelShader.hlsl +++ b/src/d2dx/GamePS.hlsl @@ -17,34 +17,27 @@ along with D2DX. If not, see . */ #include "Constants.hlsli" +#include "Game.hlsli" Texture2DArray tex : register(t0); Texture1DArray palette : register(t1); -half4 main(PixelShaderInput psInput) : SV_TARGET +void main( + in GamePSInput ps_in, + out GamePSOutput ps_out) { - const uint atlasIndex = psInput.misc.x; - const bool chromaKeyEnabled = (psInput.misc.y & MISC_CHROMAKEY_ENABLED_MASK) != 0; + const uint atlasIndex = ps_in.atlasIndex_paletteIndex_surfaceId_flags.x; + const bool chromaKeyEnabled = ps_in.atlasIndex_paletteIndex_surfaceId_flags.w & 1; + const uint surfaceId = ps_in.atlasIndex_paletteIndex_surfaceId_flags.z; + const uint paletteIndex = ps_in.atlasIndex_paletteIndex_surfaceId_flags.y; - uint indexedColor = tex.Load(float4(psInput.tc, atlasIndex, 0)); + const uint indexedColor = tex.Load(int4(ps_in.tc, atlasIndex, 0)); if (chromaKeyEnabled && indexedColor == 0) discard; - const uint paletteIndex = psInput.misc.y >> 8; - const half4 textureColor = palette.Load(int3(indexedColor, paletteIndex, 0)); + const float4 textureColor = palette.Load(int3(indexedColor, paletteIndex, 0)); - const uint colorCombine = psInput.misc.y & MISC_RGB_MASK; - const uint alphaCombine = psInput.misc.y & MISC_ALPHA_MASK; - - half3 c = - (colorCombine == MISC_RGB_ITERATED_COLOR_MULTIPLIED_BY_TEXTURE) ? textureColor.rgb * psInput.color.rgb : - ((colorCombine == MISC_RGB_CONSTANT_COLOR) ? psInput.color.rgb : - half3(1, 0, 1)); - - half a = - (alphaCombine == MISC_ALPHA_ONE) ? 1 : - (alphaCombine == MISC_ALPHA_TEXTURE) ? psInput.color.a : 0; - - return half4(c, a); + ps_out.color = ps_in.color * textureColor; + ps_out.surfaceId = ps_in.color.a > 0.5 ? surfaceId * 1.0 / 16383.0 : 0.0; } diff --git a/src/d2dx/VertexShader.hlsl b/src/d2dx/GameVS.hlsl similarity index 52% rename from src/d2dx/VertexShader.hlsl rename to src/d2dx/GameVS.hlsl index f4b7d34..e1b9e99 100644 --- a/src/d2dx/VertexShader.hlsl +++ b/src/d2dx/GameVS.hlsl @@ -17,21 +17,18 @@ along with D2DX. If not, see . */ #include "Constants.hlsli" +#include "Game.hlsli" -PixelShaderInput main( - in float2 pos : POSITION, - in int2 tc : TEXCOORD0, - in float4 color : COLOR0, - in uint2 misc : TEXCOORD1) +void main( + in GameVSInput vs_in, + out GameVSOutput vs_out) { - pos *= vertexScale; - pos += vertexOffset; - float2 fpos = (2.0 * pos / screenSize) - 1.0; - - PixelShaderInput psInput; - psInput.pos = float4(fpos.x, -fpos.y, 0.0, 1.0); - psInput.tc = tc; - psInput.color = color; - psInput.misc = misc; - return psInput; + float2 unitPos = float2(vs_in.pos) * c_invScreenSize - 0.5; + vs_out.pos = unitPos.xyxx * float4(2, -2, 0, 0) + float4(0, 0, 0, 1); + vs_out.tc = vs_in.texCoord; + vs_out.color = vs_in.color; + vs_out.atlasIndex_paletteIndex_surfaceId_flags.x = vs_in.misc.x & 4095; + vs_out.atlasIndex_paletteIndex_surfaceId_flags.y = (vs_in.misc.x >> 12) | ((vs_in.misc.y & 0x8000) ? 0x10 : 0); + vs_out.atlasIndex_paletteIndex_surfaceId_flags.z = vs_in.misc.y & 16383; + vs_out.atlasIndex_paletteIndex_surfaceId_flags.w = (vs_in.misc.y & 0x4000) ? 1 : 0; } diff --git a/src/d2dx/GammaPS.hlsl b/src/d2dx/GammaPS.hlsl index c9e1bfe..83d55ef 100644 --- a/src/d2dx/GammaPS.hlsl +++ b/src/d2dx/GammaPS.hlsl @@ -17,15 +17,18 @@ along with D2DX. If not, see . */ #include "Constants.hlsli" +#include "Display.hlsli" Texture2D sceneTexture : register(t0); Texture1D gammaTexture : register(t1); -half4 main(in float2 tc : TEXCOORD0) : SV_TARGET +float4 main( + in DisplayPSInput ps_in) : SV_TARGET { - half3 c = sceneTexture.Load(float3(tc,0)).rgb; - c.r = gammaTexture.Sample(BilinearSampler, c.r).r; - c.g = gammaTexture.Sample(BilinearSampler, c.g).g; - c.b = gammaTexture.Sample(BilinearSampler, c.b).b; - return half4(c, 1); + float4 c = sceneTexture.SampleLevel(PointSampler, ps_in.tc, 0); + c.r = gammaTexture.SampleLevel(BilinearSampler, c.r, 0).r; + c.g = gammaTexture.SampleLevel(BilinearSampler, c.g, 0).g; + c.b = gammaTexture.SampleLevel(BilinearSampler, c.b, 0).b; + c.a = dot(c.rgb, float3(0.299, 0.587, 0.114)); + return c; } diff --git a/src/d2dx/GlideHelpers.h b/src/d2dx/GlideHelpers.h deleted file mode 100644 index ac54708..0000000 --- a/src/d2dx/GlideHelpers.h +++ /dev/null @@ -1,283 +0,0 @@ -/* - This file is part of D2DX. - - Copyright (C) 2021 Bolrog - - D2DX is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - D2DX is distributed in the hope that it will be 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 D2DX. If not, see . -*/ -#pragma once - -#include "Types.h" - -namespace d2dx -{ -#define D2DX_GLIDE_ALPHA_BLEND(rgb_sf, rgb_df, alpha_sf, alpha_df) \ - (uint16_t)(((rgb_sf & 0xF) << 12) | ((rgb_df & 0xF) << 8) | ((alpha_sf & 0xF) << 4) | (alpha_df & 0xF)) - -#define XGR_PARAMS \ - XGR_PARAM(GR_PARAM_XY) \ - XGR_PARAM(GR_PARAM_Z) \ - XGR_PARAM(GR_PARAM_W) \ - XGR_PARAM(GR_PARAM_Q) \ - XGR_PARAM(GR_PARAM_FOG_EXT) \ - XGR_PARAM(GR_PARAM_A) \ - XGR_PARAM(GR_PARAM_RGB) \ - XGR_PARAM(GR_PARAM_PARGB) \ - XGR_PARAM(GR_PARAM_ST0) \ - XGR_PARAM(GR_PARAM_ST1) \ - XGR_PARAM(GR_PARAM_ST2) \ - XGR_PARAM(GR_PARAM_Q0) \ - XGR_PARAM(GR_PARAM_Q1) \ - XGR_PARAM(GR_PARAM_Q2) - -#define XGR_COMBINE_FACTORS \ - XGR_COMBINE_FACTOR(GR_COMBINE_FACTOR_ZERO) \ - XGR_COMBINE_FACTOR(GR_COMBINE_FACTOR_LOCAL) \ - XGR_COMBINE_FACTOR(GR_COMBINE_FACTOR_OTHER_ALPHA) \ - XGR_COMBINE_FACTOR(GR_COMBINE_FACTOR_LOCAL_ALPHA) \ - XGR_COMBINE_FACTOR(GR_COMBINE_FACTOR_TEXTURE_ALPHA) \ - XGR_COMBINE_FACTOR(GR_COMBINE_FACTOR_TEXTURE_RGB) \ - XGR_COMBINE_FACTOR(GR_COMBINE_FACTOR_ONE) \ - XGR_COMBINE_FACTOR(GR_COMBINE_FACTOR_ONE_MINUS_LOCAL) \ - XGR_COMBINE_FACTOR(GR_COMBINE_FACTOR_ONE_MINUS_OTHER_ALPHA) \ - XGR_COMBINE_FACTOR(GR_COMBINE_FACTOR_ONE_MINUS_LOCAL_ALPHA) \ - XGR_COMBINE_FACTOR(GR_COMBINE_FACTOR_ONE_MINUS_TEXTURE_ALPHA) \ - XGR_COMBINE_FACTOR(GR_COMBINE_FACTOR_ONE_MINUS_LOD_FRACTION) - -#define XGR_COMBINE_FUNCTIONS \ - XGR_COMBINE_FUNCTION(GR_COMBINE_FUNCTION_ZERO) \ - XGR_COMBINE_FUNCTION(GR_COMBINE_FUNCTION_LOCAL) \ - XGR_COMBINE_FUNCTION(GR_COMBINE_FUNCTION_LOCAL_ALPHA) \ - XGR_COMBINE_FUNCTION(GR_COMBINE_FUNCTION_SCALE_OTHER) \ - XGR_COMBINE_FUNCTION(GR_COMBINE_FUNCTION_SCALE_OTHER_ADD_LOCAL) \ - XGR_COMBINE_FUNCTION(GR_COMBINE_FUNCTION_SCALE_OTHER_ADD_LOCAL_ALPHA) \ - XGR_COMBINE_FUNCTION(GR_COMBINE_FUNCTION_SCALE_OTHER_MINUS_LOCAL) \ - XGR_COMBINE_FUNCTION(GR_COMBINE_FUNCTION_SCALE_OTHER_MINUS_LOCAL_ADD_LOCAL) \ - XGR_COMBINE_FUNCTION(GR_COMBINE_FUNCTION_SCALE_OTHER_MINUS_LOCAL_ADD_LOCAL_ALPHA) \ - XGR_COMBINE_FUNCTION(GR_COMBINE_FUNCTION_SCALE_MINUS_LOCAL_ADD_LOCAL) \ - XGR_COMBINE_FUNCTION(GR_COMBINE_FUNCTION_SCALE_MINUS_LOCAL_ADD_LOCAL_ALPHA) - -#define XGR_COMBINE_LOCALS \ - XGR_COMBINE_LOCAL(GR_COMBINE_LOCAL_ITERATED) \ - XGR_COMBINE_LOCAL(GR_COMBINE_LOCAL_CONSTANT) \ - XGR_COMBINE_LOCAL(GR_COMBINE_LOCAL_DEPTH) - -#define XGR_COMBINE_OTHERS \ - XGR_COMBINE_OTHER(GR_COMBINE_OTHER_ITERATED) \ - XGR_COMBINE_OTHER(GR_COMBINE_OTHER_TEXTURE) \ - XGR_COMBINE_OTHER(GR_COMBINE_OTHER_CONSTANT) - - static const char* GetPrimitiveTypeString(FxU32 mode) - { - static const char* str[] = { - "GR_POINTS", - "GR_LINE_STRIP", - "GR_LINES", - "GR_POLYGON", - "GR_TRIANGLE_STRIP", - "GR_TRIANGLE_FAN", - "GR_TRIANGLES", - "GR_TRIANGLE_STRIP_CONTINUE", - "GR_TRIANGLE_FAN_CONTINUE" - }; - return str[mode]; - } - - static const char* GetAlphaBlendFncString(GrAlphaBlendFnc_t fnc) - { - static const char* str[16] = { - "GR_BLEND_ZERO", - "GR_BLEND_SRC_ALPHA", - "GR_BLEND_SRC_COLOR", - "GR_BLEND_DST_ALPHA", - "GR_BLEND_ONE", - "GR_BLEND_ONE_MINUS_SRC_ALPHA", - "GR_BLEND_ONE_MINUS_SRC_COLOR", - "GR_BLEND_ONE_MINUS_DST_ALPHA", - "GR_BLEND_RESERVED_8", - "GR_BLEND_RESERVED_9", - "GR_BLEND_RESERVED_A", - "GR_BLEND_RESERVED_B", - "GR_BLEND_RESERVED_C", - "GR_BLEND_RESERVED_D", - "GR_BLEND_RESERVED_E", - "GR_BLEND_ALPHA_SATURATE" - }; - return str[fnc]; - } - - static const char* GetVertexLayoutParamString(FxU32 param) - { - switch (param) { -#define XGR_PARAM(x) case x: return #x; - XGR_PARAMS -#undef XGR_PARAM - default: return ""; - } - } - - static const char* GetCombineLocalString(GrCombineLocal_t local) - { - switch (local) - { -#define XGR_COMBINE_LOCAL(x) case x: return #x; - XGR_COMBINE_LOCALS -#undef XGR_COMBINE_LOCAL - default: return ""; - } - } - - static const char* GetCombineOtherString(GrCombineOther_t other) - { - switch (other) - { -#define XGR_COMBINE_OTHER(x) case x: return #x; - XGR_COMBINE_OTHERS -#undef XGR_COMBINE_OTHER - default: return ""; - } - } - - static const char* GetCombineFactorString(GrCombineFactor_t factor) - { - switch (factor) - { -#define XGR_COMBINE_FACTOR(x) case x: return #x; - XGR_COMBINE_FACTORS -#undef XGR_COMBINE_FACTOR - default: return ""; - } - } - - static const char* GetCombineFunctionString(GrCombineFunction_t fnc) - { - switch (fnc) - { -#define XGR_COMBINE_FUNCTION(x) case x: return #x; - XGR_COMBINE_FUNCTIONS -#undef XGR_COMBINE_FUNCTION - default: return ""; - } - } - - static const char* GetStringForTextureFilterMode(GrTextureFilterMode_t fm) - { - switch (fm) { - case GR_TEXTUREFILTER_POINT_SAMPLED: - return "GR_TEXTUREFILTER_POINT_SAMPLED"; - case GR_TEXTUREFILTER_BILINEAR: - return "GR_TEXTUREFILTER_BILINEAR"; - default: - return ""; - } - } - - static const char* GetTextureFormatString(GrTextureFormat_t fmt) - { - static const char* str[16] = { - "GR_TEXFMT_8BIT", - "GR_TEXFMT_YIQ_422", - "GR_TEXFMT_ALPHA_8", - "GR_TEXFMT_INTENSITY_8", - "GR_TEXFMT_ALPHA_INTENSITY_44", - "GR_TEXFMT_P_8", - "GR_TEXFMT_RSVD0", - "GR_TEXFMT_RSVD1", - "GR_TEXFMT_16BIT", - "GR_TEXFMT_AYIQ_8422", - "GR_TEXFMT_RGB_565", - "GR_TEXFMT_ARGB_1555", - "GR_TEXFMT_ARGB_4444", - "GR_TEXFMT_ALPHA_INTENSITY_88", - "GR_TEXFMT_AP_88", - "GR_TEXFMT_RSVD2" - }; - return str[fmt]; - } - - static const char* GetCoordinateSpaceModeString(GrCoordinateSpaceMode_t mode) - { - if (mode == GR_WINDOW_COORDS) return "GR_WINDOW_COORDS"; - if (mode == GR_CLIP_COORDS) return "GR_CLIP_COORDS"; - return ""; - } - - static void GetWidthHeightFromTexInfo(const GrTexInfo* info, FxU32* w, FxU32* h) - { - FxU32 ww = 1 << info->largeLodLog2; - switch (info->aspectRatioLog2) - { - case GR_ASPECT_LOG2_1x1: - *w = ww; - *h = ww; - break; - case GR_ASPECT_LOG2_1x2: - *w = ww / 2; - *h = ww; - break; - case GR_ASPECT_LOG2_2x1: - *w = ww; - *h = ww / 2; - break; - case GR_ASPECT_LOG2_1x4: - *w = ww / 4; - *h = ww; - break; - case GR_ASPECT_LOG2_4x1: - *w = ww; - *h = ww / 4; - break; - case GR_ASPECT_LOG2_1x8: - *w = ww / 8; - *h = ww; - break; - case GR_ASPECT_LOG2_8x1: - *w = ww; - *h = ww / 8; - break; - default: - *w = 0; - *h = 0; - break; - } - } - - static uint32_t GetTextureMemRequired(GrTexInfo* info) - { - FxU32 width, height; - GetWidthHeightFromTexInfo(info, &width, &height); - assert(info->format == GR_TEXFMT_P_8); - return width * height; - } - - static PrimitiveType GetPrimitiveType(uint32_t mode) - { - switch (mode) - { - case GR_TRIANGLES: - case GR_TRIANGLE_FAN: - case GR_TRIANGLE_STRIP: - case GR_TRIANGLE_FAN_CONTINUE: - case GR_TRIANGLE_STRIP_CONTINUE: - return PrimitiveType::Triangles; - case GR_LINES: - return PrimitiveType::Lines; - case GR_POINTS: - return PrimitiveType::Points; - default: - assert(false && "Unhandled primitive type."); - return PrimitiveType::Points; - } - } -} diff --git a/src/d2dx/D2DXIntegrationImpl.cpp b/src/d2dx/IBuiltinResMod.h similarity index 55% rename from src/d2dx/D2DXIntegrationImpl.cpp rename to src/d2dx/IBuiltinResMod.h index 13dbdd9..2642ace 100644 --- a/src/d2dx/D2DXIntegrationImpl.cpp +++ b/src/d2dx/IBuiltinResMod.h @@ -16,29 +16,16 @@ You should have received a copy of the GNU General Public License along with D2DX. If not, see . */ -#include "pch.h" -#include "D2DXContext.h" +#pragma once -using namespace d2dx; -using namespace std; +#include "Types.h" -extern unique_ptr g_d2dxContext; - -extern "C" +namespace d2dx { - __declspec(dllexport) void D2DX_SetCustomResolution(int32_t width, int32_t height) + struct IBuiltinResMod abstract { - if (g_d2dxContext) - { - g_d2dxContext->SetCustomResolution(width, height); - } - } + virtual ~IBuiltinResMod() noexcept {} - __declspec(dllexport) void D2DX_GetSuggestedCustomResolution(int32_t* width, int32_t* height) - { - if (g_d2dxContext) - { - g_d2dxContext->GetSuggestedCustomResolution(width, height); - } - } + virtual bool IsActive() const = 0; + }; } diff --git a/src/d2dx/ID2DXContext.h b/src/d2dx/ID2DXContext.h new file mode 100644 index 0000000..edcf3d9 --- /dev/null +++ b/src/d2dx/ID2DXContext.h @@ -0,0 +1,56 @@ +/* + This file is part of D2DX. + + Copyright (C) 2021 Bolrog + + D2DX is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + D2DX is distributed in the hope that it will be 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 D2DX. If not, see . +*/ +#pragma once + +#include "IGlide3x.h" +#include "IWin32InterceptionHandler.h" +#include "ID2InterceptionHandler.h" +#include "Options.h" + +namespace d2dx +{ + enum class Feature + { + UnitMotionPrediction = 1, + WeatherMotionPrediction = 2, + TextMotionPrediction = 4, + }; + + struct ID2DXContext abstract : + public IGlide3x, + public IWin32InterceptionHandler, + public ID2InterceptionHandler + { + virtual ~ID2DXContext() noexcept {} + + virtual void SetCustomResolution( + _In_ Size size) = 0; + + virtual Size GetSuggestedCustomResolution() = 0; + + virtual GameVersion GetGameVersion() const = 0; + + virtual void DisableBuiltinResMod() = 0; + + virtual const Options& GetOptions() const = 0; + + virtual bool IsFeatureEnabled( + _In_ Feature feature) = 0; + }; +} diff --git a/src/d2dx/ID2InterceptionHandler.h b/src/d2dx/ID2InterceptionHandler.h new file mode 100644 index 0000000..b01a8ba --- /dev/null +++ b/src/d2dx/ID2InterceptionHandler.h @@ -0,0 +1,47 @@ +/* + This file is part of D2DX. + + Copyright (C) 2021 Bolrog + + D2DX is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + D2DX is distributed in the hope that it will be 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 D2DX. If not, see . +*/ +#pragma once + +#include "IGameHelper.h" +#include "Types.h" +#include "D2Types.h" + +namespace d2dx +{ + struct ID2InterceptionHandler abstract + { + virtual ~ID2InterceptionHandler() noexcept {} + + virtual Offset BeginDrawText( + _Inout_z_ wchar_t* str, + _In_ Offset pos, + _In_ uint32_t returnAddress, + _In_ D2Function d2Function) = 0; + + virtual void EndDrawText() = 0; + + virtual Offset BeginDrawImage( + _In_ const D2::CellContext* cellContext, + _In_ uint32_t drawMode, + _In_ Offset pos, + _In_ D2Function d2Function) = 0; + + virtual void EndDrawImage() = 0; + }; +} diff --git a/src/d2dx/IGameHelper.h b/src/d2dx/IGameHelper.h new file mode 100644 index 0000000..82b3476 --- /dev/null +++ b/src/d2dx/IGameHelper.h @@ -0,0 +1,109 @@ +/* + This file is part of D2DX. + + Copyright (C) 2021 Bolrog + + D2DX is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + D2DX is distributed in the hope that it will be 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 D2DX. If not, see . +*/ +#pragma once + +#include "Types.h" +#include "D2Types.h" + +namespace d2dx +{ + enum class D2Function + { + D2Gfx_DrawImage = 0, /* call dword ptr [eax+84h] */ + D2Gfx_DrawShiftedImage = 1, /* call dword ptr [eax+88h] */ + D2Gfx_DrawVerticalCropImage = 2, /* call dword ptr [eax+8Ch] */ + D2Gfx_DrawClippedImage = 3, /* call dword ptr [eax+98h] */ + D2Gfx_DrawImageFast = 4, /* call dword ptr [eax+94h] */ + D2Gfx_DrawShadow = 5, /* call dword ptr [eax+90h] */ + D2Win_DrawText = 6, /* mov ebx, [esp+4+arg_8] */ + D2Win_DrawTextEx = 7, + D2Win_DrawFramedText = 8, + D2Win_DrawRectangledText = 9, /* setle cl */ + D2Client_DrawUnit = 10, /* push 52Bh */ + D2Client_DrawMissile = 11, + D2Client_DrawWeatherParticles = 12, + D2Client_FindClientSideUnit = 13, + D2Client_FindServerSideUnit = 14, + }; + + struct DrawParameters + { + uint32_t unitId; + uint32_t unitType; + uint32_t unitToken; + }; + + struct IGameHelper abstract + { + virtual ~IGameHelper() noexcept {} + + virtual GameVersion GetVersion() const = 0; + + virtual _Ret_z_ const char* GetVersionString() const = 0; + + virtual uint32_t ScreenOpenMode() const = 0; + + virtual Size GetConfiguredGameSize() const = 0; + + virtual GameAddress IdentifyGameAddress( + _In_ uint32_t returnAddress) const = 0; + + virtual TextureCategory GetTextureCategoryFromHash( + _In_ uint32_t textureHash) const = 0; + + virtual TextureCategory RefineTextureCategoryFromGameAddress( + _In_ TextureCategory previousCategory, + _In_ GameAddress gameAddress) const = 0; + + virtual bool TryApplyInGameFpsFix() = 0; + + virtual bool TryApplyMenuFpsFix() = 0; + + virtual bool TryApplyInGameSleepFixes() = 0; + + virtual void* GetFunction( + _In_ D2Function function) const = 0; + + virtual DrawParameters GetDrawParameters( + _In_ const D2::CellContext* cellContext) const = 0; + + virtual D2::UnitAny* GetPlayerUnit() const = 0; + + virtual Offset GetUnitPos( + _In_ const D2::UnitAny* unit) const = 0; + + virtual D2::UnitType GetUnitType( + _In_ const D2::UnitAny* unit) const = 0; + + virtual uint32_t GetUnitId( + _In_ const D2::UnitAny* unit) const = 0; + + virtual D2::UnitAny* FindUnit( + _In_ uint32_t unitId, + _In_ D2::UnitType unitType) const = 0; + + virtual int32_t GetCurrentAct() const = 0; + + virtual bool IsGameMenuOpen() const = 0; + + virtual bool IsInGame() const = 0; + + virtual bool IsProjectDiablo2() const = 0; + }; +} diff --git a/src/d2dx/IGlide3x.h b/src/d2dx/IGlide3x.h new file mode 100644 index 0000000..44238ee --- /dev/null +++ b/src/d2dx/IGlide3x.h @@ -0,0 +1,130 @@ +/* + This file is part of D2DX. + + Copyright (C) 2021 Bolrog + + D2DX is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + D2DX is distributed in the hope that it will be 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 D2DX. If not, see . +*/ +#pragma once + +#include "Types.h" + +namespace d2dx +{ + struct IGlide3x abstract + { + virtual ~IGlide3x() noexcept {} + + virtual const char* OnGetString( + _In_ uint32_t pname) = 0; + + virtual uint32_t OnGet( + _In_ uint32_t pname, + _In_ uint32_t plength, + _Out_writes_(plength) int32_t* params) = 0; + + virtual void OnSstWinOpen( + _In_ uint32_t hWnd, + _In_ int32_t width, + _In_ int32_t height) = 0; + + virtual void OnVertexLayout( + _In_ uint32_t param, + _In_ int32_t offset) = 0; + + virtual void OnTexDownload( + _In_ uint32_t tmu, + _In_reads_(width* height) const uint8_t* sourceAddress, + _In_ uint32_t startAddress, + _In_ int32_t width, + _In_ int32_t height) = 0; + + virtual void OnTexSource( + _In_ uint32_t tmu, + _In_ uint32_t startAddress, + _In_ int32_t width, + _In_ int32_t height) = 0; + + virtual void OnConstantColorValue( + _In_ uint32_t color) = 0; + + virtual void OnAlphaBlendFunction( + _In_ GrAlphaBlendFnc_t rgb_sf, + _In_ GrAlphaBlendFnc_t rgb_df, + _In_ GrAlphaBlendFnc_t alpha_sf, + _In_ GrAlphaBlendFnc_t alpha_df) = 0; + + virtual void OnColorCombine( + _In_ GrCombineFunction_t function, + _In_ GrCombineFactor_t factor, + _In_ GrCombineLocal_t local, + _In_ GrCombineOther_t other, + _In_ bool invert) = 0; + + virtual void OnAlphaCombine( + _In_ GrCombineFunction_t function, + _In_ GrCombineFactor_t factor, + _In_ GrCombineLocal_t local, + _In_ GrCombineOther_t other, + _In_ bool invert) = 0; + + virtual void OnDrawPoint( + _In_ const void* pt, + _In_ uint32_t gameContext) = 0; + + virtual void OnDrawLine( + _In_ const void* v1, + _In_ const void* v2, + _In_ uint32_t gameContext) = 0; + + virtual void OnDrawVertexArray( + _In_ uint32_t mode, + _In_ uint32_t count, + _In_reads_(count) uint8_t** pointers, + _In_ uint32_t gameContext) = 0; + + virtual void OnDrawVertexArrayContiguous( + _In_ uint32_t mode, + _In_ uint32_t count, + _In_reads_(count* stride) uint8_t* vertex, + _In_ uint32_t stride, + _In_ uint32_t gameContext) = 0; + + virtual void OnTexDownloadTable( + _In_ GrTexTable_t type, + _In_reads_bytes_(256 * 4) void* data) = 0; + + virtual void OnLoadGammaTable( + _In_ uint32_t nentries, + _In_reads_(nentries) uint32_t* red, + _In_reads_(nentries) uint32_t* green, + _In_reads_(nentries) uint32_t* blue) = 0; + + virtual void OnChromakeyMode( + _In_ GrChromakeyMode_t mode) = 0; + + virtual void OnLfbUnlock( + _In_reads_bytes_(strideInBytes * 480) const uint32_t* lfbPtr, + _In_ uint32_t strideInBytes) = 0; + + virtual void OnGammaCorrectionRGB( + _In_ float red, + _In_ float green, + _In_ float blue) = 0; + + virtual void OnBufferSwap() = 0; + + virtual void OnBufferClear() = 0; + }; +} diff --git a/src/d2dx/IRenderContext.h b/src/d2dx/IRenderContext.h new file mode 100644 index 0000000..515ce1e --- /dev/null +++ b/src/d2dx/IRenderContext.h @@ -0,0 +1,86 @@ +/* + This file is part of D2DX. + + Copyright (C) 2021 Bolrog + + D2DX is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + D2DX is distributed in the hope that it will be 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 D2DX. If not, see . +*/ +#pragma once + +#include "ITextureCache.h" +#include "Types.h" +#include "Options.h" + +namespace d2dx +{ + class Vertex; + class Batch; + + struct IRenderContext abstract + { + virtual ~IRenderContext() noexcept {} + + virtual HWND GetHWnd() const = 0; + + virtual void LoadGammaTable( + _In_reads_(valueCount) const uint32_t* values, + _In_ uint32_t valueCount) = 0; + + virtual uint32_t BulkWriteVertices( + _In_reads_(vertexCount) const Vertex* vertices, + _In_ uint32_t vertexCount) = 0; + + virtual TextureCacheLocation UpdateTexture( + _In_ const Batch& batch, + _In_reads_(tmuDataSize) const uint8_t* tmuData, + _In_ uint32_t tmuDataSize) = 0; + + virtual void Draw( + _In_ const Batch& batch, + _In_ uint32_t startVertexLocation) = 0; + + virtual void Present() = 0; + + virtual void WriteToScreen( + _In_reads_(width * height) const uint32_t* pixels, + _In_ int32_t width, + _In_ int32_t height) = 0; + + virtual void SetPalette( + _In_ int32_t paletteIndex, + _In_reads_(256) const uint32_t* palette) = 0; + + virtual const Options& GetOptions() const = 0; + + virtual ITextureCache* GetTextureCache( + _In_ const Batch& batch) const = 0; + + virtual void SetSizes( + _In_ Size gameSize, + _In_ Size windowSize) = 0; + + virtual void GetCurrentMetrics( + _Out_opt_ Size* gameSize, + _Out_opt_ Rect* renderRect, + _Out_opt_ Size* desktopSize) const = 0; + + virtual void ToggleFullscreen() = 0; + + virtual float GetFrameTime() const = 0; + + virtual int32_t GetFrameTimeFp() const = 0; + + virtual ScreenMode GetScreenMode() const = 0; + }; +} diff --git a/src/d2dx/TextureCachePolicy.h b/src/d2dx/ISimd.h similarity index 74% rename from src/d2dx/TextureCachePolicy.h rename to src/d2dx/ISimd.h index 60892d3..ed2f451 100644 --- a/src/d2dx/TextureCachePolicy.h +++ b/src/d2dx/ISimd.h @@ -18,15 +18,17 @@ */ #pragma once +#include "Utils.h" + namespace d2dx { - class TextureCachePolicy abstract + struct ISimd abstract { - public: - virtual ~TextureCachePolicy() {} + virtual ~ISimd() noexcept {} - virtual int32_t Find(uint32_t contentKey, int32_t lastIndex) = 0; - virtual int32_t Insert(uint32_t contentKey, bool& evicted) = 0; - virtual void OnNewFrame() = 0; + virtual int32_t IndexOfUInt32( + _In_reads_(itemsCount) const uint32_t* __restrict items, + _In_ uint32_t itemsCount, + _In_ uint32_t item) = 0; }; } diff --git a/src/d2dx/ITextureCache.h b/src/d2dx/ITextureCache.h new file mode 100644 index 0000000..6ca8289 --- /dev/null +++ b/src/d2dx/ITextureCache.h @@ -0,0 +1,59 @@ +/* + This file is part of D2DX. + + Copyright (C) 2021 Bolrog + + D2DX is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + D2DX is distributed in the hope that it will be 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 D2DX. If not, see . +*/ +#pragma once + +#include "ISimd.h" +#include "Types.h" + +namespace d2dx +{ + class Batch; + + struct TextureCacheLocation final + { + int16_t _textureAtlas; + int16_t _textureIndex; + }; + + static_assert(sizeof(TextureCacheLocation) == 4, "sizeof(TextureCacheLocation) == 4"); + + struct ITextureCache abstract + { + virtual ~ITextureCache() noexcept {} + + virtual void OnNewFrame() = 0; + + virtual TextureCacheLocation FindTexture( + _In_ uint32_t contentKey, + _In_ int32_t lastIndex) = 0; + + virtual TextureCacheLocation InsertTexture( + _In_ uint32_t contentKey, + _In_ const Batch& batch, + _In_reads_(tmuDataSize) const uint8_t* tmuData, + _In_ uint32_t tmuDataSize) = 0; + + virtual ID3D11ShaderResourceView* GetSrv( + _In_ uint32_t atlasIndex) const = 0; + + virtual uint32_t GetMemoryFootprint() const = 0; + + virtual uint32_t GetUsedCount() const = 0; + }; +} diff --git a/src/d2dx/IWin32InterceptionHandler.h b/src/d2dx/IWin32InterceptionHandler.h new file mode 100644 index 0000000..089be42 --- /dev/null +++ b/src/d2dx/IWin32InterceptionHandler.h @@ -0,0 +1,38 @@ +/* + This file is part of D2DX. + + Copyright (C) 2021 Bolrog + + D2DX is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + D2DX is distributed in the hope that it will be 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 D2DX. If not, see . +*/ +#pragma once + +#include "Types.h" + +namespace d2dx +{ + struct IWin32InterceptionHandler abstract + { + virtual ~IWin32InterceptionHandler() noexcept {} + + virtual Offset OnSetCursorPos( + _In_ Offset pos) = 0; + + virtual Offset OnMouseMoveMessage( + _In_ Offset pos) = 0; + + virtual int32_t OnSleep( + _In_ int32_t ms) = 0; + }; +} diff --git a/src/d2dx/Metrics.cpp b/src/d2dx/Metrics.cpp new file mode 100644 index 0000000..4183adf --- /dev/null +++ b/src/d2dx/Metrics.cpp @@ -0,0 +1,249 @@ +#include "pch.h" +#include "Metrics.h" + +using namespace d2dx; +using namespace DirectX; + +struct MetricsTableEntry final +{ + Size desktopSize; + Size gameSize; + Size gameSizeWide; +}; + +static const struct MetricsTableEntry metricsTable[] = +{ + { + .desktopSize { 1280, 720 }, + .gameSize { 960, 720 }, + .gameSizeWide { 1280, 720 }, + }, + { + .desktopSize { 1360, 768 }, + .gameSize { 1024, 768 }, + .gameSizeWide { 1360, 768 }, + }, + { + .desktopSize { 1366, 768 }, + .gameSize { 1024, 768 }, + .gameSizeWide { 1366, 768 }, + }, + { + .desktopSize { 1280, 800 }, + .gameSize { 1066, 800 }, + .gameSizeWide { 1280, 800 }, + }, + { + .desktopSize { 1536, 864 }, + .gameSize { 1152, 864 }, + .gameSizeWide { 1536, 864 }, + }, + { + .desktopSize { 1440, 900 }, + .gameSize { 1200, 900 }, + .gameSizeWide { 1440, 900 }, + }, + { + .desktopSize { 1600, 900 }, + .gameSize { 1200, 900 }, + .gameSizeWide { 1600, 900 }, + }, + { + .desktopSize { 1280, 1024 }, + .gameSize { 640, 480 }, + .gameSizeWide { 640, 512 }, + }, + { + .desktopSize { 1400, 1050 }, + .gameSize { 700, 525 }, + .gameSizeWide { 700, 525 }, + }, + { + .desktopSize { 1680, 1050 }, + .gameSize { 700, 525 }, + .gameSizeWide { 840, 525 }, + }, + { + .desktopSize { 1920, 1080 }, + .gameSize { 720, 540 }, + .gameSizeWide { 960, 540 }, + }, + { + .desktopSize { 2560, 1080 }, + .gameSize { 720, 540 }, + .gameSizeWide { 960, 540 }, /* note: doesn't cover entire monitor */ + }, + { + .desktopSize { 2048, 1152 }, + .gameSize { 768, 576 }, + .gameSizeWide { 1024, 576 }, + }, + { + .desktopSize { 1600, 1200 }, + .gameSize { 800, 600 }, + .gameSizeWide { 800, 600 }, + }, + { + .desktopSize { 1920, 1200 }, + .gameSize { 800, 600 }, + .gameSizeWide { 960, 600 }, + }, + { + .desktopSize { 2560, 1440 }, + .gameSize { 960, 720 }, + .gameSizeWide { 1280, 720 }, + }, + { + .desktopSize { 3440, 1440 }, + .gameSize { 960, 720 }, + .gameSizeWide { 1280, 720 }, /* note: doesn't cover entire monitor */ + }, + { + .desktopSize { 2048, 1536 }, + .gameSize { 1024, 768 }, + .gameSizeWide { 1024, 768 }, + }, + { + .desktopSize { 2560, 1600 }, + .gameSize { 710, 532 }, + .gameSizeWide { 852, 532 }, + }, + { + .desktopSize { 2560, 2048 }, + .gameSize { 852, 640 }, + .gameSizeWide { 852, 682 }, + }, + { + .desktopSize { 3200, 2048 }, + .gameSize { 852, 640 }, + .gameSizeWide { 1066, 682 }, + }, + { + .desktopSize { 3840, 2160 }, + .gameSize { 960, 720 }, + .gameSizeWide { 1280, 720 }, + }, + { + .desktopSize { 4096, 2160 }, + .gameSize { 960, 720 }, + .gameSizeWide { 1364, 720 }, + }, + { + .desktopSize { 4320, 2160 }, + .gameSize { 960, 720 }, + .gameSizeWide { 1440, 720 }, + }, + { + .desktopSize { 3200, 2400 }, + .gameSize { 800, 600 }, + .gameSizeWide { 800, 600 }, + }, + { + .desktopSize { 3840, 2400 }, + .gameSize { 800, 600 }, + .gameSizeWide { 960, 600 }, + }, +}; + +_Use_decl_annotations_ +Size d2dx::Metrics::GetSuggestedGameSize( + Size desktopSize, + bool wide) noexcept +{ + for (int32_t i = 0; i < ARRAYSIZE(metricsTable); ++i) + { + if (metricsTable[i].desktopSize.width == desktopSize.width && + metricsTable[i].desktopSize.height == desktopSize.height) + { + return wide ? metricsTable[i].gameSizeWide : metricsTable[i].gameSize; + } + } + + Size size = { 0, 0 }; + for (int32_t scaleFactor = 1; scaleFactor <= 8; ++scaleFactor) + { + size.width = desktopSize.width / scaleFactor; + size.height = desktopSize.height / scaleFactor; + if ((desktopSize.height / (scaleFactor + 1)) <= 600) + { + break; + } + } + + if (size.height > 720) + { + const float aspect = (float)desktopSize.width / desktopSize.height; + size.width = (int32_t)(aspect * 720); + size.height = 720; + } + + return size; +} + +_Use_decl_annotations_ +Rect d2dx::Metrics::GetRenderRect( + Size gameSize, + Size desktopSize, + bool wide) noexcept +{ + int32_t scaleFactor = 1; + + while ( + gameSize.width * (scaleFactor + 1) <= desktopSize.width && + gameSize.height * (scaleFactor + 1) <= desktopSize.height) + { + ++scaleFactor; + } + + Rect rect + { + (desktopSize.width - gameSize.width * scaleFactor) / 2, + (desktopSize.height - gameSize.height * scaleFactor) / 2, + gameSize.width * scaleFactor, + gameSize.height * scaleFactor + }; + + /* Allow for a small amount of black margin on all sides. When more than that, + rescale the image with a non-integer factor. */ + if (rect.offset.x < 0 || rect.offset.y < 0 || (rect.offset.x >= 16 && rect.offset.y >= 16)) + { + float scaleFactorF = (float)desktopSize.width / rect.size.width; + int32_t scaledHeight = (int32_t)(rect.size.height * scaleFactorF); + rect.offset.x = 0; + rect.offset.y = (desktopSize.height - scaledHeight) / 2; + rect.size.width = desktopSize.width; + rect.size.height = scaledHeight; + + if (rect.offset.x < 0 || rect.offset.y < 0) + { + float scaleFactorF = (float)desktopSize.height / rect.size.height; + int32_t scaledWidth = (int32_t)(rect.size.width * scaleFactorF); + rect.offset.x = (desktopSize.width - scaledWidth) / 2; + rect.offset.y = 0; + rect.size.width = scaledWidth; + rect.size.height = desktopSize.height; + } + } + + assert( + rect.offset.x >= 0 && + rect.offset.y >= 0 && + rect.size.width > 0 && + rect.size.height > 0 && + (rect.offset.x + rect.size.width) <= desktopSize.width && + (rect.offset.y + rect.size.height) <= desktopSize.height); + + return rect; +} + +Buffer d2dx::Metrics::GetStandardDesktopSizes() noexcept +{ + Buffer standardDesktopSizes(ARRAYSIZE(metricsTable)); + + for (int32_t i = 0; i < ARRAYSIZE(metricsTable); ++i) + { + standardDesktopSizes.items[i] = metricsTable[i].desktopSize; + } + + return standardDesktopSizes; +} diff --git a/src/d2dx/Metrics.h b/src/d2dx/Metrics.h new file mode 100644 index 0000000..b30de5b --- /dev/null +++ b/src/d2dx/Metrics.h @@ -0,0 +1,21 @@ +#pragma once + +#include "Buffer.h" +#include "Types.h" + +namespace d2dx +{ + namespace Metrics + { + Size GetSuggestedGameSize( + _In_ Size desktopSize, + _In_ bool wide) noexcept; + + Rect GetRenderRect( + _In_ Size gameSize, + _In_ Size desktopSize, + _In_ bool wide) noexcept; + + Buffer GetStandardDesktopSizes() noexcept; + } +} diff --git a/src/d2dx/Options.cpp b/src/d2dx/Options.cpp new file mode 100644 index 0000000..26adff1 --- /dev/null +++ b/src/d2dx/Options.cpp @@ -0,0 +1,229 @@ +/* + This file is part of D2DX. + + Copyright (C) 2021 Bolrog + + D2DX is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + D2DX is distributed in the hope that it will be 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 D2DX. If not, see . +*/ +#include "pch.h" +#include "Options.h" +#include "Buffer.h" +#include "Utils.h" + +#include "../../thirdparty/toml/toml.h" + +using namespace d2dx; + +Options::Options() +{ +} + +Options::~Options() noexcept +{ +} + +_Use_decl_annotations_ +void Options::ApplyCfg( + const char* cfg) +{ + auto cfgLen = strlen(cfg); + + if (cfgLen > 65536) + { + D2DX_FATAL_ERROR("Configuration file size limit exceeded."); + } + + Buffer cfgTemp{ cfgLen + 1, true }; + Buffer errorMsg{ 1024, true }; + + strcpy_s(cfgTemp.items, cfgTemp.capacity, cfg); + + auto root = toml_parse(cfgTemp.items, errorMsg.items, errorMsg.capacity); + + if (!root) + { + return; + } + + toml_table_t* optouts = toml_table_in(root, "optouts"); + + if (optouts) + { + toml_datum_t datum; + +#define READ_OPTOUTS_FLAG(flag, cfgStringName) \ + datum = toml_bool_in(optouts, cfgStringName); \ + if (datum.ok) \ + { \ + SetFlag(flag, datum.u.b); \ + } + + READ_OPTOUTS_FLAG(OptionsFlag::NoClipCursor, "noclipcursor"); + READ_OPTOUTS_FLAG(OptionsFlag::NoFpsFix, "nofpsfix"); + READ_OPTOUTS_FLAG(OptionsFlag::NoResMod, "noresmod"); + READ_OPTOUTS_FLAG(OptionsFlag::NoWide, "nowide"); + READ_OPTOUTS_FLAG(OptionsFlag::NoLogo, "nologo"); + READ_OPTOUTS_FLAG(OptionsFlag::NoVSync, "novsync"); + READ_OPTOUTS_FLAG(OptionsFlag::NoAntiAliasing, "noaa"); + READ_OPTOUTS_FLAG(OptionsFlag::NoCompatModeFix, "nocompatmodefix"); + READ_OPTOUTS_FLAG(OptionsFlag::NoTitleChange, "notitlechange"); + READ_OPTOUTS_FLAG(OptionsFlag::NoMotionPrediction, "nomotionprediction"); + +#undef READ_OPTOUTS_FLAG + } + + auto game = toml_table_in(root, "game"); + + if (game) + { + auto gameSize = toml_array_in(game, "size"); + if (gameSize) + { + auto w = toml_int_at(gameSize, 0); + auto h = toml_int_at(gameSize, 1); + + if (w.ok && h.ok) + { + SetUserSpecifiedGameSize({ (int32_t)w.u.i, (int32_t)h.u.i }); + } + } + + auto filtering = toml_int_in(game, "filtering"); + if (filtering.ok) + { + _filtering = (FilteringOption)filtering.u.i; + } + } + + auto window = toml_table_in(root, "window"); + + if (window) + { + auto windowScale = toml_int_in(window, "scale"); + if (windowScale.ok) + { + SetWindowScale((int32_t)windowScale.u.i); + } + + auto windowPosition = toml_array_in(window, "position"); + if (windowPosition) + { + auto x = toml_int_at(windowPosition, 0); + auto y = toml_int_at(windowPosition, 1); + + if (x.ok && y.ok) + { + SetWindowPosition({ (int32_t)x.u.i, (int32_t)y.u.i }); + } + } + + auto frameless = toml_bool_in(window, "frameless"); + if (frameless.ok) + { + SetFlag(OptionsFlag::Frameless, frameless.u.b); + } + } + + auto debug = toml_table_in(root, "debug"); + + if (debug) + { + auto dumpTextures = toml_bool_in(debug, "dumptextures"); + if (dumpTextures.ok) + { + SetFlag(OptionsFlag::DbgDumpTextures, dumpTextures.u.b); + } + } + + toml_free(root); +} + +_Use_decl_annotations_ +void Options::ApplyCommandLine( + const char* cmdLine) +{ + if (strstr(cmdLine, "-dxnoclipcursor")) SetFlag(OptionsFlag::NoClipCursor, true); + if (strstr(cmdLine, "-dxnofpsfix")) SetFlag(OptionsFlag::NoFpsFix, true); + if (strstr(cmdLine, "-dxnoresmod")) SetFlag(OptionsFlag::NoResMod, true); + if (strstr(cmdLine, "-dxnowide")) SetFlag(OptionsFlag::NoWide, true); + if (strstr(cmdLine, "-dxnologo")) SetFlag(OptionsFlag::NoLogo, true); + if (strstr(cmdLine, "-dxnovsync")) SetFlag(OptionsFlag::NoVSync, true); + if (strstr(cmdLine, "-dxnoaa")) SetFlag(OptionsFlag::NoAntiAliasing, true); + if (strstr(cmdLine, "-dxnocompatmodefix")) SetFlag(OptionsFlag::NoCompatModeFix, true); + if (strstr(cmdLine, "-dxnotitlechange")) SetFlag(OptionsFlag::NoTitleChange, true); + if (strstr(cmdLine, "-dxnomop")) SetFlag(OptionsFlag::NoMotionPrediction, true); + + if (strstr(cmdLine, "-dxscale3")) SetWindowScale(3); + else if (strstr(cmdLine, "-dxscale2")) SetWindowScale(2); + + if (strstr(cmdLine, "-dxdbg_dump_textures")) SetFlag(OptionsFlag::DbgDumpTextures, true); +} + +_Use_decl_annotations_ +bool Options::GetFlag( + OptionsFlag flag) const +{ + return (_flags & (1 << (int32_t)flag)) != 0; +} + +_Use_decl_annotations_ +void Options::SetFlag( + OptionsFlag flag, + bool value) +{ + uint32_t mask = 1 << (int32_t)flag; + _flags &= ~mask; + if (value) + { + _flags |= mask; + } +} + +int32_t Options::GetWindowScale() const +{ + return _windowScale; +} + +void Options::SetWindowScale( + _In_ int32_t windowScale) +{ + _windowScale = min(3, max(1, windowScale)); +} + +Offset Options::GetWindowPosition() const +{ + return _windowPosition; +} + +void Options::SetWindowPosition( + _In_ Offset windowPosition) +{ + _windowPosition = { max(-1, windowPosition.x), max(-1, windowPosition.y) }; +} + +Size Options::GetUserSpecifiedGameSize() const +{ + return _userSpecifiedGameSize; +} + +void Options::SetUserSpecifiedGameSize( + _In_ Size size) +{ + _userSpecifiedGameSize = { max(-1, size.width), max(-1, size.height) }; +} + +FilteringOption Options::GetFiltering() const +{ + return _filtering; +} diff --git a/src/d2dx/Options.h b/src/d2dx/Options.h new file mode 100644 index 0000000..9521663 --- /dev/null +++ b/src/d2dx/Options.h @@ -0,0 +1,96 @@ +/* + This file is part of D2DX. + + Copyright (C) 2021 Bolrog + + D2DX is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + D2DX is distributed in the hope that it will be 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 D2DX. If not, see . +*/ +#pragma once + +#include "Types.h" + +namespace d2dx +{ + enum class OptionsFlag + { + NoClipCursor, + NoFpsFix, + NoResMod, + NoWide, + NoLogo, + NoAntiAliasing, + NoCompatModeFix, + NoTitleChange, + NoVSync, + NoMotionPrediction, + + DbgDumpTextures, + + Frameless, + + Count + }; + + enum class FilteringOption + { + HighQuality = 0, + Bilinear = 1, + CatmullRom = 2, + Count = 3 + }; + + class Options final + { + public: + Options(); + ~Options() noexcept; + + void ApplyCfg( + _In_z_ const char* cfg); + + void ApplyCommandLine( + _In_z_ const char* cmdLine); + + bool GetFlag( + _In_ OptionsFlag flag) const; + + void SetFlag( + _In_ OptionsFlag flag, + _In_ bool value); + + int32_t GetWindowScale() const; + + void SetWindowScale( + _In_ int32_t zoomLevel); + + Offset GetWindowPosition() const; + + void SetWindowPosition( + _In_ Offset windowPosition); + + Size GetUserSpecifiedGameSize() const; + + void SetUserSpecifiedGameSize( + _In_ Size size); + + FilteringOption GetFiltering() const; + + private: + uint32_t _flags = 0; + int32_t _windowScale = 1; + Offset _windowPosition{ -1, -1 }; + Size _userSpecifiedGameSize{ -1, -1 }; + FilteringOption _filtering{ FilteringOption::HighQuality }; + }; +} \ No newline at end of file diff --git a/src/d2dx/RenderContext.cpp b/src/d2dx/RenderContext.cpp new file mode 100644 index 0000000..9beb040 --- /dev/null +++ b/src/d2dx/RenderContext.cpp @@ -0,0 +1,1055 @@ +/* + This file is part of D2DX. + + Copyright (C) 2021 Bolrog + + D2DX is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + D2DX is distributed in the hope that it will be 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 D2DX. If not, see . +*/ +#include "pch.h" +#include "Batch.h" +#include "D2DXContextFactory.h" +#include "RenderContext.h" +#include "Metrics.h" +#include "TextureCache.h" +#include "Vertex.h" +#include "Utils.h" + +#define MAX_FRAME_LATENCY 1 +#undef ALLOW_SET_SOURCE_SIZE + +using namespace d2dx; +using namespace std; + +extern int (WINAPI* ShowCursor_Real)( + _In_ BOOL bShow); + +extern BOOL(WINAPI* SetWindowPos_Real)( + _In_ HWND hWnd, + _In_opt_ HWND hWndInsertAfter, + _In_ int X, + _In_ int Y, + _In_ int cx, + _In_ int cy, + _In_ UINT uFlags); + +static LRESULT CALLBACK d2dxSubclassWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, + LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData); + +_Use_decl_annotations_ +RenderContext::RenderContext( + HWND hWnd, + Size gameSize, + Size windowSize, + ScreenMode initialScreenMode, + ID2DXContext* d2dxContext, + const std::shared_ptr& simd) +{ + HRESULT hr = S_OK; + + _screenMode = initialScreenMode; + + _timeStart = TimeStart(); + + _hWnd = hWnd; + _d2dxContext = d2dxContext; + _simd = simd; + + memset(&_shadowState, 0, sizeof(_shadowState)); + + _desktopSize = { GetSystemMetrics(SM_CXSCREEN), GetSystemMetrics(SM_CYSCREEN) }; + _desktopClientMaxHeight = GetSystemMetrics(SM_CYFULLSCREEN); + + _gameSize = gameSize; + _windowSize = windowSize; + _renderRect = Metrics::GetRenderRect( + gameSize, + _screenMode == ScreenMode::FullscreenDefault ? _desktopSize : _windowSize, + !_d2dxContext->GetOptions().GetFlag(OptionsFlag::NoWide)); + +#ifndef NDEBUG + ShowCursor_Real(TRUE); +#endif + + RECT clientRect; + GetClientRect(hWnd, &clientRect); + + SetWindowSubclass((HWND)hWnd, d2dxSubclassWndProc, 1234, (DWORD_PTR)this); + + const int32_t widthFromClientRect = clientRect.right - clientRect.left; + const int32_t heightFromClientRect = clientRect.bottom - clientRect.top; + + _featureLevel = D3D_FEATURE_LEVEL_11_0; + D3D_FEATURE_LEVEL requestedFeatureLevels[] = + { + D3D_FEATURE_LEVEL_11_1, + D3D_FEATURE_LEVEL_11_0, + D3D_FEATURE_LEVEL_10_1 + }; + + _dxgiAllowTearingFlagSupported = IsAllowTearingFlagSupported(); + _frameLatencyWaitableObjectSupported = false; // IsFrameLatencyWaitableObjectSupported(); + + _swapChainCreateFlags = 0; + + if (_d2dxContext->GetOptions().GetFlag(OptionsFlag::NoVSync)) + { + if (_dxgiAllowTearingFlagSupported) + { + _syncStrategy = RenderContextSyncStrategy::AllowTearing; + _swapChainCreateFlags |= DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING; + D2DX_LOG("Using 'AllowTearing' sync strategy.") + } + else + { + _syncStrategy = RenderContextSyncStrategy::Interval0; + D2DX_LOG("Using 'Interval0' sync strategy.") + } + } + else + { + if (_frameLatencyWaitableObjectSupported) + { + _syncStrategy = RenderContextSyncStrategy::FrameLatencyWaitableObject; + _swapChainCreateFlags |= DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT; + D2DX_LOG("Using 'FrameLatencyWaitableObject' sync strategy.") + } + else + { + _syncStrategy = RenderContextSyncStrategy::Interval1; + D2DX_LOG("Using 'Interval1' sync strategy.") + } + } + + if (GetWindowsVersion().major >= 10) + { + _swapStrategy = RenderContextSwapStrategy::FlipDiscard; + D2DX_LOG("Using 'FlipDiscard' swap strategy.") + } + else + { + _swapStrategy = RenderContextSwapStrategy::Discard; + D2DX_LOG("Using 'Discard' swap strategy.") + } + + DXGI_SWAP_CHAIN_DESC swapChainDesc; + ZeroMemory(&swapChainDesc, sizeof(swapChainDesc)); + swapChainDesc.BufferCount = 2; + swapChainDesc.BufferDesc.Width = _desktopSize.width; + swapChainDesc.BufferDesc.Height = _desktopSize.height; + swapChainDesc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; + swapChainDesc.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED; + swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; + swapChainDesc.OutputWindow = hWnd; + swapChainDesc.BufferDesc.RefreshRate.Numerator = 0; + swapChainDesc.BufferDesc.RefreshRate.Denominator = 0; + swapChainDesc.SwapEffect = _swapStrategy == RenderContextSwapStrategy::FlipDiscard ? DXGI_SWAP_EFFECT_FLIP_DISCARD : DXGI_SWAP_EFFECT_DISCARD; + swapChainDesc.Flags = _swapChainCreateFlags; + swapChainDesc.SampleDesc.Count = 1; + swapChainDesc.SampleDesc.Quality = 0; + swapChainDesc.Windowed = TRUE; + +#ifdef NDEBUG + uint32_t flags = 0; +#else + uint32_t flags = D3D11_CREATE_DEVICE_DEBUG; +#endif + + ComPtr swapChain; + + D2DX_CHECK_HR( + D3D11CreateDeviceAndSwapChain( + NULL, + D3D_DRIVER_TYPE_HARDWARE, + NULL, + flags, + requestedFeatureLevels, + ARRAYSIZE(requestedFeatureLevels), + D3D11_SDK_VERSION, + &swapChainDesc, + swapChain.GetAddressOf(), + &_device, + &_featureLevel, + &_deviceContext)); + + D2DX_LOG("Created device supports %s.", + _featureLevel == D3D_FEATURE_LEVEL_11_1 ? "D3D_FEATURE_LEVEL_11_1" : + _featureLevel == D3D_FEATURE_LEVEL_11_0 ? "D3D_FEATURE_LEVEL_11_0" : + _featureLevel == D3D_FEATURE_LEVEL_10_1 ? "D3D_FEATURE_LEVEL_10_1" : "unknown"); + + D2DX_CHECK_HR( + swapChain.As(&_swapChain1)); + + ComPtr dxgiFactory; + _swapChain1->GetParent(IID_PPV_ARGS(&dxgiFactory)); + if (dxgiFactory) + { + dxgiFactory->MakeWindowAssociation(hWnd, DXGI_MWA_NO_WINDOW_CHANGES); + } + + _swapChain1.As(&_swapChain2); + +#ifdef ALLOW_SET_SOURCE_SIZE + if (_swapChain2) + { + _backbufferSizingStrategy = RenderContextBackbufferSizingStrategy::SetSourceSize; + D2DX_LOG("Using 'SetSourceSize' backbuffer sizing strategy."); + + if (!_options.noVSync && _frameLatencyWaitableObjectSupported) + { + D2DX_LOG("Will sync using IDXGISwapChain2::GetFrameLatencyWaitableObject."); + _frameLatencyWaitableObject = _swapChain2->GetFrameLatencyWaitableObject(); + } + } + else +#endif + { + _backbufferSizingStrategy = RenderContextBackbufferSizingStrategy::ResizeBuffers; + D2DX_LOG("Using 'ResizeBuffers' backbuffer sizing strategy.") + } + + if (SUCCEEDED(_deviceContext->QueryInterface(IID_PPV_ARGS(&_deviceContext1)))) + { + D2DX_LOG("Device context supports ID3D11DeviceContext1. Will use this to discard resources and views."); + } + else + { + D2DX_LOG("Device context does not support ID3D11DeviceContext1."); + } + + if (_syncStrategy == RenderContextSyncStrategy::FrameLatencyWaitableObject) + { + assert(_swapChain2); + if (_swapChain2) + { + D2DX_LOG("Setting maximum frame latency to %i.", MAX_FRAME_LATENCY); + D2DX_CHECK_HR( + _swapChain2->SetMaximumFrameLatency(MAX_FRAME_LATENCY)); + } + } + else + { + ComPtr dxgiDevice1; + if (SUCCEEDED(_swapChain1->GetDevice(IID_PPV_ARGS(&dxgiDevice1)))) + { + D2DX_LOG("Setting maximum frame latency to %i.", MAX_FRAME_LATENCY); + D2DX_CHECK_HR( + dxgiDevice1->SetMaximumFrameLatency(MAX_FRAME_LATENCY)); + } + } + + // get a pointer directly to the back buffer + ComPtr backbuffer; + D2DX_CHECK_HR( + _swapChain1->GetBuffer(0, __uuidof(ID3D11Texture2D), &backbuffer)); + + // create a render target pointing to the back buffer + D2DX_CHECK_HR( + _device->CreateRenderTargetView(backbuffer.Get(), nullptr, &_backbufferRtv)); + + backbuffer = nullptr; + + float color[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; + _deviceContext->ClearRenderTargetView(_backbufferRtv.Get(), color); + + Size renderTargetSize = _d2dxContext->GetSuggestedCustomResolution(); + renderTargetSize.width = max(1024, renderTargetSize.width); + renderTargetSize.height = max(768, renderTargetSize.height); + + _vbCapacity = 4 * 1024 * 1024; + + SetSizes(_gameSize, _windowSize); + + _resources = std::make_unique( + _vbCapacity * sizeof(Vertex), + 16 * sizeof(Constants), + renderTargetSize, + _device.Get(), + simd); + + SetRasterizerState(_resources->GetRasterizerState(true)); + _deviceContext->IASetInputLayout(_resources->GetInputLayout()); + + ID3D11Buffer* cb = _resources->GetConstantBuffer(); + _deviceContext->VSSetConstantBuffers(0, 1, &cb); + _deviceContext->PSSetConstantBuffers(0, 1, &cb); + + ID3D11SamplerState* samplerState[2] = + { + _resources->GetSamplerState(RenderContextSamplerState::Point), + _resources->GetSamplerState(RenderContextSamplerState::Bilinear), + }; + + _deviceContext->PSSetSamplers(0, 2, samplerState); + + SetRenderTargets( + _resources->GetFramebufferRtv(RenderContextFramebuffer::Game), + _resources->GetFramebufferRtv(RenderContextFramebuffer::SurfaceId)); + + uint32_t stride = sizeof(Vertex); + uint32_t offset = 0; + ID3D11Buffer* vbs[1] = { _resources->GetVertexBuffer() }; + _deviceContext->IASetVertexBuffers(0, 1, vbs, &stride, &offset); +} + +HWND RenderContext::GetHWnd() const +{ + return _hWnd; +} + +_Use_decl_annotations_ +void RenderContext::Draw( + const Batch& batch, + uint32_t startVertexLocation) +{ + SetBlendState(batch.GetAlphaBlend()); + + ITextureCache* atlas = GetTextureCache(batch); + + SetShaderState( + _resources->GetVertexShader(RenderContextVertexShader::Game), + _resources->GetPixelShader(RenderContextPixelShader::Game), + atlas ? atlas->GetSrv(batch.GetTextureAtlas()) : nullptr, + _resources->GetTexture1DSrv(RenderContextTexture1D::Palette)); + + _deviceContext->Draw(batch.GetVertexCount(), startVertexLocation + batch.GetStartVertex()); +} + +bool RenderContext::IsIntegerScale() const +{ + float scaleX = ((float)_renderRect.size.width / _gameSize.width); + float scaleY = ((float)_renderRect.size.height / _gameSize.height); + + return fabs(scaleX - floor(scaleX)) < 0.01 && fabs(scaleY - floor(scaleY)) < 0.01; +} + +void RenderContext::Present() +{ + _deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); + + float color[] = { .0f, .0f, .0f, .0f }; + + SetBlendState(AlphaBlend::Opaque); + + SetRenderTargets( + _resources->GetFramebufferRtv(RenderContextFramebuffer::GammaCorrected), + nullptr); + + _deviceContext->ClearRenderTargetView(_resources->GetFramebufferRtv(RenderContextFramebuffer::GammaCorrected), color); + UpdateViewport({ 0,0,_gameSize.width, _gameSize.height }); + + SetShaderState( + _resources->GetVertexShader(RenderContextVertexShader::Display), + _resources->GetPixelShader(RenderContextPixelShader::Gamma), + _resources->GetFramebufferSrv(RenderContextFramebuffer::Game), + _resources->GetTexture1DSrv(RenderContextTexture1D::GammaTable)); + + auto startVertexLocation = _vbWriteIndex; + uint32_t vertexCount = UpdateVerticesWithFullScreenTriangle( + _gameSize, + _resources->GetFramebufferSize(), + { 0,0,_gameSize.width, _gameSize.height }); + + _deviceContext->Draw(vertexCount, startVertexLocation); + + if (!_d2dxContext->GetOptions().GetFlag(OptionsFlag::NoAntiAliasing)) + { + SetShaderState( + nullptr, + nullptr, + nullptr, + nullptr); + + SetRenderTargets( + _resources->GetFramebufferRtv(RenderContextFramebuffer::Game), + nullptr); + + _deviceContext->ClearRenderTargetView(_resources->GetFramebufferRtv(RenderContextFramebuffer::Game), color); + UpdateViewport({ 0,0,_gameSize.width, _gameSize.height }); + + SetShaderState( + _resources->GetVertexShader(RenderContextVertexShader::Display), + _resources->GetPixelShader(RenderContextPixelShader::ResolveAA), + _resources->GetFramebufferSrv(RenderContextFramebuffer::GammaCorrected), + _resources->GetFramebufferSrv(RenderContextFramebuffer::SurfaceId)); + + startVertexLocation = _vbWriteIndex; + vertexCount = UpdateVerticesWithFullScreenTriangle( + _gameSize, + _resources->GetFramebufferSize(), + { 0,0,_gameSize.width, _gameSize.height }); + + _deviceContext->Draw(vertexCount, startVertexLocation); + } + + SetRasterizerState(_resources->GetRasterizerState(false)); + + SetRenderTargets(_backbufferRtv.Get(), nullptr); + + _deviceContext->ClearRenderTargetView(_backbufferRtv.Get(), color); + UpdateViewport(_renderRect); + + RenderContextPixelShader pixelShader; + + switch (_d2dxContext->GetOptions().GetFiltering()) + { + default: + case FilteringOption::HighQuality: + pixelShader = IsIntegerScale() ? + RenderContextPixelShader::DisplayIntegerScale : + RenderContextPixelShader::DisplayNonintegerScale; + break; + case FilteringOption::Bilinear: + pixelShader = RenderContextPixelShader::DisplayBilinearScale; + break; + case FilteringOption::CatmullRom: + pixelShader = RenderContextPixelShader::DisplayCatmullRomScale; + break; + } + + SetShaderState( + _resources->GetVertexShader(RenderContextVertexShader::Display), + _resources->GetPixelShader(pixelShader), + _d2dxContext->GetOptions().GetFlag(OptionsFlag::NoAntiAliasing) ? _resources->GetFramebufferSrv(RenderContextFramebuffer::GammaCorrected) : _resources->GetFramebufferSrv(RenderContextFramebuffer::Game), + nullptr); + + startVertexLocation = _vbWriteIndex; + vertexCount = UpdateVerticesWithFullScreenTriangle( + _gameSize, + _resources->GetFramebufferSize(), + _renderRect); + + _deviceContext->Draw(vertexCount, startVertexLocation); + + SetShaderState( + nullptr, + nullptr, + nullptr, + nullptr); + +#ifndef NDEBUG + if (!(_frameCount & 255)) + { + D2DX_LOG("Texture cache use: %u, %u, %u, %u, %u, %u, %u", + this->_resources->GetTextureCache(8, 8)->GetUsedCount(), + this->_resources->GetTextureCache(16, 16)->GetUsedCount(), + this->_resources->GetTextureCache(32, 32)->GetUsedCount(), + this->_resources->GetTextureCache(64, 64)->GetUsedCount(), + this->_resources->GetTextureCache(128, 128)->GetUsedCount(), + this->_resources->GetTextureCache(256, 256)->GetUsedCount(), + this->_resources->GetTextureCache(256, 128)->GetUsedCount()); + } +#endif + + switch (_syncStrategy) + { + case RenderContextSyncStrategy::AllowTearing: + D2DX_CHECK_HR(_swapChain1->Present(0, DXGI_PRESENT_ALLOW_TEARING)); + break; + case RenderContextSyncStrategy::Interval0: + D2DX_CHECK_HR(_swapChain1->Present(0, 0)); + break; + case RenderContextSyncStrategy::FrameLatencyWaitableObject: + D2DX_CHECK_HR(_swapChain1->Present(0, 0)); + ::WaitForSingleObjectEx(_frameLatencyWaitableObject.Get(), 1000, true); + break; + case RenderContextSyncStrategy::Interval1: + D2DX_CHECK_HR(_swapChain1->Present(1, 0)); + break; + } + + double curTime = TimeEndMs(_timeStart); + _frameTimeMs = curTime - _prevTime; + _prevTime = curTime; + + if (_deviceContext1) + { + _deviceContext1->DiscardView(_resources->GetFramebufferRtv(RenderContextFramebuffer::Game)); + _deviceContext1->DiscardView(_backbufferRtv.Get()); + } + + _resources->OnNewFrame(); + + SetRenderTargets( + _resources->GetFramebufferRtv(RenderContextFramebuffer::Game), + _resources->GetFramebufferRtv(RenderContextFramebuffer::SurfaceId) + ); + + _deviceContext->ClearRenderTargetView(_resources->GetFramebufferRtv(RenderContextFramebuffer::Game), color); + _deviceContext->ClearRenderTargetView(_resources->GetFramebufferRtv(RenderContextFramebuffer::SurfaceId), color); + + UpdateViewport({ 0,0,_gameSize.width, _gameSize.height }); + + SetRasterizerState(_resources->GetRasterizerState(true)); + + SetShaderState( + _resources->GetVertexShader(RenderContextVertexShader::Game), + _resources->GetPixelShader(RenderContextPixelShader::Game), + nullptr, + nullptr); + + ++_frameCount; +} + +_Use_decl_annotations_ +void RenderContext::LoadGammaTable( + _In_reads_(valueCount) const uint32_t* values, + _In_ uint32_t valueCount) +{ + _deviceContext->UpdateSubresource( + _resources->GetTexture1D(RenderContextTexture1D::GammaTable), 0, nullptr, values, valueCount * sizeof(uint32_t), 0); +} + +_Use_decl_annotations_ +void RenderContext::WriteToScreen( + const uint32_t* pixels, + int32_t width, + int32_t height) +{ + D3D11_MAPPED_SUBRESOURCE ms; + D2DX_CHECK_HR(_deviceContext->Map(_resources->GetVideoTexture(), 0, D3D11_MAP_WRITE_DISCARD, 0, &ms)); + memcpy(ms.pData, pixels, width * height * 4); + _deviceContext->Unmap(_resources->GetVideoTexture(), 0); + + SetBlendState(AlphaBlend::Opaque); + + SetShaderState( + _resources->GetVertexShader(RenderContextVertexShader::Display), + _resources->GetPixelShader(RenderContextPixelShader::Video), + _resources->GetVideoSrv(), + nullptr); + + uint32_t startVertexLocation = _vbWriteIndex; + uint32_t vertexCount = UpdateVerticesWithFullScreenTriangle(_gameSize, _resources->GetVideoTextureSize(), { 0,0,_gameSize.width, _gameSize.height }); + UpdateViewport({ 0,0,_gameSize.width, _gameSize.height }); + _deviceContext->Draw(vertexCount, startVertexLocation); + + Present(); +} + +_Use_decl_annotations_ +void RenderContext::SetBlendState( + AlphaBlend alphaBlend) +{ + SetBlendState(_resources->GetBlendState(alphaBlend)); +} + +_Use_decl_annotations_ +uint32_t RenderContext::BulkWriteVertices( + const Vertex* vertices, + uint32_t vertexCount) +{ + auto mapType = D3D11_MAP_WRITE_NO_OVERWRITE; + if ((_vbWriteIndex + vertexCount) > _vbCapacity) + { + mapType = D3D11_MAP_WRITE_DISCARD; + _vbWriteIndex = 0; + assert(vertexCount <= _vbCapacity); + vertexCount = min(vertexCount, _vbCapacity); + } + + const uint32_t startVertexLocation = _vbWriteIndex; + + if (vertexCount > 0) + { + D3D11_MAPPED_SUBRESOURCE mappedSubResource = { 0 }; + D2DX_CHECK_HR(_deviceContext->Map(_resources->GetVertexBuffer(), 0, mapType, 0, &mappedSubResource)); + Vertex* pMappedVertices = (Vertex*)mappedSubResource.pData + _vbWriteIndex; + memcpy(pMappedVertices, vertices, sizeof(Vertex) * vertexCount); + _deviceContext->Unmap(_resources->GetVertexBuffer(), 0); + } + + _vbWriteIndex += vertexCount; + + return startVertexLocation; +} + +_Use_decl_annotations_ +uint32_t RenderContext::UpdateVerticesWithFullScreenTriangle( + Size srcSize, + Size srcTextureSize, + Rect dstRect) +{ + Vertex vertices[3] = { + Vertex{ 0, 0, srcTextureSize.width, srcTextureSize.height, 0xFFFFFFFF, false, srcSize.height, 0, srcSize.width }, + Vertex{ dstRect.size.width * 2, 0, srcTextureSize.width, srcTextureSize.height, 0xFFFFFFFF, false, srcSize.height, 0, srcSize.width }, + Vertex{ 0, dstRect.size.height * 2, srcTextureSize.width, srcTextureSize.height, 0xFFFFFFFF, false, srcSize.height, 0, srcSize.width }, + }; + + auto mapType = D3D11_MAP_WRITE_NO_OVERWRITE; + + if ((_vbWriteIndex + ARRAYSIZE(vertices)) > _vbCapacity) + { + mapType = D3D11_MAP_WRITE_DISCARD; + _vbWriteIndex = 0; + } + + D3D11_MAPPED_SUBRESOURCE mappedSubResource = { 0 }; + D2DX_CHECK_HR(_deviceContext->Map(_resources->GetVertexBuffer(), 0, mapType, 0, &mappedSubResource)); + Vertex* pMappedVertices = (Vertex*)mappedSubResource.pData + _vbWriteIndex; + memcpy(pMappedVertices, vertices, sizeof(Vertex) * ARRAYSIZE(vertices)); + _deviceContext->Unmap(_resources->GetVertexBuffer(), 0); + + _vbWriteIndex += ARRAYSIZE(vertices); + + return ARRAYSIZE(vertices); +} + +_Use_decl_annotations_ +TextureCacheLocation RenderContext::UpdateTexture( + const Batch& batch, + const uint8_t* tmuData, + uint32_t tmuDataSize) +{ + if (!batch.IsValid()) + { + return { -1, -1 }; + } + + const uint32_t contentKey = batch.GetHash(); + + ITextureCache* atlas = GetTextureCache(batch); + + auto tcl = atlas->FindTexture(contentKey, -1); + + if (tcl._textureAtlas < 0) + { + tcl = atlas->InsertTexture(contentKey, batch, tmuData, tmuDataSize); + } + + return tcl; +} + +_Use_decl_annotations_ +void RenderContext::UpdateViewport( + Rect rect) +{ + CD3D11_VIEWPORT viewport{ (float)rect.offset.x, (float)rect.offset.y, (float)rect.size.width, (float)rect.size.height }; + _deviceContext->RSSetViewports(1, &viewport); + + CD3D11_RECT scissorRect{ rect.offset.x, rect.offset.y, rect.size.width, rect.size.height }; + _deviceContext->RSSetScissorRects(1, &scissorRect); + + _constants.screenSize[0] = (float)rect.size.width; + _constants.screenSize[1] = (float)rect.size.height; + _constants.invScreenSize[0] = 1.0f / _constants.screenSize[0]; + _constants.invScreenSize[1] = 1.0f / _constants.screenSize[1]; + _constants.flags[0] = _d2dxContext->GetOptions().GetFlag(OptionsFlag::NoAntiAliasing) ? 0 : 1; + _constants.flags[1] = 0; + if (memcmp(&_constants, &_shadowState.constants, sizeof(Constants)) != 0) + { + D3D11_MAPPED_SUBRESOURCE mappedSubResource = { 0 }; + D2DX_CHECK_HR(_deviceContext->Map(_resources->GetConstantBuffer(), 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedSubResource)); + memcpy(mappedSubResource.pData, &_constants, sizeof(Constants)); + _deviceContext->Unmap(_resources->GetConstantBuffer(), 0); + _shadowState.constants = _constants; + } +} + +_Use_decl_annotations_ +void RenderContext::SetPalette( + int32_t paletteIndex, + const uint32_t* palette) +{ + _deviceContext->UpdateSubresource( + _resources->GetTexture1D(RenderContextTexture1D::Palette), + paletteIndex, + nullptr, + palette, + 1024, + 0); +} + +const Options& RenderContext::GetOptions() const +{ + return _d2dxContext->GetOptions(); +} + +static LRESULT CALLBACK d2dxSubclassWndProc( + HWND hWnd, + UINT uMsg, + WPARAM wParam, + LPARAM lParam, + UINT_PTR uIdSubclass, + DWORD_PTR dwRefData) +{ + RenderContext* renderContext = (RenderContext*)dwRefData; + + if (uMsg == WM_ACTIVATEAPP) + { + if (wParam) + { + renderContext->ClipCursor(); + } + else + { + renderContext->UnclipCursor(); + } + } + else if (uMsg == WM_SYSKEYDOWN || uMsg == WM_KEYDOWN) + { + if (wParam == VK_RETURN && (HIWORD(lParam) & KF_ALTDOWN)) + { + renderContext->ToggleFullscreen(); + return 0; + } + } + else if (uMsg == WM_DESTROY) + { + RemoveWindowSubclass(hWnd, d2dxSubclassWndProc, 1234); + D2DXContextFactory::DestroyInstance(); + } + else if (uMsg == WM_NCMOUSEMOVE) + { + ShowCursor_Real(TRUE); + return 0; + } + else if (uMsg >= WM_MOUSEFIRST && uMsg <= WM_MOUSELAST) + { +#ifdef NDEBUG + ShowCursor_Real(FALSE); +#endif + Offset mousePos = { LOWORD(lParam), HIWORD(lParam) }; + + Size gameSize; + Rect renderRect; + Size desktopSize; + renderContext->GetCurrentMetrics(&gameSize, &renderRect, &desktopSize); + const bool isFullscreen = renderContext->GetScreenMode() == ScreenMode::FullscreenDefault; + const float scale = (float)renderRect.size.height / gameSize.height; + const uint32_t scaledWidth = (uint32_t)(scale * gameSize.width); + const float mouseOffsetX = isFullscreen ? (float)(desktopSize.width / 2 - scaledWidth / 2) : 0.0f; + + mousePos.x = (int32_t)(max(0, mousePos.x - mouseOffsetX) / scale); + mousePos.y = (int32_t)(mousePos.y / scale); + + lParam = mousePos.x; + lParam |= mousePos.y << 16; + } + + return DefSubclassProc(hWnd, uMsg, wParam, lParam); +} + +_Use_decl_annotations_ +void RenderContext::SetRenderTargets( + ID3D11RenderTargetView* rtv0, + ID3D11RenderTargetView* rtv1) +{ + if (rtv0 != _shadowState.rtv0 || + rtv1 != _shadowState.rtv1) + { + ID3D11RenderTargetView* rtvs[2] = { rtv0, rtv1 }; + _deviceContext->OMSetRenderTargets(2, rtvs, nullptr); + _shadowState.rtv0 = rtv0; + _shadowState.rtv1 = rtv1; + } +} + +_Use_decl_annotations_ +void RenderContext::SetRasterizerState( + ID3D11RasterizerState* rs) +{ + if (rs != _shadowState.rs) + { + _deviceContext->RSSetState(rs); + _shadowState.rs = rs; + } +} + +_Use_decl_annotations_ +void RenderContext::SetBlendState( + ID3D11BlendState* blendState) +{ + if (blendState != _shadowState.bs) + { + float blendFactor[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; + UINT sampleMask = 0xffffffff; + _deviceContext->OMSetBlendState(blendState, blendFactor, sampleMask); + _shadowState.bs = blendState; + } +} + +_Use_decl_annotations_ +void RenderContext::SetShaderState( + ID3D11VertexShader* vs, + ID3D11PixelShader* ps, + ID3D11ShaderResourceView* srv0, + ID3D11ShaderResourceView* srv1) +{ + if (vs != _shadowState.vs) + { + _deviceContext->VSSetShader(vs, NULL, 0); + _shadowState.vs = vs; + } + + if (ps != _shadowState.ps) + { + _deviceContext->PSSetShader(ps, NULL, 0); + _shadowState.ps = ps; + } + + if (srv0 != _shadowState.psSrv0 || + srv1 != _shadowState.psSrv1) + { + ID3D11ShaderResourceView* srvs[2] = { srv0, srv1 }; + _deviceContext->PSSetShaderResources(0, 2, srvs); + _shadowState.psSrv0 = srv0; + _shadowState.psSrv1 = srv1; + } +} + +_Use_decl_annotations_ +ITextureCache* RenderContext::GetTextureCache( + const Batch& batch) const +{ + return _resources->GetTextureCache(batch.GetTextureWidth(), batch.GetTextureHeight()); +} + +void RenderContext::ResizeBackbuffer() +{ + if (_backbufferSizingStrategy == RenderContextBackbufferSizingStrategy::SetSourceSize) + { + D2DX_CHECK_HR(_swapChain2->SetSourceSize( + _renderRect.offset.x * 2 + _renderRect.size.width, + _renderRect.offset.y * 2 + _renderRect.size.height)); + } + else if (_backbufferSizingStrategy == RenderContextBackbufferSizingStrategy::ResizeBuffers) + { + SetRenderTargets(nullptr, nullptr); + _backbufferRtv = nullptr; + + D2DX_CHECK_HR(_swapChain1->ResizeBuffers(0, 0, 0, DXGI_FORMAT_UNKNOWN, _swapChainCreateFlags)); + + ComPtr backbuffer; + D2DX_CHECK_HR(_swapChain1->GetBuffer(0, IID_PPV_ARGS(&backbuffer))); + D2DX_CHECK_HR(_device->CreateRenderTargetView(backbuffer.Get(), nullptr, &_backbufferRtv)); + } +} + +_Use_decl_annotations_ +void RenderContext::SetSizes( + Size gameSize, + Size windowSize) +{ + _gameSize = gameSize; + _windowSize = windowSize; + + auto displaySize = _screenMode == ScreenMode::FullscreenDefault ? _desktopSize : _windowSize; + + _renderRect = Metrics::GetRenderRect( + _gameSize, + displaySize, + !_d2dxContext->GetOptions().GetFlag(OptionsFlag::NoWide)); + + bool centerOnCurrentPosition = _hasAdjustedWindowPlacement; + _hasAdjustedWindowPlacement = true; + + const int32_t desktopCenterX = _desktopSize.width / 2; + const int32_t desktopCenterY = _screenMode == ScreenMode::FullscreenDefault ? _desktopSize.height / 2 : _desktopClientMaxHeight / 2; + const Offset preferredPosition = _d2dxContext->GetOptions().GetWindowPosition(); + bool usePreferredPosition = preferredPosition.x >= 0 && preferredPosition.y >= 0; + + if (_screenMode == ScreenMode::Windowed) + { + Size maxWindowSize{ _desktopSize.width, _desktopClientMaxHeight }; + + RECT oldWindowRect; + GetWindowRect(_hWnd, &oldWindowRect); + const int32_t oldWindowWidth = oldWindowRect.right - oldWindowRect.left; + const int32_t oldWindowHeight = oldWindowRect.bottom - oldWindowRect.top; + const int32_t oldWindowCenterX = (oldWindowRect.left + oldWindowRect.right) / 2; + const int32_t oldWindowCenterY = (oldWindowRect.top + oldWindowRect.bottom) / 2; + + DWORD windowStyle = WS_VISIBLE; + + if (!_d2dxContext->GetOptions().GetFlag(OptionsFlag::Frameless)) + { + windowStyle |= WS_CAPTION | WS_MINIMIZEBOX | WS_SYSMENU; + } + + Size windowStylingSize{ 0,0 }; + RECT windowRect = { 0, 0, _windowSize.width, _windowSize.height }; + AdjustWindowRect(&windowRect, windowStyle, FALSE); + windowStylingSize.width = (windowRect.right - windowRect.left) - _windowSize.width; + windowStylingSize.height = (windowRect.bottom - windowRect.top) - _windowSize.height; + + if (_windowSize.height > maxWindowSize.height) + { + const float aspectRatio = (float)maxWindowSize.width / maxWindowSize.height; + _windowSize.height = maxWindowSize.height; + _windowSize.width = (int32_t)(_windowSize.height * aspectRatio); + + _renderRect = Metrics::GetRenderRect( + _gameSize, + _windowSize, + !_d2dxContext->GetOptions().GetFlag(OptionsFlag::NoWide)); + + windowRect = { 0, 0, _windowSize.width, _windowSize.height }; + AdjustWindowRect(&windowRect, windowStyle, FALSE); + } + + const int32_t newWindowWidth = windowRect.right - windowRect.left; + const int32_t newWindowHeight = windowRect.bottom - windowRect.top; + const int32_t newWindowCenterX = centerOnCurrentPosition ? oldWindowCenterX : desktopCenterX; + const int32_t newWindowCenterY = centerOnCurrentPosition ? oldWindowCenterY : desktopCenterY; + const int32_t newWindowX = usePreferredPosition ? preferredPosition.x : (newWindowCenterX - newWindowWidth / 2); + const int32_t newWindowY = max(0, usePreferredPosition ? preferredPosition.y : (newWindowCenterY - newWindowHeight / 2)); + + SetWindowLongPtr(_hWnd, GWL_STYLE, windowStyle); + SetWindowPos_Real(_hWnd, HWND_TOP, newWindowX, newWindowY, newWindowWidth, newWindowHeight, SWP_SHOWWINDOW | SWP_NOSENDCHANGING | SWP_FRAMECHANGED); + +#ifndef NDEBUG + RECT newWindowRect; + GetWindowRect(_hWnd, &newWindowRect); + assert(newWindowWidth == (newWindowRect.right - newWindowRect.left)); + assert(newWindowHeight == (newWindowRect.bottom - newWindowRect.top)); +#endif + } + else if (_screenMode == ScreenMode::FullscreenDefault) + { + SetWindowLongPtr(_hWnd, GWL_STYLE, WS_VISIBLE | WS_POPUP); + SetWindowPos_Real(_hWnd, HWND_TOP, 0, 0, _desktopSize.width, _desktopSize.height, SWP_SHOWWINDOW | SWP_NOSENDCHANGING | SWP_FRAMECHANGED); + } + + ClipCursor(); + + if (!_d2dxContext->GetOptions().GetFlag(OptionsFlag::NoTitleChange)) + { + char newWindowText[256]; + sprintf_s(newWindowText, "Diablo II DX [%ix%i, scale %i%%]", + _gameSize.width, + _gameSize.height, + (int)(((float)_renderRect.size.height / _gameSize.height) * 100.0f)); + ::SetWindowTextA(_hWnd, newWindowText); + } + + D2DX_LOG("Sizes: desktop %ix%i, window %ix%i, game %ix%i, render %ix%i", + _desktopSize.width, + _desktopSize.height, + _windowSize.width, + _windowSize.height, + _gameSize.width, + _gameSize.height, + _renderRect.size.width, + _renderRect.size.height); + + if (_resources) + { + ResizeBackbuffer(); + UpdateViewport({ 0,0,_gameSize.width, _gameSize.height }); + Present(); + } +} + +bool RenderContext::IsFrameLatencyWaitableObjectSupported() const +{ + auto windowsVersion = GetWindowsVersion(); + return (windowsVersion.major == 6 && windowsVersion.minor >= 3) || windowsVersion.major > 6; +} + +bool RenderContext::IsAllowTearingFlagSupported() const +{ + ComPtr dxgiFactory4; + + if (FAILED(CreateDXGIFactory1(IID_PPV_ARGS(&dxgiFactory4)))) + { + return false; + } + + ComPtr dxgiFactory5; + + if (FAILED(dxgiFactory4.As(&dxgiFactory5))) + { + return false; + } + + BOOL allowTearing = FALSE; + + if (SUCCEEDED(dxgiFactory5->CheckFeatureSupport(DXGI_FEATURE_PRESENT_ALLOW_TEARING, &allowTearing, sizeof(allowTearing)))) + { + return allowTearing; + } + + return false; +} + +void RenderContext::ToggleFullscreen() +{ + if (_screenMode == ScreenMode::FullscreenDefault) + { + _screenMode = ScreenMode::Windowed; + SetSizes(_gameSize, _windowSize); + } + else + { + _screenMode = ScreenMode::FullscreenDefault; + SetSizes(_gameSize, _windowSize); + } +} + +_Use_decl_annotations_ +void RenderContext::GetCurrentMetrics( + Size* gameSize, + Rect* renderRect, + Size* desktopSize) const +{ + if (gameSize) + { + *gameSize = _gameSize; + } + + if (renderRect) + { + *renderRect = _renderRect; + } + + if (desktopSize) + { + *desktopSize = _desktopSize; + } +} + +void RenderContext::ClipCursor() +{ + if (_d2dxContext->GetOptions().GetFlag(OptionsFlag::NoClipCursor)) + { + return; + } + + RECT clipRect; + ::GetClientRect(_hWnd, &clipRect); + ::ClientToScreen(_hWnd, (LPPOINT)&clipRect.left); + ::ClientToScreen(_hWnd, (LPPOINT)&clipRect.right); + ::ClipCursor(&clipRect); +} + +void RenderContext::UnclipCursor() +{ + ::ClipCursor(NULL); +} + +float RenderContext::GetFrameTime() const +{ + return (float)(_frameTimeMs / 1000.0); +} + +int32_t RenderContext::GetFrameTimeFp() const +{ + auto frameTimeMs = (int64_t)(_frameTimeMs * (65536.0 / 1000.0)); + return (int32_t)max(INT_MIN, min(INT_MAX, frameTimeMs)); +} + +ScreenMode RenderContext::GetScreenMode() const +{ + return _screenMode; +} diff --git a/src/d2dx/RenderContext.h b/src/d2dx/RenderContext.h new file mode 100644 index 0000000..cc1b919 --- /dev/null +++ b/src/d2dx/RenderContext.h @@ -0,0 +1,217 @@ +/* + This file is part of D2DX. + + Copyright (C) 2021 Bolrog + + D2DX is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + D2DX is distributed in the hope that it will be 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 D2DX. If not, see . +*/ +#pragma once + +#include "IRenderContext.h" +#include "ISimd.h" +#include "ITextureCache.h" +#include "RenderContextResources.h" +#include "Types.h" + +namespace d2dx +{ + class Vertex; + class Batch; + + enum class RenderContextSyncStrategy + { + AllowTearing = 0, + Interval0 = 1, + FrameLatencyWaitableObject = 2, + Interval1 = 3, + }; + + enum class RenderContextSwapStrategy + { + FlipDiscard = 0, + Discard = 1, + }; + + enum class RenderContextBackbufferSizingStrategy + { + SetSourceSize = 0, + ResizeBuffers = 1, + }; + + class RenderContext final : public IRenderContext + { + public: + RenderContext( + _In_ HWND hWnd, + _In_ Size gameSize, + _In_ Size windowSize, + _In_ ScreenMode initialScreenMode, + _In_ ID2DXContext* d2dxContext, + _In_ const std::shared_ptr& simd); + + virtual ~RenderContext() noexcept {} + + virtual HWND GetHWnd() const override; + + virtual void LoadGammaTable( + _In_reads_(valueCount) const uint32_t* values, + _In_ uint32_t valueCount) override; + + virtual uint32_t BulkWriteVertices( + _In_reads_(vertexCount) const Vertex* vertices, + _In_ uint32_t vertexCount) override; + + virtual TextureCacheLocation UpdateTexture( + _In_ const Batch& batch, + _In_reads_(tmuDataSize) const uint8_t* tmuData, + _In_ uint32_t tmuDataSize) override; + + virtual void Draw( + _In_ const Batch& batch, + _In_ uint32_t startVertexLocation) override; + + virtual void Present() override; + + virtual void WriteToScreen( + _In_reads_(width* height) const uint32_t* pixels, + _In_ int32_t width, + _In_ int32_t height) override; + + virtual void SetPalette( + _In_ int32_t paletteIndex, + _In_reads_(256) const uint32_t* palette) override; + + virtual const Options& GetOptions() const override; + + virtual ITextureCache* GetTextureCache( + _In_ const Batch& batch) const override; + + virtual void SetSizes( + _In_ Size gameSize, + _In_ Size windowSize) override; + + virtual void GetCurrentMetrics( + _Out_opt_ Size* gameSize, + _Out_opt_ Rect* renderRect, + _Out_opt_ Size* desktopSize) const override; + + virtual void ToggleFullscreen() override; + + virtual float GetFrameTime() const override; + virtual int32_t GetFrameTimeFp() const override; + + virtual ScreenMode GetScreenMode() const override; + + void ClipCursor(); + void UnclipCursor(); + + private: + bool IsIntegerScale() const; + + void UpdateViewport( + _In_ Rect rect); + + void SetRenderTargets( + _In_opt_ ID3D11RenderTargetView* rtv0, + _In_opt_ ID3D11RenderTargetView* rtv1); + + void SetRasterizerState( + _In_ ID3D11RasterizerState* rasterizerState); + + void SetBlendState( + _In_ AlphaBlend alphaBlend); + + void AdjustWindowPlacement( + _In_ HWND hWnd); + + uint32_t UpdateVerticesWithFullScreenTriangle( + _In_ Size srcSize, + _In_ Size srcTextureSize, + _In_ Rect dstRect); + + bool IsFrameLatencyWaitableObjectSupported() const; + + bool IsAllowTearingFlagSupported() const; + + void ResizeBackbuffer(); + + void SetShaderState( + _In_opt_ ID3D11VertexShader* vs, + _In_opt_ ID3D11PixelShader* ps, + _In_opt_ ID3D11ShaderResourceView* psSrv0, + _In_opt_ ID3D11ShaderResourceView* psSrv1); + + void SetBlendState( + _In_ ID3D11BlendState* blendState); + + struct Constants final + { + float screenSize[2] = { 0.0f, 0.0f }; + float invScreenSize[2] = { 0.0f, 0.0f }; + uint32_t flags[4] = { 0, 0, 0, 0 }; + }; + + static_assert(sizeof(Constants) == 8 * 4, "size of Constants"); + + struct DeviceContextState final + { + Constants constants; + ID3D11RasterizerState* rs = nullptr; + ID3D11VertexShader* vs = nullptr; + ID3D11PixelShader* ps = nullptr; + ID3D11BlendState* bs = nullptr; + ID3D11ShaderResourceView* psSrv0 = nullptr; + ID3D11ShaderResourceView* psSrv1 = nullptr; + ID3D11RenderTargetView* rtv0 = nullptr; + ID3D11RenderTargetView* rtv1 = nullptr; + }; + + ScreenMode _screenMode = ScreenMode::Windowed; + ComPtr _device; + ComPtr _device3; + ComPtr _deviceContext; + ComPtr _deviceContext1; + ComPtr _swapChain1; + ComPtr _swapChain2; + ComPtr _backbufferRtv; + std::unique_ptr _resources; + std::shared_ptr _simd; + + uint32_t _frameCount = 0; + Size _gameSize = { 0, 0 }; + Rect _renderRect = { 0,0,0,0 }; + Size _windowSize = { 0,0 }; + Size _desktopSize = { 0,0 }; + int32_t _desktopClientMaxHeight = 0; + uint32_t _vbWriteIndex = 0; + uint32_t _vbCapacity = 0; + Constants _constants; + RenderContextSyncStrategy _syncStrategy = RenderContextSyncStrategy::AllowTearing; + RenderContextSwapStrategy _swapStrategy = RenderContextSwapStrategy::FlipDiscard; + RenderContextBackbufferSizingStrategy _backbufferSizingStrategy = RenderContextBackbufferSizingStrategy::SetSourceSize; + DWORD _swapChainCreateFlags = 0; + bool _dxgiAllowTearingFlagSupported = false; + bool _frameLatencyWaitableObjectSupported = false; + D3D_FEATURE_LEVEL _featureLevel = D3D_FEATURE_LEVEL_11_0; + HWND _hWnd = nullptr; + ID2DXContext* _d2dxContext = nullptr; + DeviceContextState _shadowState; + EventHandle _frameLatencyWaitableObject; + int64_t _timeStart; + bool _hasAdjustedWindowPlacement = false; + + double _prevTime; + double _frameTimeMs; + }; +} \ No newline at end of file diff --git a/src/d2dx/RenderContextResources.cpp b/src/d2dx/RenderContextResources.cpp new file mode 100644 index 0000000..004b4ac --- /dev/null +++ b/src/d2dx/RenderContextResources.cpp @@ -0,0 +1,540 @@ +/* + This file is part of D2DX. + + Copyright (C) 2021 Bolrog + + D2DX is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + D2DX is distributed in the hope that it will be 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 D2DX. If not, see . +*/ +#include "pch.h" +#include "RenderContextResources.h" +#include "Utils.h" +#include "Types.h" +#include "TextureCache.h" +#include "DisplayVS_cso.h" +#include "DisplayNonintegerScalePS_cso.h" +#include "DisplayIntegerScalePS_cso.h" +#include "DisplayBilinearScalePS_cso.h" +#include "DisplayCatmullRomScalePS_cso.h" +#include "GamePS_cso.h" +#include "GameVS_cso.h" +#include "VideoPS_cso.h" +#include "GammaPS_cso.h" +#include "ResolveAA_cso.h" +#include "Metrics.h" + +using namespace d2dx; + +_Use_decl_annotations_ +RenderContextResources::RenderContextResources( + uint32_t vbSizeBytes, + uint32_t cbSizeBytes, + Size framebufferSize, + ID3D11Device* device, + const std::shared_ptr& simd) +{ + CreateTexture1Ds(device); + CreateTextureCaches(device, simd); + CreateVideoTextures(device); + CreateShadersAndInputLayout(device); + CreateRasterizerState(device); + CreateSamplerStates(device); + CreateBlendStates(device); + CreateFramebuffers(framebufferSize, device); + CreateVertexBuffer(vbSizeBytes, device); + CreateConstantBuffer(cbSizeBytes, device); +} + +void RenderContextResources::OnNewFrame() +{ + for (int32_t i = 0; i < ARRAYSIZE(_textureCaches); ++i) + { + _textureCaches[i]->OnNewFrame(); + } +} + +ITextureCache* RenderContextResources::GetTextureCache( + int32_t textureWidth, + int32_t textureHeight) const +{ + if (textureWidth == 256 && textureHeight == 128) + { + return _textureCaches[6].get(); + } + + const int32_t longest = max(textureWidth, textureHeight); + assert(longest >= 8); + uint32_t log2Longest = 0; + BitScanForward((DWORD*)&log2Longest, (DWORD)longest); + log2Longest -= 3; + assert(log2Longest <= 5); + return _textureCaches[log2Longest].get(); +} + +_Use_decl_annotations_ +void RenderContextResources::CreateShadersAndInputLayout( + ID3D11Device* device) +{ + D2DX_CHECK_HR( + device->CreateVertexShader(GameVS_cso, ARRAYSIZE(GameVS_cso), NULL, &_vertexShaders[(int32_t)RenderContextVertexShader::Game])); + + D2DX_CHECK_HR( + device->CreatePixelShader(GamePS_cso, ARRAYSIZE(GamePS_cso), NULL, &_pixelShaders[(int32_t)RenderContextPixelShader::Game])); + + D2DX_CHECK_HR( + device->CreatePixelShader(VideoPS_cso, ARRAYSIZE(VideoPS_cso), NULL, &_pixelShaders[(int32_t)RenderContextPixelShader::Video])); + + D2DX_CHECK_HR( + device->CreateVertexShader(DisplayVS_cso, ARRAYSIZE(DisplayVS_cso), NULL, &_vertexShaders[(int32_t)RenderContextVertexShader::Display])); + + D2DX_CHECK_HR( + device->CreatePixelShader(DisplayIntegerScalePS_cso, ARRAYSIZE(DisplayIntegerScalePS_cso), NULL, &_pixelShaders[(int32_t)RenderContextPixelShader::DisplayIntegerScale])); + + D2DX_CHECK_HR( + device->CreatePixelShader(DisplayNonintegerScalePS_cso, ARRAYSIZE(DisplayNonintegerScalePS_cso), NULL, &_pixelShaders[(int32_t)RenderContextPixelShader::DisplayNonintegerScale])); + + D2DX_CHECK_HR( + device->CreatePixelShader(DisplayBilinearScalePS_cso, ARRAYSIZE(DisplayBilinearScalePS_cso), NULL, &_pixelShaders[(int32_t)RenderContextPixelShader::DisplayBilinearScale])); + + D2DX_CHECK_HR( + device->CreatePixelShader(DisplayCatmullRomScalePS_cso, ARRAYSIZE(DisplayCatmullRomScalePS_cso), NULL, &_pixelShaders[(int32_t)RenderContextPixelShader::DisplayCatmullRomScale])); + + D2DX_CHECK_HR( + device->CreatePixelShader(GammaPS_cso, ARRAYSIZE(GammaPS_cso), NULL, &_pixelShaders[(int32_t)RenderContextPixelShader::Gamma])); + + D2DX_CHECK_HR( + device->CreatePixelShader(ResolveAA_cso, ARRAYSIZE(ResolveAA_cso), NULL, &_pixelShaders[(int32_t)RenderContextPixelShader::ResolveAA])); + + D3D11_INPUT_ELEMENT_DESC inputElementDescs[4] = + { + { "POSITION", 0, DXGI_FORMAT_R16G16_SINT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 }, + { "TEXCOORD", 0, DXGI_FORMAT_R16G16_SINT, 0, 4, D3D11_INPUT_PER_VERTEX_DATA, 0 }, + { "COLOR", 0, DXGI_FORMAT_B8G8R8A8_UNORM, 0, 8, D3D11_INPUT_PER_VERTEX_DATA, 0 }, + { "TEXCOORD", 1, DXGI_FORMAT_R16G16_UINT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0 } + }; + + D2DX_CHECK_HR( + device->CreateInputLayout(inputElementDescs, ARRAYSIZE(inputElementDescs), GameVS_cso, ARRAYSIZE(GameVS_cso), &_inputLayout)); +} + +_Use_decl_annotations_ +void RenderContextResources::CreateTexture1Ds( + ID3D11Device* device) +{ + CD3D11_TEXTURE1D_DESC desc{ + DXGI_FORMAT_B8G8R8A8_UNORM, + (UINT)256, + D2DX_MAX_PALETTES, + 1U, + D3D11_BIND_SHADER_RESOURCE, + D3D11_USAGE_DEFAULT + }; + + Buffer initialPalettes(D2DX_MAX_PALETTES * 256, true, 0xFFFFFFFF); + + D3D11_SUBRESOURCE_DATA subResourceData[D2DX_MAX_PALETTES]; + for (int32_t i = 0; i < D2DX_MAX_PALETTES; ++i) + { + subResourceData[i].pSysMem = initialPalettes.items + 256 * i; + subResourceData[i].SysMemPitch = 0; + subResourceData[i].SysMemSlicePitch = 0; + } + + D2DX_CHECK_HR( + device->CreateTexture1D( + &desc, + &subResourceData[0], + &_texture1Ds[(int32_t)RenderContextTexture1D::Palette].texture)); + + D2DX_CHECK_HR( + device->CreateShaderResourceView( + _texture1Ds[(int32_t)RenderContextTexture1D::Palette].texture.Get(), + nullptr, + &_texture1Ds[(int32_t)RenderContextTexture1D::Palette].srv)); + + desc.ArraySize = 1; + + D2DX_CHECK_HR( + device->CreateTexture1D( + &desc, + nullptr, + &_texture1Ds[(int32_t)RenderContextTexture1D::GammaTable].texture)); + + D2DX_CHECK_HR( + device->CreateShaderResourceView( + _texture1Ds[(int32_t)RenderContextTexture1D::GammaTable].texture.Get(), + nullptr, + &_texture1Ds[(int32_t)RenderContextTexture1D::GammaTable].srv)); +} + +_Use_decl_annotations_ +void RenderContextResources::CreateTextureCaches( + ID3D11Device* device, + const std::shared_ptr& simd) +{ + static const uint32_t capacities[7] = { 512, 1024, 2048, 2048, 1024, 512, 1024 }; + + const uint32_t texturesPerAtlas = DetermineMaxTextureArraySize(device); + D2DX_LOG("The device supports %u textures per atlas.", texturesPerAtlas); + + uint32_t totalSize = 0; + for (int32_t i = 0; i < ARRAYSIZE(_textureCaches); ++i) + { + int32_t width = 1U << (i + 3); + int32_t height = 1U << (i + 3); + + if (i == 6) + { + width = 256; + height = 128; + } + + _textureCaches[i] = std::make_unique(width, height, capacities[i], texturesPerAtlas, device, simd); + + D2DX_DEBUG_LOG("Creating texture cache for %i x %i with capacity %u (%u kB).", width, height, capacities[i], _textureCaches[i]->GetMemoryFootprint() / 1024); + + totalSize += _textureCaches[i]->GetMemoryFootprint(); + } + + D2DX_LOG("Total size of texture caches is %u kB.", totalSize / 1024); +} + +_Use_decl_annotations_ +uint32_t RenderContextResources::DetermineMaxTextureArraySize( + ID3D11Device* device) +{ + for (uint32_t arraySize = 2048; arraySize > 512; arraySize /= 2) + { + CD3D11_TEXTURE2D_DESC desc{ + DXGI_FORMAT_R8G8B8A8_UNORM, + 16U, + 16U, + arraySize, + 1U, + D3D11_BIND_SHADER_RESOURCE, + D3D11_USAGE_DEFAULT + }; + + ComPtr tempTexture; + HRESULT hr = device->CreateTexture2D(&desc, nullptr, &tempTexture); + + if (SUCCEEDED(hr)) + { + return arraySize; + } + } + + return 512; +} + +_Use_decl_annotations_ +void RenderContextResources::CreateVideoTextures( + ID3D11Device* device) +{ + CD3D11_TEXTURE2D_DESC desc + { + DXGI_FORMAT_B8G8R8A8_UNORM, + 640, + 480, + 1U, + 1U, + D3D11_BIND_SHADER_RESOURCE, + D3D11_USAGE_DYNAMIC, + D3D11_CPU_ACCESS_WRITE + }; + + _videoTextureSize = { 640, 480 }; + + D2DX_CHECK_HR( + device->CreateTexture2D(&desc, NULL, &_videoTexture)); + + D2DX_CHECK_HR( + device->CreateShaderResourceView(_videoTexture.Get(), NULL, &_videoTextureSrv)); +} + +_Use_decl_annotations_ +void RenderContextResources::CreateFramebuffers( + Size framebufferSize, + ID3D11Device* device) +{ + DXGI_FORMAT renderTargetFormat = DXGI_FORMAT_R8G8B8A8_UNORM; + + UINT formatSupport = 0; + HRESULT hr = device->CheckFormatSupport(DXGI_FORMAT_R10G10B10A2_UNORM, &formatSupport); + if (SUCCEEDED(hr) && + (formatSupport & D3D11_FORMAT_SUPPORT_TEXTURE2D) && + (formatSupport & D3D11_FORMAT_SUPPORT_SHADER_LOAD) && + (formatSupport & D3D11_FORMAT_SUPPORT_RENDER_TARGET)) + { + D2DX_LOG("Using DXGI_FORMAT_R10G10B10A2_UNORM for the render buffer."); + renderTargetFormat = DXGI_FORMAT_R10G10B10A2_UNORM; + } + else + { + D2DX_LOG("Using DXGI_FORMAT_R8G8B8A8_UNORM for the render buffer."); + hr = S_OK; + } + + _framebufferSize = framebufferSize; + + CD3D11_TEXTURE2D_DESC desc + { + renderTargetFormat, + (UINT)framebufferSize.width, + (UINT)framebufferSize.height, + 1U, + 1U, + D3D11_BIND_SHADER_RESOURCE | D3D11_BIND_RENDER_TARGET, + D3D11_USAGE_DEFAULT + }; + + CD3D11_SHADER_RESOURCE_VIEW_DESC srvDesc + { + D3D11_SRV_DIMENSION_TEXTURE2D, + renderTargetFormat + }; + + D2DX_CHECK_HR( + device->CreateTexture2D( + &desc, + NULL, + &_framebuffers[(int32_t)RenderContextFramebuffer::Game].texture)); + + D2DX_CHECK_HR( + device->CreateShaderResourceView( + _framebuffers[(int32_t)RenderContextFramebuffer::Game].texture.Get(), + &srvDesc, + &_framebuffers[(int32_t)RenderContextFramebuffer::Game].srv)); + + CD3D11_RENDER_TARGET_VIEW_DESC rtvDesc{ + D3D11_RTV_DIMENSION_TEXTURE2D, + renderTargetFormat + }; + + D2DX_CHECK_HR( + device->CreateRenderTargetView( + _framebuffers[(int32_t)RenderContextFramebuffer::Game].texture.Get(), + &rtvDesc, + &_framebuffers[(int32_t)RenderContextFramebuffer::Game].rtv)); + + desc.Format = DXGI_FORMAT_R8G8B8A8_TYPELESS; + D2DX_CHECK_HR( + device->CreateTexture2D( + &desc, + NULL, + &_framebuffers[(int32_t)RenderContextFramebuffer::GammaCorrected].texture)); + + srvDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; + D2DX_CHECK_HR( + device->CreateShaderResourceView( + _framebuffers[(int32_t)RenderContextFramebuffer::GammaCorrected].texture.Get(), + &srvDesc, + &_framebuffers[(int32_t)RenderContextFramebuffer::GammaCorrected].srv)); + + rtvDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; + D2DX_CHECK_HR( + device->CreateRenderTargetView( + _framebuffers[(int32_t)RenderContextFramebuffer::GammaCorrected].texture.Get(), + &rtvDesc, + &_framebuffers[(int32_t)RenderContextFramebuffer::GammaCorrected].rtv)); + + desc.Format = DXGI_FORMAT_R16_TYPELESS; + D2DX_CHECK_HR( + device->CreateTexture2D( + &desc, + NULL, + &_framebuffers[(int32_t)RenderContextFramebuffer::SurfaceId].texture)); + + srvDesc.Format = DXGI_FORMAT_R16_UNORM; + D2DX_CHECK_HR( + device->CreateShaderResourceView( + _framebuffers[(int32_t)RenderContextFramebuffer::SurfaceId].texture.Get(), + &srvDesc, + &_framebuffers[(int32_t)RenderContextFramebuffer::SurfaceId].srv)); + + rtvDesc.Format = DXGI_FORMAT_R16_UNORM; + D2DX_CHECK_HR( + device->CreateRenderTargetView( + _framebuffers[(int32_t)RenderContextFramebuffer::SurfaceId].texture.Get(), + &rtvDesc, + &_framebuffers[(int32_t)RenderContextFramebuffer::SurfaceId].rtv)); +} + +_Use_decl_annotations_ +void RenderContextResources::CreateRasterizerState( + ID3D11Device* device) +{ + CD3D11_RASTERIZER_DESC rasterizerDesc{ CD3D11_DEFAULT() }; + + rasterizerDesc.CullMode = D3D11_CULL_NONE; + rasterizerDesc.DepthClipEnable = false; + rasterizerDesc.FillMode = D3D11_FILL_SOLID; + rasterizerDesc.ScissorEnable = FALSE; + D2DX_CHECK_HR( + device->CreateRasterizerState(&rasterizerDesc, &_rasterizerStateNoScissor)); + + rasterizerDesc.ScissorEnable = TRUE; + D2DX_CHECK_HR( + device->CreateRasterizerState(&rasterizerDesc, &_rasterizerState)); +} + +_Use_decl_annotations_ +void RenderContextResources::CreateSamplerStates( + ID3D11Device* device) +{ + CD3D11_SAMPLER_DESC samplerDesc{ CD3D11_DEFAULT() }; + + samplerDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_POINT; + D2DX_CHECK_HR( + device->CreateSamplerState(&samplerDesc, _samplerState[0].GetAddressOf())); + + samplerDesc.Filter = D3D11_FILTER_MIN_MAG_LINEAR_MIP_POINT; + D2DX_CHECK_HR( + device->CreateSamplerState(&samplerDesc, _samplerState[1].GetAddressOf())); +} + +_Use_decl_annotations_ +void RenderContextResources::CreateBlendStates( + ID3D11Device* device) +{ + for (int32_t i = 0; i < (int32_t)AlphaBlend::Count; ++i) + { + AlphaBlend alphaBlend = (AlphaBlend)i; + + D3D11_BLEND_DESC blendDesc; + ZeroMemory(&blendDesc, sizeof(D3D11_BLEND_DESC)); + blendDesc.IndependentBlendEnable = TRUE; + + if (alphaBlend == AlphaBlend::Opaque) + { + blendDesc.RenderTarget[0].BlendEnable = TRUE; + blendDesc.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL; + blendDesc.RenderTarget[0].SrcBlend = D3D11_BLEND_ONE; + blendDesc.RenderTarget[0].DestBlend = D3D11_BLEND_ZERO; + blendDesc.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ONE; + blendDesc.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_ZERO; + blendDesc.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD; + blendDesc.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD; + + blendDesc.RenderTarget[1].BlendEnable = TRUE; + blendDesc.RenderTarget[1].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL; + blendDesc.RenderTarget[1].SrcBlend = D3D11_BLEND_ONE; + blendDesc.RenderTarget[1].DestBlend = D3D11_BLEND_ZERO; + blendDesc.RenderTarget[1].SrcBlendAlpha = D3D11_BLEND_ZERO; + blendDesc.RenderTarget[1].DestBlendAlpha = D3D11_BLEND_ZERO; + blendDesc.RenderTarget[1].BlendOp = D3D11_BLEND_OP_MAX; + blendDesc.RenderTarget[1].BlendOpAlpha = D3D11_BLEND_OP_ADD; + } + else if (alphaBlend == AlphaBlend::Additive) + { + blendDesc.RenderTarget[0].BlendEnable = TRUE; + blendDesc.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL; + blendDesc.RenderTarget[0].SrcBlend = D3D11_BLEND_ONE; + blendDesc.RenderTarget[0].DestBlend = D3D11_BLEND_ONE; + blendDesc.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ZERO; + blendDesc.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_ONE; + blendDesc.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD; + blendDesc.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD; + + blendDesc.RenderTarget[1].BlendEnable = TRUE; + blendDesc.RenderTarget[1].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL; + blendDesc.RenderTarget[1].SrcBlend = D3D11_BLEND_ZERO; + blendDesc.RenderTarget[1].DestBlend = D3D11_BLEND_ONE; + blendDesc.RenderTarget[1].SrcBlendAlpha = D3D11_BLEND_ZERO; + blendDesc.RenderTarget[1].DestBlendAlpha = D3D11_BLEND_ZERO; + blendDesc.RenderTarget[1].BlendOp = D3D11_BLEND_OP_ADD; + blendDesc.RenderTarget[1].BlendOpAlpha = D3D11_BLEND_OP_ADD; + } + else if (alphaBlend == AlphaBlend::Multiplicative) + { + blendDesc.RenderTarget[0].BlendEnable = TRUE; + blendDesc.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL; + blendDesc.RenderTarget[0].SrcBlend = D3D11_BLEND_ZERO; + blendDesc.RenderTarget[0].DestBlend = D3D11_BLEND_SRC_COLOR; + blendDesc.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ZERO; + blendDesc.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_ZERO; + blendDesc.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD; + blendDesc.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD; + + blendDesc.RenderTarget[1].BlendEnable = TRUE; + blendDesc.RenderTarget[1].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL; + blendDesc.RenderTarget[1].SrcBlend = D3D11_BLEND_ZERO; + blendDesc.RenderTarget[1].DestBlend = D3D11_BLEND_ONE; + blendDesc.RenderTarget[1].SrcBlendAlpha = D3D11_BLEND_ZERO; + blendDesc.RenderTarget[1].DestBlendAlpha = D3D11_BLEND_ZERO; + blendDesc.RenderTarget[1].BlendOp = D3D11_BLEND_OP_ADD; + blendDesc.RenderTarget[1].BlendOpAlpha = D3D11_BLEND_OP_ADD; + } + else if (alphaBlend == AlphaBlend::SrcAlphaInvSrcAlpha) + { + blendDesc.RenderTarget[0].BlendEnable = TRUE; + blendDesc.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL; + blendDesc.RenderTarget[0].SrcBlend = D3D11_BLEND_SRC_ALPHA; + blendDesc.RenderTarget[0].DestBlend = D3D11_BLEND_INV_SRC_ALPHA; + blendDesc.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ZERO; + blendDesc.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_ZERO; + blendDesc.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD; + blendDesc.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD; + + blendDesc.RenderTarget[1].BlendEnable = TRUE; + blendDesc.RenderTarget[1].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL; + blendDesc.RenderTarget[1].SrcBlend = D3D11_BLEND_ONE; + blendDesc.RenderTarget[1].DestBlend = D3D11_BLEND_ZERO; + blendDesc.RenderTarget[1].SrcBlendAlpha = D3D11_BLEND_ZERO; + blendDesc.RenderTarget[1].DestBlendAlpha = D3D11_BLEND_ZERO; + blendDesc.RenderTarget[1].BlendOp = D3D11_BLEND_OP_MAX; + blendDesc.RenderTarget[1].BlendOpAlpha = D3D11_BLEND_OP_ADD; + } + else + { + assert(false && "Unhandled alphablend."); + } + + D2DX_CHECK_HR( + device->CreateBlendState(&blendDesc, &_blendStates[i])); + } +} + +_Use_decl_annotations_ +void RenderContextResources::CreateVertexBuffer( + uint32_t vbSizeBytes, + ID3D11Device* device) +{ + const CD3D11_BUFFER_DESC vbDesc + { + vbSizeBytes, + D3D11_BIND_VERTEX_BUFFER, + D3D11_USAGE_DYNAMIC, + D3D11_CPU_ACCESS_WRITE + }; + + D2DX_CHECK_HR( + device->CreateBuffer(&vbDesc, NULL, &_vb)); +} + +_Use_decl_annotations_ +void RenderContextResources::CreateConstantBuffer( + uint32_t cbSizeBytes, + ID3D11Device* device) +{ + CD3D11_BUFFER_DESC desc + { + cbSizeBytes, + D3D11_BIND_CONSTANT_BUFFER, + D3D11_USAGE_DYNAMIC, + D3D11_CPU_ACCESS_WRITE + }; + + D2DX_CHECK_HR( + device->CreateBuffer(&desc, NULL, _cb.GetAddressOf())); +} diff --git a/src/d2dx/RenderContextResources.h b/src/d2dx/RenderContextResources.h new file mode 100644 index 0000000..2117848 --- /dev/null +++ b/src/d2dx/RenderContextResources.h @@ -0,0 +1,244 @@ +/* + This file is part of D2DX. + + Copyright (C) 2021 Bolrog + + D2DX is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + D2DX is distributed in the hope that it will be 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 D2DX. If not, see . +*/ +#pragma once + +#include "ITextureCache.h" +#include "Types.h" + +namespace d2dx +{ + enum class RenderContextSamplerState + { + Point = 0, + Bilinear = 1, + Count = 2 + }; + + enum class RenderContextVertexShader + { + Game = 0, + Display = 1, + Count = 2 + }; + + enum class RenderContextPixelShader + { + Game = 0, + Gamma = 1, + Video = 2, + DisplayIntegerScale = 3, + DisplayNonintegerScale = 4, + DisplayBilinearScale = 5, + DisplayCatmullRomScale = 6, + ResolveAA = 7, + Count = 8 + }; + + enum class RenderContextTexture1D + { + Palette = 0, + GammaTable = 1, + Count = 2, + }; + + enum class RenderContextFramebuffer + { + Game = 0, + GammaCorrected = 1, + SurfaceId = 2, + Count = 3, + }; + + class RenderContextResources final + { + public: + RenderContextResources( + _In_ uint32_t vbSizeBytes, + _In_ uint32_t cbSizeBytes, + _In_ Size framebufferSize, + _In_ ID3D11Device* device, + _In_ const std::shared_ptr& simd); + + virtual ~RenderContextResources() noexcept {} + + void OnNewFrame(); + + ID3D11InputLayout* GetInputLayout() const { return _inputLayout.Get(); } + + ID3D11VertexShader* GetVertexShader(RenderContextVertexShader vertexShader) const + { + return _vertexShaders[(int32_t)vertexShader].Get(); + } + + ID3D11PixelShader* GetPixelShader(RenderContextPixelShader pixelShader) const + { + return _pixelShaders[(int32_t)pixelShader].Get(); + } + + ITextureCache* GetTextureCache( + int32_t textureWidth, + int32_t textureHeight) const; + + ID3D11Texture1D* GetTexture1D(RenderContextTexture1D texture1d) const + { + return _texture1Ds[(int32_t)texture1d].texture.Get(); + } + + ID3D11ShaderResourceView* GetTexture1DSrv(RenderContextTexture1D texture1d) const + { + return _texture1Ds[(int32_t)texture1d].srv.Get(); + } + + Size GetVideoTextureSize() const + { + return _videoTextureSize; + } + + ID3D11Texture2D* GetVideoTexture() const + { + return _videoTexture.Get(); + } + + ID3D11ShaderResourceView* GetVideoSrv() const + { + return _videoTextureSrv.Get(); + } + + ID3D11RasterizerState* GetRasterizerState(bool enableScissor) const + { + return enableScissor ? _rasterizerState.Get() : _rasterizerStateNoScissor.Get(); + } + + ID3D11SamplerState* GetSamplerState(RenderContextSamplerState samplerState) const + { + return _samplerState[(int32_t)samplerState].Get(); + } + + ID3D11BlendState* GetBlendState(AlphaBlend alphaBlend) const + { + return _blendStates[(int32_t)alphaBlend].Get(); + } + + Size GetFramebufferSize() const + { + return _framebufferSize; + } + + ID3D11Texture2D* GetFramebufferTexture(RenderContextFramebuffer fb) const + { + return _framebuffers[(int32_t)fb].texture.Get(); + } + + ID3D11ShaderResourceView* GetFramebufferSrv(RenderContextFramebuffer fb) const + { + return _framebuffers[(int32_t)fb].srv.Get(); + } + + ID3D11RenderTargetView* GetFramebufferRtv(RenderContextFramebuffer fb) const + { + return _framebuffers[(int32_t)fb].rtv.Get(); + } + + ID3D11Buffer* GetVertexBuffer() const + { + return _vb.Get(); + } + + ID3D11Buffer* GetConstantBuffer() const + { + return _cb.Get(); + } + + private: + void CreateRasterizerState( + _In_ ID3D11Device* device); + + void CreateShadersAndInputLayout( + _In_ ID3D11Device* device); + + void CreateTexture1Ds( + _In_ ID3D11Device* device); + + uint32_t DetermineMaxTextureArraySize( + _In_ ID3D11Device* device); + + void CreateTextureCaches( + _In_ ID3D11Device* device, + _In_ const std::shared_ptr& simd); + + void CreateVideoTextures( + _In_ ID3D11Device* device); + + void CreateSamplerStates( + _In_ ID3D11Device* device); + + void CreateBlendStates( + _In_ ID3D11Device* device); + + void CreateFramebuffers( + _In_ Size framebufferSize, + _In_ ID3D11Device* device); + + void CreateVertexBuffer( + _In_ uint32_t vbSizeBytes, + _In_ ID3D11Device* device); + + void CreateConstantBuffer( + _In_ uint32_t cbSizeBytes, + _In_ ID3D11Device* device); + + ComPtr _inputLayout; + ComPtr _vertexShaders[(int32_t)RenderContextVertexShader::Count]; + ComPtr _pixelShaders[(int32_t)RenderContextPixelShader::Count]; + ComPtr _gammaPS; + ComPtr _resolveAAPS; + + struct + { + ComPtr texture; + ComPtr srv; + + } _texture1Ds[(int32_t)RenderContextTexture1D::Count]; + + Size _videoTextureSize; + ComPtr _videoTexture; + ComPtr _videoTextureSrv; + + std::unique_ptr _textureCaches[7]; + + ComPtr _rasterizerStateNoScissor; + ComPtr _rasterizerState; + + ComPtr _samplerState[2]; + + ComPtr _blendStates[(int32_t)AlphaBlend::Count]; + + struct { + ComPtr texture; + ComPtr rtv; + ComPtr srv; + + } _framebuffers[(int32_t)RenderContextFramebuffer::Count]; + + Size _framebufferSize; + + ComPtr _vb; + ComPtr _cb; + }; +} diff --git a/src/d2dx/ResolveAA.hlsl b/src/d2dx/ResolveAA.hlsl new file mode 100644 index 0000000..3b7a3a0 --- /dev/null +++ b/src/d2dx/ResolveAA.hlsl @@ -0,0 +1,90 @@ +/* + This file is part of D2DX. + + Copyright (C) 2021 Bolrog + + D2DX is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + D2DX is distributed in the hope that it will be 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 D2DX. If not, see . +*/ +#include "Constants.hlsli" +#include "Display.hlsli" + +//#define SHOW_SURFACE_IDS +//#define SHOW_MASK +//#define SHOW_AMPLIFIED_DIFFERENCE + +Texture2D sceneTexture : register(t0); +Texture2D idTexture : register(t1); + +#define FXAA_PC 1 +#define FXAA_HLSL_4 1 +#define FXAA_QUALITY__PRESET 23 +#include "FXAA.hlsli" + +float4 main( + in DisplayPSInput ps_in) : SV_TARGET +{ + float4 c = sceneTexture.SampleLevel(PointSampler, ps_in.tc, 0); + + /* + A B C + D E F + G H I + */ + + float2 tcShifted = ps_in.tc - 0.5 * ps_in.textureSize_invTextureSize.zw; + + float idC = idTexture.SampleLevel(PointSampler, ps_in.tc, 0, int2(1,-1)); + float4 idDEBA = idTexture.Gather(BilinearSampler, tcShifted); + float4 idHIFE = idTexture.Gather(BilinearSampler, tcShifted, int2(1, 1)); + float idG = idTexture.SampleLevel(PointSampler, ps_in.tc, 0, int2(-1, 1)); + + bool isEdge = + idDEBA.y < (1.0-1.0/16383.0) && (idG != idC || any(idDEBA - idC) || any(idHIFE - idC)); + + if (isEdge) + { +#ifdef SHOW_MASK + return float4(1, 0, 0, 1); +#else + FxaaTex ftx; + ftx.smpl = BilinearSampler; + ftx.tex = sceneTexture; + +#ifdef SHOW_AMPLIFIED_DIFFERENCE + float4 oldc = c; +#endif + c = FxaaPixelShader(c, ps_in.tc, ftx, ps_in.textureSize_invTextureSize.zw, 0.5, 0.166, 0.166 * 0.5); +#ifdef SHOW_AMPLIFIED_DIFFERENCE + c.rgb = 0.5 + 4*(c.rgb - oldc.rgb); +#endif +#endif + } +#if defined(SHOW_MASK) || defined(SHOW_AMPLIFIED_DIFFERENCE) + else + { + return float4(0.5, 0.5, 0.5, 1); + } +#endif + +#ifdef SHOW_SURFACE_IDS + uint iid = (uint)(idTexture.SampleLevel(PointSampler, ps_in.tc, 0).x * 16383.0); + float3 idc; + idc.r = (iid & 31) / 31.0; + idc.g = ((iid >> 5) & 31) / 31.0; + idc.b = ((iid >> 10) & 15) / 15.0; + c.rgb = lerp(c.rgb, isEdge ? 1 - idc : idc, 0.5); +#endif + + return c; +} diff --git a/src/d2dx/SimdSse2.cpp b/src/d2dx/SimdSse2.cpp index 89f2131..96879e7 100644 --- a/src/d2dx/SimdSse2.cpp +++ b/src/d2dx/SimdSse2.cpp @@ -23,7 +23,10 @@ using namespace d2dx; using namespace std; _Use_decl_annotations_ -int32_t SimdSse2::IndexOfUInt32(const uint32_t* items, uint32_t itemsCount, uint32_t item) +int32_t SimdSse2::IndexOfUInt32( + const uint32_t* __restrict items, + uint32_t itemsCount, + uint32_t item) { assert(items && ((uintptr_t)items & 63) == 0); assert(!(itemsCount & 0x3F)); diff --git a/src/d2dx/SimdSse2.h b/src/d2dx/SimdSse2.h index 0b42444..8eb451d 100644 --- a/src/d2dx/SimdSse2.h +++ b/src/d2dx/SimdSse2.h @@ -18,13 +18,18 @@ */ #pragma once -#include "Simd.h" +#include "ISimd.h" namespace d2dx { - class SimdSse2 final : public Simd + class SimdSse2 final : public ISimd { public: - virtual int32_t IndexOfUInt32(_In_reads_(itemsCount) const uint32_t* __restrict items, uint32_t itemsCount, uint32_t item) override; + virtual ~SimdSse2() noexcept {} + + virtual int32_t IndexOfUInt32( + _In_reads_(itemsCount) const uint32_t* __restrict items, + _In_ uint32_t itemsCount, + _In_ uint32_t item) override; }; } diff --git a/src/d2dx/SurfaceIdTracker.cpp b/src/d2dx/SurfaceIdTracker.cpp new file mode 100644 index 0000000..f939c37 --- /dev/null +++ b/src/d2dx/SurfaceIdTracker.cpp @@ -0,0 +1,159 @@ +/* + This file is part of D2DX. + + Copyright (C) 2021 Bolrog + + D2DX is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + D2DX is distributed in the hope that it will be 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 D2DX. If not, see . +*/ +#include "pch.h" +#include "SurfaceIdTracker.h" +#include "Batch.h" +#include "Vertex.h" + +using namespace d2dx; + +_Use_decl_annotations_ +SurfaceIdTracker::SurfaceIdTracker( + const std::shared_ptr& gameHelper) : + _gameHelper{ gameHelper } +{ +} + +void SurfaceIdTracker::OnNewFrame() +{ + _nextSurfaceId = 0; + _previousDrawCallRect = { 0,0,0,0 }; + _previousDrawCallTexture = 0; + _previousSurfaceId = -1; +} + +_Use_decl_annotations_ +void SurfaceIdTracker::UpdateBatchSurfaceId( + Batch& batch, + MajorGameState majorGameState, + Size gameSize, + Vertex* batchVertices, + int32_t batchVerticesCount) +{ + uint32_t surfaceId = 0; + + uint64_t drawCallTexture = (uint64_t)batch.GetTextureIndex() | ((uint64_t)batch.GetTextureAtlas() << 32ULL); + + int32_t minx = INT_MAX; + int32_t miny = INT_MAX; + int32_t maxx = INT_MIN; + int32_t maxy = INT_MIN; + + for (uint32_t i = 0; i < batch.GetVertexCount(); ++i) + { + int32_t x = (int32_t)batchVertices[i].GetX(); + int32_t y = (int32_t)batchVertices[i].GetY(); + minx = min(minx, x); + miny = min(miny, y); + maxx = max(maxx, x); + maxy = max(maxy, y); + } + + if (majorGameState != MajorGameState::InGame) + { + surfaceId = D2DX_SURFACE_ID_USER_INTERFACE; + } + else if (!batch.IsChromaKeyEnabled()) + { + /* Text background rectangle. */ + surfaceId = D2DX_SURFACE_ID_USER_INTERFACE; + } + else + { + if (batch.GetGameAddress() == GameAddress::DrawLine && + (maxy - miny) == 1) + { + surfaceId = D2DX_SURFACE_ID_USER_INTERFACE; + } + else if (miny >= (gameSize.height - 48)) + { + surfaceId = D2DX_SURFACE_ID_USER_INTERFACE; + } + else if ((_gameHelper->ScreenOpenMode() & 1) && minx >= gameSize.width / 2) + { + surfaceId = D2DX_SURFACE_ID_USER_INTERFACE; + } + else if ((_gameHelper->ScreenOpenMode() & 2) && maxx <= gameSize.width / 2) + { + surfaceId = D2DX_SURFACE_ID_USER_INTERFACE; + } + else + { + switch (batch.GetTextureCategory()) + { + case TextureCategory::MousePointer: + case TextureCategory::UserInterface: + surfaceId = D2DX_SURFACE_ID_USER_INTERFACE; + break; + default: + case TextureCategory::Floor: + case TextureCategory::Wall: + case TextureCategory::Unknown: + { + bool isNewSurface = true; + + if (_previousDrawCallTexture == drawCallTexture) + { + isNewSurface = false; + } + else if (batch.GetTextureWidth() == 32 && batch.GetTextureHeight() == 32) + { + if (/* 32x32 wall block drawn to the left of the previous one. */ + (maxx == _previousDrawCallRect.offset.x && miny == _previousDrawCallRect.offset.y) || + + /* 32x32 wall block drawn on the next row from the previous one. */ + (miny == (_previousDrawCallRect.offset.y + _previousDrawCallRect.size.height))) + { + isNewSurface = false; + } + } + + if (isNewSurface) + { + surfaceId = ++_nextSurfaceId; + } + else + { + surfaceId = _nextSurfaceId; + } + + break; + } + } + } + + } + + _previousSurfaceId = surfaceId; + + for (uint32_t i = 0; i < batch.GetVertexCount(); ++i) + { + batchVertices[i].SetSurfaceId(surfaceId); + } + _previousDrawCallTexture = drawCallTexture; + _previousDrawCallRect.offset.x = minx; + _previousDrawCallRect.offset.y = miny; + _previousDrawCallRect.size.width = maxx - minx; + _previousDrawCallRect.size.height = maxy - miny; +} + +int32_t SurfaceIdTracker::GetCurrentSurfaceId() const +{ + return _nextSurfaceId; +} diff --git a/src/d2dx/SurfaceIdTracker.h b/src/d2dx/SurfaceIdTracker.h new file mode 100644 index 0000000..4e74b28 --- /dev/null +++ b/src/d2dx/SurfaceIdTracker.h @@ -0,0 +1,53 @@ +/* + This file is part of D2DX. + + Copyright (C) 2021 Bolrog + + D2DX is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + D2DX is distributed in the hope that it will be 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 D2DX. If not, see . +*/ +#pragma once + +#include "Types.h" + +namespace d2dx +{ + class Batch; + class Vertex; + struct IGameHelper; + + class SurfaceIdTracker final + { + public: + SurfaceIdTracker( + _In_ const std::shared_ptr& gameHelper); + + void OnNewFrame(); + + void UpdateBatchSurfaceId( + _Inout_ Batch& batch, + _In_ MajorGameState majorGameState, + _In_ Size gameSize, + _Inout_updates_all_(batchVerticesCount) Vertex* batchVertices, + _In_ int32_t batchVerticesCount); + + int32_t GetCurrentSurfaceId() const; + + private: + std::shared_ptr _gameHelper; + int32_t _nextSurfaceId = 0; + int32_t _previousSurfaceId = -1; + Rect _previousDrawCallRect = { 0,0,0,0 }; + uint64_t _previousDrawCallTexture = 0; + }; +} diff --git a/src/d2dx/TextMotionPredictor.cpp b/src/d2dx/TextMotionPredictor.cpp new file mode 100644 index 0000000..5b13593 --- /dev/null +++ b/src/d2dx/TextMotionPredictor.cpp @@ -0,0 +1,158 @@ +/* + This file is part of D2DX. + + Copyright (C) 2021 Bolrog + + D2DX is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + D2DX is distributed in the hope that it will be 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 D2DX. If not, see . +*/ +#include "pch.h" +#include "TextMotionPredictor.h" + +using namespace d2dx; +using namespace DirectX; + +_Use_decl_annotations_ +TextMotionPredictor::TextMotionPredictor( + const std::shared_ptr& gameHelper) : + _gameHelper{ gameHelper }, + _textMotions{ 128, true }, + _textsCount{ 0 }, + _frame{ 0 } +{ +} + +_Use_decl_annotations_ +void TextMotionPredictor::Update( + IRenderContext* renderContext) +{ + renderContext->GetCurrentMetrics(&_gameSize, nullptr, nullptr); + + const float dt = renderContext->GetFrameTime(); + int32_t expiredTextIndex = -1; + + for (int32_t i = 0; i < _textsCount; ++i) + { + TextMotion& tm = _textMotions.items[i]; + + if (!tm.id) + { + expiredTextIndex = i; + continue; + } + + if (abs((int64_t)_frame - (int64_t)tm.lastUsedFrame) > 2) + { + tm.id = 0; + expiredTextIndex = i; + continue; + } + + OffsetF targetVec = tm.targetPos - tm.currentPos; + float targetDistance = targetVec.Length(); + targetVec.Normalize(); + + float step = min(dt * 30.0f * targetDistance, targetDistance); + + auto moveVec = targetVec * step; + + tm.currentPos += moveVec; + } + + // Gradually (one change per frame) compact the list. + if (_textsCount > 0) + { + if (!_textMotions.items[_textsCount - 1].id) + { + // The last entry is expired. Shrink the list. + _textMotions.items[_textsCount - 1] = { }; + --_textsCount; + } + else if (expiredTextIndex >= 0 && expiredTextIndex < (_textsCount - 1)) + { + // Some entry is expired. Move the last entry to that place, and shrink the list. + _textMotions.items[expiredTextIndex] = _textMotions.items[_textsCount - 1]; + _textMotions.items[_textsCount - 1] = { }; + --_textsCount; + } + } + + ++_frame; +} + +_Use_decl_annotations_ +Offset TextMotionPredictor::GetOffset( + uint64_t textId, + Offset posFromGame) +{ + OffsetF posFromGameF{ (float)posFromGame.x, (float)posFromGame.y }; + int32_t textIndex = -1; + + for (int32_t i = 0; i < _textsCount; ++i) + { + if (_textMotions.items[i].id == textId) + { + bool resetCurrentPos = false; + + if ((_gameHelper->ScreenOpenMode() & 1) && posFromGameF.x >= _gameSize.width / 2) + { + resetCurrentPos = true; + } + else if ((_gameHelper->ScreenOpenMode() & 2) && posFromGameF.x <= _gameSize.width / 2) + { + resetCurrentPos = true; + } + else + { + auto distance = (posFromGameF - _textMotions.items[i].targetPos).Length(); + if (distance > 32.0f) + { + resetCurrentPos = true; + } + } + + _textMotions.items[i].targetPos = posFromGameF; + if (resetCurrentPos) + { + _textMotions.items[i].currentPos = posFromGameF; + } + _textMotions.items[i].lastUsedFrame = _frame; + textIndex = i; + break; + } + } + + if (textIndex < 0) + { + if (_textsCount < (int32_t)_textMotions.capacity) + { + textIndex = _textsCount++; + _textMotions.items[textIndex].id = textId; + _textMotions.items[textIndex].targetPos = posFromGameF; + _textMotions.items[textIndex].currentPos = posFromGameF; + _textMotions.items[textIndex].lastUsedFrame = _frame; + } + else + { + D2DX_DEBUG_LOG("TMP: Too many texts."); + } + } + + if (textIndex < 0) + { + return { 0, 0 }; + } + + TextMotion& tm = _textMotions.items[textIndex]; + return { (int32_t)(tm.currentPos.x - posFromGame.x), (int32_t)(tm.currentPos.y - posFromGame.y) }; +} diff --git a/src/d2dx/TextMotionPredictor.h b/src/d2dx/TextMotionPredictor.h new file mode 100644 index 0000000..31f616c --- /dev/null +++ b/src/d2dx/TextMotionPredictor.h @@ -0,0 +1,55 @@ +/* + This file is part of D2DX. + + Copyright (C) 2021 Bolrog + + D2DX is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + D2DX is distributed in the hope that it will be 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 D2DX. If not, see . +*/ +#pragma once + +#include "IGameHelper.h" +#include "IRenderContext.h" + +namespace d2dx +{ + class TextMotionPredictor + { + public: + TextMotionPredictor( + _In_ const std::shared_ptr& gameHelper); + + void Update( + _In_ IRenderContext* renderContext); + + Offset GetOffset( + _In_ uint64_t textId, + _In_ Offset posFromGame); + + private: + struct TextMotion final + { + uint64_t id = 0; + uint32_t lastUsedFrame = 0; + OffsetF targetPos = { 0, 0 }; + OffsetF currentPos = { 0, 0 }; + int64_t dtLastPosChange = 0; + }; + + std::shared_ptr _gameHelper; + uint32_t _frame = 0; + Buffer _textMotions; + int32_t _textsCount; + Size _gameSize; + }; +} diff --git a/src/d2dx/TextureCache.cpp b/src/d2dx/TextureCache.cpp index dc1e67b..b374efb 100644 --- a/src/d2dx/TextureCache.cpp +++ b/src/d2dx/TextureCache.cpp @@ -25,17 +25,24 @@ using namespace d2dx; using namespace std; -TextureCache::TextureCache(int32_t width, int32_t height, uint32_t capacity, uint32_t texturesPerAtlas, ID3D11Device* device, shared_ptr simd, shared_ptr textureProcessor) : - _width{ width }, - _height{ height }, - _capacity{ capacity }, - _texturesPerAtlas{ texturesPerAtlas }, - _atlasCount{ (int32_t)max(1, capacity / texturesPerAtlas) }, - _textureProcessor(textureProcessor), - _policy(make_unique(capacity, simd)) -{ +_Use_decl_annotations_ +TextureCache::TextureCache( + int32_t width, + int32_t height, + uint32_t capacity, + uint32_t texturesPerAtlas, + ID3D11Device* device, + const std::shared_ptr& simd) +{ assert(_atlasCount <= 4); + _width = width; + _height = height; + _capacity = capacity; + _texturesPerAtlas = texturesPerAtlas; + _atlasCount = (int32_t)max(1, capacity / texturesPerAtlas); + _policy = TextureCachePolicyBitPmru(capacity, simd); + #ifndef D2DX_UNITTEST CD3D11_TEXTURE2D_DESC desc @@ -49,13 +56,12 @@ TextureCache::TextureCache(int32_t width, int32_t height, uint32_t capacity, uin D3D11_USAGE_DEFAULT }; - HRESULT hr = S_OK; uint32_t capacityPerPartition = _capacity; for (int32_t partition = 0; partition < 4; ++partition) { - D2DX_RELEASE_CHECK_HR(device->CreateTexture2D(&desc, nullptr, &_textures[partition])); - D2DX_RELEASE_CHECK_HR(device->CreateShaderResourceView(_textures[partition].Get(), NULL, _srvs[partition].GetAddressOf())); + D2DX_CHECK_HR(device->CreateTexture2D(&desc, nullptr, &_textures[partition])); + D2DX_CHECK_HR(device->CreateShaderResourceView(_textures[partition].Get(), NULL, _srvs[partition].GetAddressOf())); } device->GetImmediateContext(&_deviceContext); @@ -68,9 +74,12 @@ uint32_t TextureCache::GetMemoryFootprint() const return _width * _height * _texturesPerAtlas * _atlasCount; } -TextureCacheLocation TextureCache::FindTexture(uint32_t contentKey, int32_t lastIndex) +_Use_decl_annotations_ +TextureCacheLocation TextureCache::FindTexture( + uint32_t contentKey, + int32_t lastIndex) { - const int32_t index = _policy->Find(contentKey, lastIndex); + const int32_t index = _policy.Find(contentKey, lastIndex); if (index < 0) { @@ -81,57 +90,79 @@ TextureCacheLocation TextureCache::FindTexture(uint32_t contentKey, int32_t last } _Use_decl_annotations_ -TextureCacheLocation TextureCache::InsertTexture(uint32_t contentKey, const Batch& batch, const uint8_t* tmuData) +TextureCacheLocation TextureCache::InsertTexture( + uint32_t contentKey, + const Batch& batch, + const uint8_t* tmuData, + uint32_t tmuDataSize) { - assert(batch.IsValid() && batch.GetWidth() > 0 && batch.GetHeight() > 0); + assert(batch.IsValid() && batch.GetTextureWidth() > 0 && batch.GetTextureHeight() > 0); bool evicted = false; - int32_t replacementIndex = _policy->Insert(contentKey, evicted); + int32_t replacementIndex = _policy.Insert(contentKey, evicted); if (evicted) { - DEBUG_PRINT("Evicted %ix%i texture %i from cache.", batch.GetWidth(), batch.GetHeight(), replacementIndex); + D2DX_DEBUG_LOG("Evicted %ix%i texture %i from cache.", batch.GetTextureWidth(), batch.GetTextureHeight(), replacementIndex); } #ifndef D2DX_UNITTEST CD3D11_BOX box; box.left = 0; box.top = 0; - box.right = batch.GetWidth(); - box.bottom = batch.GetHeight(); + box.right = batch.GetTextureWidth(); + box.bottom = batch.GetTextureHeight(); box.front = 0; box.back = 1; const uint8_t* pData = tmuData + batch.GetTextureStartAddress(); - _deviceContext->UpdateSubresource(_textures[replacementIndex / _texturesPerAtlas].Get(), replacementIndex & (_texturesPerAtlas - 1), &box, pData, batch.GetWidth(), 0); + _deviceContext->UpdateSubresource(_textures[replacementIndex / _texturesPerAtlas].Get(), replacementIndex & (_texturesPerAtlas - 1), &box, pData, batch.GetTextureWidth(), 0); #endif return { (int16_t)(replacementIndex / _texturesPerAtlas), (int16_t)(replacementIndex & (_texturesPerAtlas - 1)) }; } -uint32_t TextureCache::GetCapacity() const +_Use_decl_annotations_ +ID3D11ShaderResourceView* TextureCache::GetSrv( + uint32_t textureAtlas) const { - return _capacity; + assert(textureAtlas >= 0 && textureAtlas < (uint32_t)_atlasCount); + return _srvs[textureAtlas].Get(); } -uint32_t TextureCache::GetTexturesPerAtlas() const +void TextureCache::OnNewFrame() { - return _texturesPerAtlas; + _policy.OnNewFrame(); } -ID3D11Texture2D* TextureCache::GetTexture(uint32_t atlasIndex) const +_Use_decl_annotations_ +void TextureCache::CopyPixels( + int32_t srcWidth, + int32_t srcHeight, + const uint8_t* __restrict srcPixels, + uint32_t srcPitch, + uint8_t* __restrict dstPixels, + uint32_t dstPitch) { - return _textures[atlasIndex / _texturesPerAtlas].Get(); -} + const int32_t srcSkip = srcPitch - srcWidth; + const int32_t dstSkip = dstPitch - srcWidth; -ID3D11ShaderResourceView* TextureCache::GetSrv(uint32_t textureAtlas) const -{ - assert(textureAtlas >= 0 && textureAtlas < _atlasCount); - return _srvs[textureAtlas].Get(); + assert(srcSkip >= 0); + assert(dstSkip >= 0); + + for (int32_t y = 0; y < srcHeight; ++y) + { + for (int32_t x = 0; x < srcWidth; ++x) + { + *dstPixels++ = *srcPixels++; + } + srcPixels += srcSkip; + dstPixels += dstSkip; + } } -void TextureCache::OnNewFrame() +uint32_t TextureCache::GetUsedCount() const { - _policy->OnNewFrame(); + return _policy.GetUsedCount(); } diff --git a/src/d2dx/TextureCache.h b/src/d2dx/TextureCache.h index 0101a81..d6355d8 100644 --- a/src/d2dx/TextureCache.h +++ b/src/d2dx/TextureCache.h @@ -18,56 +18,60 @@ */ #pragma once -#include "Utils.h" -#include "TextureProcessor.h" -#include "Simd.h" -#include "TextureCachePolicy.h" +#include "ITextureCache.h" +#include "TextureCachePolicyBitPmru.h" namespace d2dx { - class Batch; - - struct TextureCacheLocation final - { - int16_t _textureAtlas; - int16_t _textureIndex; - }; - - class TextureCache final + class TextureCache final : public ITextureCache { public: TextureCache( - int32_t width, - int32_t height, - uint32_t capacity, - uint32_t texturesPerAtlas, - ID3D11Device* device, - std::shared_ptr simd, - std::shared_ptr textureProcessor); - - void OnNewFrame(); + _In_ int32_t width, + _In_ int32_t height, + _In_ uint32_t capacity, + _In_ uint32_t texturesPerAtlas, + _In_ ID3D11Device* device, + _In_ const std::shared_ptr& simd); + + virtual ~TextureCache() noexcept {} - TextureCacheLocation FindTexture(uint32_t contentKey, int32_t lastIndex); + virtual void OnNewFrame() override; - TextureCacheLocation InsertTexture(uint32_t contentKey, const Batch& batch, _In_reads_(D2DX_TMU_MEMORY_SIZE) const uint8_t* tmuData); + virtual TextureCacheLocation FindTexture( + _In_ uint32_t contentKey, + _In_ int32_t lastIndex) override; - uint32_t GetCapacity() const; - uint32_t GetTexturesPerAtlas() const; - ID3D11Texture2D* GetTexture(uint32_t atlasIndex) const; - ID3D11ShaderResourceView* GetSrv(uint32_t atlasIndex) const; - uint32_t GetMemoryFootprint() const; + virtual TextureCacheLocation InsertTexture( + _In_ uint32_t contentKey, + _In_ const Batch& batch, + _In_reads_(tmuDataSize) const uint8_t* tmuData, + _In_ uint32_t tmuDataSize) override; + + virtual ID3D11ShaderResourceView* GetSrv( + _In_ uint32_t atlasIndex) const override; + + virtual uint32_t GetMemoryFootprint() const override; + + virtual uint32_t GetUsedCount() const override; private: - int32_t _width; - int32_t _height; - uint32_t _capacity; - uint32_t _texturesPerAtlas; - int32_t _atlasCount; + void CopyPixels( + _In_ int32_t srcWidth, + _In_ int32_t srcHeight, + _In_reads_(srcPitch* srcHeight) const uint8_t* __restrict srcPixels, + _In_ uint32_t srcPitch, + _Out_writes_all_(dstPitch* srcHeight) uint8_t* __restrict dstPixels, + _In_ uint32_t dstPitch); - Microsoft::WRL::ComPtr _deviceContext; - Microsoft::WRL::ComPtr _textures[4]; - Microsoft::WRL::ComPtr _srvs[4]; - std::shared_ptr _textureProcessor; - std::unique_ptr _policy; + int32_t _width = 0; + int32_t _height = 0; + uint32_t _capacity = 0; + uint32_t _texturesPerAtlas = 0; + int32_t _atlasCount = 0; + ComPtr _deviceContext; + ComPtr _textures[4]; + ComPtr _srvs[4]; + TextureCachePolicyBitPmru _policy; }; } diff --git a/src/d2dx/TextureCachePolicyBitPmru.cpp b/src/d2dx/TextureCachePolicyBitPmru.cpp index e335527..51db4fd 100644 --- a/src/d2dx/TextureCachePolicyBitPmru.cpp +++ b/src/d2dx/TextureCachePolicyBitPmru.cpp @@ -19,31 +19,36 @@ #include "pch.h" #include "Utils.h" #include "TextureCachePolicyBitPmru.h" -#include "Simd.h" +#include "ISimd.h" using namespace d2dx; -TextureCachePolicyBitPmru::TextureCachePolicyBitPmru(uint32_t capacity, std::shared_ptr simd) : - _capacity(capacity), - _contentKeys(capacity), - _usedInFrameBits(capacity >> 5), - _mruBits(capacity >> 5), - _simd(simd) +_Use_decl_annotations_ +TextureCachePolicyBitPmru::TextureCachePolicyBitPmru( + uint32_t capacity, + const std::shared_ptr& simd) : + _capacity{ capacity }, + _contentKeys{ capacity, true }, + _usedInFrameBits{ capacity >> 5, true }, + _mruBits{ capacity >> 5, true }, + _simd{ simd } { assert(!(capacity & 63)); - memset(_contentKeys.items, 0, sizeof(uint32_t) * _contentKeys.capacity); - memset(_usedInFrameBits.items, 0, sizeof(uint32_t) * _usedInFrameBits.capacity); - memset(_mruBits.items, 0, sizeof(uint32_t) * _mruBits.capacity); + assert(simd); } -TextureCachePolicyBitPmru::~TextureCachePolicyBitPmru() -{ -} - -int32_t TextureCachePolicyBitPmru::Find(uint32_t contentKey, int32_t lastIndex) +_Use_decl_annotations_ +int32_t TextureCachePolicyBitPmru::Find( + uint32_t contentKey, + int32_t lastIndex) { assert(contentKey != 0); + if (_capacity == 0) + { + return -1; + } + if (lastIndex >= 0 && lastIndex < (int32_t)_capacity && contentKey == _contentKeys.items[lastIndex]) { @@ -64,8 +69,17 @@ int32_t TextureCachePolicyBitPmru::Find(uint32_t contentKey, int32_t lastIndex) return -1; } -int32_t TextureCachePolicyBitPmru::Insert(uint32_t contentKey, bool& evicted) +_Use_decl_annotations_ +int32_t TextureCachePolicyBitPmru::Insert( + uint32_t contentKey, + bool& evicted) { + if (_capacity == 0) + { + evicted = false; + return -1; + } + int32_t replacementIndex = -1; for (uint32_t i = 0; i < _mruBits.capacity; ++i) @@ -95,7 +109,7 @@ int32_t TextureCachePolicyBitPmru::Insert(uint32_t contentKey, bool& evicted) if (replacementIndex < 0) { - ALWAYS_PRINT("All texture atlas entries used in a single frame, starting over!"); + D2DX_LOG("All texture atlas entries used in a single frame, starting over!"); memset(_mruBits.items, 0, sizeof(uint32_t) * _mruBits.capacity); memset(_usedInFrameBits.items, 0, sizeof(uint32_t) * _usedInFrameBits.capacity); @@ -115,6 +129,11 @@ int32_t TextureCachePolicyBitPmru::Insert(uint32_t contentKey, bool& evicted) evicted = _contentKeys.items[replacementIndex] != 0; + if (!evicted) + { + ++_usedCount; + } + _contentKeys.items[replacementIndex] = contentKey; return replacementIndex; @@ -123,4 +142,9 @@ int32_t TextureCachePolicyBitPmru::Insert(uint32_t contentKey, bool& evicted) void TextureCachePolicyBitPmru::OnNewFrame() { memset(_usedInFrameBits.items, 0, sizeof(uint32_t) * _usedInFrameBits.capacity); -} \ No newline at end of file +} + +uint32_t TextureCachePolicyBitPmru::GetUsedCount() const +{ + return _usedCount; +} diff --git a/src/d2dx/TextureCachePolicyBitPmru.h b/src/d2dx/TextureCachePolicyBitPmru.h index 85e80dd..e158ee3 100644 --- a/src/d2dx/TextureCachePolicyBitPmru.h +++ b/src/d2dx/TextureCachePolicyBitPmru.h @@ -19,27 +19,39 @@ #pragma once #include "Buffer.h" -#include "TextureCachePolicy.h" +#include "ISimd.h" namespace d2dx { - class Simd; - - class TextureCachePolicyBitPmru final : public TextureCachePolicy + class TextureCachePolicyBitPmru final { public: - TextureCachePolicyBitPmru(uint32_t capacity, std::shared_ptr simd); - virtual ~TextureCachePolicyBitPmru(); - - virtual int32_t Find(uint32_t contentKey, int32_t lastIndex) override; - virtual int32_t Insert(uint32_t contentKey, bool& evicted) override; - virtual void OnNewFrame() override; + TextureCachePolicyBitPmru() = default; + TextureCachePolicyBitPmru& operator=(TextureCachePolicyBitPmru&& rhs) = default; + + TextureCachePolicyBitPmru( + _In_ uint32_t capacity, + _In_ const std::shared_ptr& simd); + ~TextureCachePolicyBitPmru() noexcept {} + + int32_t Find( + _In_ uint32_t contentKey, + _In_ int32_t lastIndex); + + int32_t Insert( + _In_ uint32_t contentKey, + _Out_ bool& evicted); + + void OnNewFrame(); + + uint32_t GetUsedCount() const; private: - uint32_t _capacity; - std::shared_ptr _simd; + uint32_t _capacity = 0; + std::shared_ptr _simd; Buffer _contentKeys; Buffer _usedInFrameBits; Buffer _mruBits; + uint32_t _usedCount = 0; }; } diff --git a/src/d2dx/TextureHasher.cpp b/src/d2dx/TextureHasher.cpp new file mode 100644 index 0000000..01d3d08 --- /dev/null +++ b/src/d2dx/TextureHasher.cpp @@ -0,0 +1,71 @@ +/* + This file is part of D2DX. + + Copyright (C) 2021 Bolrog + + D2DX is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + D2DX is distributed in the hope that it will be 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 D2DX. If not, see . +*/ +#include "pch.h" +#include "TextureHasher.h" +#include "Types.h" +#include "Utils.h" + +using namespace d2dx; + +TextureHasher::TextureHasher() : + _cache{ D2DX_TMU_MEMORY_SIZE / 256, true }, + _cacheHits{ 0 }, + _cacheMisses{ 0 } +{ +} + +_Use_decl_annotations_ +void TextureHasher::Invalidate( + uint32_t startAddress) +{ + _cache.items[startAddress >> 8] = 0; +} + +_Use_decl_annotations_ +uint32_t TextureHasher::GetHash( + uint32_t startAddress, + const uint8_t* pixels, + uint32_t pixelsSize) +{ + assert((startAddress & 255) == 0); + + uint32_t hash = _cache.items[startAddress >> 8]; + + if (hash) + { + ++_cacheHits; + } + else + { + ++_cacheMisses; + hash = fnv_32a_buf((void*)pixels, pixelsSize, FNV1_32A_INIT); + _cache.items[startAddress >> 8] = hash; + } + + return hash; +} + +void TextureHasher::PrintStats() +{ + D2DX_DEBUG_LOG("Texture hash cache hits: %u (%i%%) misses %u", + _cacheHits, + (int32_t)(100.0f * (float)_cacheHits / (_cacheHits + _cacheMisses)), + _cacheMisses + ); +} \ No newline at end of file diff --git a/src/d2dx/TextureHasher.h b/src/d2dx/TextureHasher.h new file mode 100644 index 0000000..7c7190e --- /dev/null +++ b/src/d2dx/TextureHasher.h @@ -0,0 +1,46 @@ +/* + This file is part of D2DX. + + Copyright (C) 2021 Bolrog + + D2DX is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + D2DX is distributed in the hope that it will be 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 D2DX. If not, see . +*/ +#pragma once + +#include "Buffer.h" + +namespace d2dx +{ + class TextureHasher final + { + public: + TextureHasher(); + ~TextureHasher() noexcept {} + + void Invalidate( + _In_ uint32_t startAddress); + + uint32_t GetHash( + _In_ uint32_t startAddress, + _In_reads_(pixelsSize) const uint8_t* pixels, + _In_ uint32_t pixelsSize); + + void PrintStats(); + + private: + Buffer _cache; + uint32_t _cacheHits; + uint32_t _cacheMisses; + }; +} diff --git a/src/d2dx/TextureProcessor.cpp b/src/d2dx/TextureProcessor.cpp deleted file mode 100644 index b51beaa..0000000 --- a/src/d2dx/TextureProcessor.cpp +++ /dev/null @@ -1,287 +0,0 @@ -/* - This file is part of D2DX. - - Copyright (C) 2021 Bolrog - - D2DX is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - D2DX is distributed in the hope that it will be 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 D2DX. If not, see . -*/ -#include "pch.h" -#include "TextureProcessor.h" -#include "Utils.h" - -using namespace d2dx; - -TextureProcessor::TextureProcessor() -{ -} - -TextureProcessor::~TextureProcessor() -{ -} - -_Use_decl_annotations_ -void TextureProcessor::CopyPixels( - int32_t srcWidth, - int32_t srcHeight, - const uint8_t* __restrict srcPixels, - uint32_t srcPitch, - uint8_t* __restrict dstPixels, - uint32_t dstPitch) -{ - const int32_t srcSkip = srcPitch - srcWidth; - const int32_t dstSkip = dstPitch - srcWidth; - - assert(srcSkip >= 0); - assert(dstSkip >= 0); - - for (int32_t y = 0; y < srcHeight; ++y) - { - for (int32_t x = 0; x < srcWidth; ++x) - { - *dstPixels++ = *srcPixels++; - } - srcPixels += srcSkip; - dstPixels += dstSkip; - } -} - -_Use_decl_annotations_ -void TextureProcessor::ConvertToRGBA(int32_t width, int32_t height, const uint8_t* __restrict srcPixels, const uint32_t* __restrict palette, uint32_t* __restrict dstPixels) -{ - assert(width <= 256 && height <= 256); - const uint32_t pixelCount = (uint32_t)(width * height); - - for (uint32_t i = 0; i < pixelCount; ++i) - { - dstPixels[i] = palette[srcPixels[i]] | 0xFF000000; - } -} - -_Use_decl_annotations_ -void TextureProcessor::ConvertChromaKeyedToRGBA( - int32_t width, - int32_t height, - const uint8_t* __restrict srcPixels, - const uint32_t* __restrict palette, - uint32_t* __restrict dstPixels, - uint32_t dstPitch) -{ - assert(width <= 256 && height <= 256); - - uint32_t skip = dstPitch - width; - - const uint32_t pixelCount = (uint32_t)(width * height); - for (int32_t y = 0; y < height; ++y) - { - for (int32_t x = 0; x < width; ++x) - { - const uint32_t c32 = palette[*srcPixels++] | 0xFF000000; - *dstPixels++ = c32 == 0xFF000000 ? 0 : c32; - } - dstPixels += skip; - } -} - -_Use_decl_annotations_ -void TextureProcessor::DilateFloorTile( - int32_t width, - int32_t height, - const uint32_t* __restrict srcPixels, - uint32_t* __restrict dstPixels) -{ - static const int32_t padx[] = { 79, 77, 75, 73, 71, 69, 67, 65, 63, 61, 59, 57, 55, 53, 51, 49, 47, 45, 43, 41, 39, 37, 35, 33, 31, 29, 27, 25, 23, 21, 19, 17, 15, 13, 11, 9, 7, 5, 3, 1 }; - - for (int32_t y = 0; y < 40; ++y) - { - int32_t padxval = padx[y]; - uint32_t padPixel = srcPixels[padxval + y * width]; - - for (int32_t x = 0; x < padxval; ++x) - { - dstPixels[x + y * width] = padPixel; - } - - for (int32_t x = padxval; x < (159 - padxval) + 1; ++x) - { - dstPixels[x + y * width] = srcPixels[x + y * width]; - } - - padPixel = srcPixels[(159 - padxval) + y * width]; - - for (int32_t x = (159 - padxval) + 1; x < 160; ++x) - { - dstPixels[x + y * width] = padPixel; - } - } - for (int32_t y = 40; y < 79; ++y) - { - int32_t padxval = padx[78 - y]; - - uint32_t padPixel = srcPixels[padxval + y * width]; - - for (int32_t x = 0; x < padxval; ++x) - { - dstPixels[x + y * width] = padPixel; - } - - for (int32_t x = padxval; x < (159 - padxval) + 1; ++x) - { - dstPixels[x + y * width] = srcPixels[x + y * width]; - } - - padPixel = srcPixels[(159 - padxval) + y * width]; - - for (int32_t x = (159 - padxval) + 1; x < 160; ++x) - { - dstPixels[x + y * width] = padPixel; - } - } -} - -_Use_decl_annotations_ -void TextureProcessor::Dilate( - int32_t width, - int32_t height, - uint32_t* __restrict pixels) -{ - const uint32_t pixelCount = (uint32_t)(width * height); - - for (int32_t y = 0; y < (height - 1); ++y) - { - uint32_t* pRow = &pixels[y * width]; - for (int32_t x = 0; x < (width - 1); ++x) - { - uint32_t c00 = pRow[x]; - uint32_t c10 = pRow[x + 1]; - uint32_t c11 = pRow[x + 1 + width]; - uint32_t c01 = pRow[x + width]; - - uint32_t a00 = c00 >> 24; - uint32_t a10 = c10 >> 24; - uint32_t a11 = c11 >> 24; - uint32_t a01 = c01 >> 24; - - if (a00 == 0) - { - if (a10 > a11) - { - if (a01 > a10) - { - c00 = c01; - } - else - { - c00 = c10; - } - } - else - { - c00 = c11; - } - - pRow[x] = c00; - } - } - } - - uint32_t* pRow = &pixels[(height - 1) * width]; - for (int32_t x = 0; x < (width - 1); ++x) - { - const uint32_t c00 = pRow[x]; - const uint32_t c10 = pRow[x + 1]; - const uint32_t a00 = c00 >> 24; - if (a00 == 0) - { - pRow[x] = c10; - } - } - - for (int32_t y = (height - 1); y >= 1; --y) - { - uint32_t* pRow = &pixels[y * width]; - for (int32_t x = (width - 1); x >= 1; --x) - { - uint32_t c00 = pRow[x]; - uint32_t c10 = pRow[x - 1]; - uint32_t c11 = pRow[x - 1 - width]; - uint32_t c01 = pRow[x - width]; - - uint32_t a00 = c00 >> 24; - uint32_t a10 = c10 >> 24; - uint32_t a11 = c11 >> 24; - uint32_t a01 = c01 >> 24; - - if (a00 == 0) - { - if (a10 > a11) - { - if (a01 > a10) - { - c00 = c01; - } - else - { - c00 = c10; - } - } - else - { - c00 = c11; - } - - pRow[x] = c00; - } - } - - pRow = pixels; - for (int32_t x = width - 1; x >= 1; --x) - { - const uint32_t c00 = pRow[x]; - const uint32_t c10 = pRow[x - 1]; - const uint32_t a00 = c00 >> 24; - if (a00 == 0) - { - pRow[x] = c10; - } - } - } -} - -_Use_decl_annotations_ -void TextureProcessor::CopyPixels( - int32_t width, - int32_t height, - const uint32_t* __restrict srcPixels, - uint32_t* __restrict dstPixels) -{ - const uint32_t pixelCount = (uint32_t)(width * height); - memcpy(dstPixels, srcPixels, pixelCount * sizeof(uint32_t)); -} - -_Use_decl_annotations_ -void TextureProcessor::CopyAlpha( - int32_t width, - int32_t height, - const uint32_t* __restrict srcPixels, - uint32_t* __restrict dstPixels) -{ - const uint32_t pixelCount = (uint32_t)(width * height); - - for (uint32_t i = 0; i < pixelCount; ++i) - { - const uint32_t a = srcPixels[i] & 0xFF000000; - const uint32_t c = dstPixels[i] & 0x00FFFFFF; - dstPixels[i] = c | a; - } -} diff --git a/src/d2dx/TextureProcessor.h b/src/d2dx/TextureProcessor.h deleted file mode 100644 index 89d4543..0000000 --- a/src/d2dx/TextureProcessor.h +++ /dev/null @@ -1,77 +0,0 @@ -/* - This file is part of D2DX. - - Copyright (C) 2021 Bolrog - - D2DX is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - D2DX is distributed in the hope that it will be 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 D2DX. If not, see . -*/ -#pragma once - -#include "Buffer.h" - -namespace d2dx -{ - class TextureProcessor final - { - public: - TextureProcessor(); - ~TextureProcessor(); - - void CopyPixels( - int32_t srcWidth, - int32_t srcHeight, - _In_reads_(srcPitch * srcHeight) const uint8_t* __restrict srcPixels, - uint32_t srcPitch, - _Out_writes_all_(dstPitch * srcHeight) uint8_t* __restrict dstPixels, - uint32_t dstPitch); - - void ConvertToRGBA( - int32_t width, - int32_t height, - _In_reads_(width * height) const uint8_t* __restrict srcPixels, - _In_reads_(256) const uint32_t* __restrict palette, - _Out_writes_all_(width * height) uint32_t* __restrict dstPixels); - - void ConvertChromaKeyedToRGBA( - int32_t width, - int32_t height, - _In_reads_(width* height) const uint8_t* __restrict srcPixels, - _In_reads_(256) const uint32_t* __restrict palette, - _Out_writes_all_(dstPitch * height) uint32_t* __restrict dstPixels, - uint32_t dstPitch); - - void Dilate( - int32_t width, - int32_t height, - _Inout_updates_all_(width * height) uint32_t* __restrict pixels); - - void DilateFloorTile( - int32_t width, - int32_t height, - _In_reads_(width * height) const uint32_t* __restrict srcPixels, - _Out_writes_all_(width * height) uint32_t* __restrict dstPixels); - - void CopyPixels( - int32_t width, - int32_t height, - _In_reads_(width * height) const uint32_t* __restrict srcPixels, - _Out_writes_all_(width * height) uint32_t* __restrict dstPixels); - - void CopyAlpha( - int32_t width, - int32_t height, - _In_reads_(width * height) const uint32_t* __restrict srcPixels, - _Inout_updates_all_(width * height) uint32_t* __restrict dstPixels); - }; -} diff --git a/src/d2dx/Types.h b/src/d2dx/Types.h index 2cd3d2b..98aad25 100644 --- a/src/d2dx/Types.h +++ b/src/d2dx/Types.h @@ -21,10 +21,16 @@ #define D2DX_TMU_ADDRESS_ALIGNMENT 256 #define D2DX_TMU_MEMORY_SIZE (16 * 1024 * 1024) #define D2DX_SIDE_TMU_MEMORY_SIZE (1 * 1024 * 1024) -#define D2DX_MAX_PALETTES 16 #define D2DX_MAX_BATCHES_PER_FRAME 16384 #define D2DX_MAX_VERTICES_PER_FRAME (1024 * 1024) +#define D2DX_MAX_GAME_PALETTES 14 +#define D2DX_WHITE_PALETTE_INDEX 14 +#define D2DX_LOGO_PALETTE_INDEX 15 +#define D2DX_MAX_PALETTES 16 + +#define D2DX_SURFACE_ID_USER_INTERFACE 16383 + namespace d2dx { static_assert(((D2DX_TMU_MEMORY_SIZE - 1) >> 8) == 0xFFFF, "TMU memory start addresses aren't 16 bit."); @@ -35,14 +41,6 @@ namespace d2dx FullscreenDefault = 1, }; - struct Options - { - ScreenMode screenMode; - bool skipLogo; - bool noVSync; - uint32_t defaultZoomLevel; - }; - enum class MajorGameState { Unknown = 0, @@ -79,7 +77,7 @@ namespace d2dx enum class AlphaCombine { One = 0, - Texture = 1, + FromColor = 1, Count = 2 }; @@ -87,7 +85,7 @@ namespace d2dx { Unknown = 0, MousePointer = 1, - Font = 2, + Player = 2, LoadingScreen = 3, Floor = 4, TitleScreen = 5, @@ -100,7 +98,7 @@ namespace d2dx { Unsupported = 0, Lod109d = 1, - Lod110 = 2, + Lod110f = 2, Lod112 = 3, Lod113c = 4, Lod113d = 5, @@ -116,7 +114,255 @@ namespace d2dx DrawShadow = 4, DrawDynamic = 5, DrawSomething1 = 6, - DrawSomething2 = 7, + DrawLine = 7, Count = 8, }; + + struct OffsetF final + { + float x = 0; + float y = 0; + + OffsetF(float x_, float y_) noexcept : + x{ x_ }, + y{ y_ } + { + } + + OffsetF& operator+=(const OffsetF& rhs) noexcept + { + x += rhs.x; + y += rhs.y; + return *this; + } + + OffsetF& operator-=(const OffsetF& rhs) noexcept + { + x -= rhs.x; + y -= rhs.y; + return *this; + } + + OffsetF& operator*=(const OffsetF& rhs) noexcept + { + x *= rhs.x; + y *= rhs.y; + return *this; + } + + OffsetF& operator+=(float rhs) noexcept + { + x += rhs; + y += rhs; + return *this; + } + + OffsetF& operator-=(float rhs) noexcept + { + x -= rhs; + y -= rhs; + return *this; + } + + OffsetF& operator*=(float rhs) noexcept + { + x *= rhs; + y *= rhs; + return *this; + } + + OffsetF operator+(const OffsetF& rhs) const noexcept + { + return { x + rhs.x, y + rhs.y }; + } + + OffsetF operator-(const OffsetF& rhs) const noexcept + { + return { x - rhs.x, y - rhs.y }; + } + + OffsetF operator*(const OffsetF& rhs) const noexcept + { + return { x * rhs.x, y * rhs.y }; + } + + OffsetF operator+(float rhs) const noexcept + { + return { x + rhs, y + rhs }; + } + + OffsetF operator-(float rhs) const noexcept + { + return { x - rhs, y - rhs }; + } + + OffsetF operator*(float rhs) const noexcept + { + return { x * rhs, y * rhs }; + } + + bool operator==(const OffsetF& rhs) const noexcept + { + return x == rhs.x && y == rhs.y; + } + + float Length() const noexcept + { + const float lensqr = x * x + y * y; + return lensqr > 0.01f ? sqrtf(lensqr) : 1.0f; + } + + void Normalize() noexcept + { + const float lensqr = x * x + y * y; + const float len = lensqr > 0.01f ? sqrtf(lensqr) : 1.0f; + const float invlen = 1.0f / len; + x *= invlen; + y *= invlen; + } + }; + + struct Offset final + { + int32_t x = 0; + int32_t y = 0; + + Offset(int32_t x_, int32_t y_) noexcept : + x{ x_ }, + y{ y_ } + { + } + + Offset(const OffsetF& rhs) noexcept : + x{ (int32_t)rhs.x }, + y{ (int32_t)rhs.y } + { + } + + Offset& operator+=(const Offset& rhs) noexcept + { + x += rhs.x; + y += rhs.y; + return *this; + } + + Offset& operator-=(const Offset& rhs) noexcept + { + x -= rhs.x; + y -= rhs.y; + return *this; + } + + Offset& operator*=(const Offset& rhs) noexcept + { + x *= rhs.x; + y *= rhs.y; + return *this; + } + + Offset& operator+=(int32_t rhs) noexcept + { + x += rhs; + y += rhs; + return *this; + } + + Offset& operator-=(int32_t rhs) noexcept + { + x -= rhs; + y -= rhs; + return *this; + } + + Offset& operator*=(int32_t rhs) noexcept + { + x *= rhs; + y *= rhs; + return *this; + } + + Offset operator+(const Offset& rhs) const noexcept + { + return { x + rhs.x, y + rhs.y }; + } + + Offset operator-(const Offset& rhs) const noexcept + { + return { x - rhs.x, y - rhs.y }; + } + + Offset operator*(const Offset& rhs) const noexcept + { + return { x * rhs.x, y * rhs.y }; + } + + Offset operator+(int32_t rhs) const noexcept + { + return { x + rhs, y + rhs }; + } + + Offset operator-(int32_t rhs) const noexcept + { + return { x - rhs, y - rhs }; + } + + Offset operator*(int32_t rhs) const noexcept + { + return { x * rhs, y * rhs }; + } + + bool operator==(const Offset& rhs) const noexcept + { + return x == rhs.x && y == rhs.y; + } + }; + + struct Size final + { + int32_t width = 0; + int32_t height = 0; + + Size operator*(int32_t value) noexcept + { + return { width * value, height * value }; + } + + Size operator*(uint32_t value) noexcept + { + return { width * (int32_t)value, height * (int32_t)value }; + } + + bool operator==(const Size& rhs) const noexcept + { + return width == rhs.width && height == rhs.height; + } + }; + + struct Rect final + { + Offset offset; + Size size; + + Rect() noexcept : + offset{ 0,0 }, + size{ 0,0 } + { + } + + Rect(int32_t x, int32_t y, int32_t w, int32_t h) noexcept : + offset{ x,y }, + size{ w,h } + { + } + + bool IsValid() const noexcept + { + return size.width > 0 && size.height > 0; + } + + bool operator==(const Rect& rhs) const noexcept + { + return offset == rhs.offset && size == rhs.size; + } + }; } diff --git a/src/d2dx/UnitMotionPredictor.cpp b/src/d2dx/UnitMotionPredictor.cpp new file mode 100644 index 0000000..d7f5ae0 --- /dev/null +++ b/src/d2dx/UnitMotionPredictor.cpp @@ -0,0 +1,253 @@ +/* + This file is part of D2DX. + + Copyright (C) 2021 Bolrog + + D2DX is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + D2DX is distributed in the hope that it will be 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 D2DX. If not, see . +*/ +#include "pch.h" +#include "UnitMotionPredictor.h" + +using namespace d2dx; +using namespace DirectX; + +_Use_decl_annotations_ +UnitMotionPredictor::UnitMotionPredictor( + const std::shared_ptr& gameHelper) : + _gameHelper{ gameHelper }, + _unitIdAndTypes{ 1024, true }, + _unitMotions{ 1024, true }, + _unitScreenPositions{ 1024, true } +{ +} + +_Use_decl_annotations_ +void UnitMotionPredictor::Update( + IRenderContext* renderContext) +{ + const int32_t dt = renderContext->GetFrameTimeFp(); + int32_t expiredUnitIndex = -1; + + for (int32_t i = 0; i < _unitsCount; ++i) + { + UnitIdAndType& uiat = _unitIdAndTypes.items[i]; + + if (!uiat.unitId) + { + expiredUnitIndex = i; + continue; + } + + auto unit = _gameHelper->FindUnit(uiat.unitId, (D2::UnitType)uiat.unitType); + + if (!unit) + { + uiat.unitId = 0; + expiredUnitIndex = i; + continue; + } + + UnitMotion& um = _unitMotions.items[i]; + const Offset pos = _gameHelper->GetUnitPos(unit); + + Offset posWhole{ pos.x >> 16, pos.y >> 16 }; + Offset lastPosWhole{ um.lastPos.x >> 16, um.lastPos.y >> 16 }; + Offset predictedPosWhole{ um.predictedPos.x >> 16, um.predictedPos.y >> 16 }; + + int32_t lastPosMd = max(abs(posWhole.x - lastPosWhole.x), abs(posWhole.y - lastPosWhole.y)); + int32_t predictedPosMd = max(abs(posWhole.x - predictedPosWhole.x), abs(posWhole.y - predictedPosWhole.y)); + + if (lastPosMd > 2 || predictedPosMd > 2) + { + um.predictedPos = pos; + um.correctedPos = pos; + um.lastPos = pos; + um.velocity = { 0,0 }; + } + + const int32_t dx = pos.x - um.lastPos.x; + const int32_t dy = pos.y - um.lastPos.y; + + um.dtLastPosChange += dt; + + if (dx != 0 || dy != 0 || um.dtLastPosChange >= (65536 / 25)) + { + um.correctedPos.x = ((int64_t)pos.x + um.lastPos.x) >> 1; + um.correctedPos.y = ((int64_t)pos.y + um.lastPos.y) >> 1; + //D2DX_DEBUG_LOG("Server %f %f", pos.x / 65536.0f, pos.y / 65536.0f); + + um.velocity.x = 25 * dx; + um.velocity.y = 25 * dy; + + um.lastPos = pos; + um.dtLastPosChange = 0; + } + + if (um.velocity.x != 0 || um.velocity.y != 0) + { + if (um.dtLastPosChange < (65536 / 25)) + { + Offset vStep{ + (int32_t)(((int64_t)dt * um.velocity.x) >> 16), + (int32_t)(((int64_t)dt * um.velocity.y) >> 16) }; + + const int32_t correctionAmount = 7000; + const int32_t oneMinusCorrectionAmount = 65536 - correctionAmount; + + um.predictedPos.x = (int32_t)(((int64_t)um.predictedPos.x * oneMinusCorrectionAmount + (int64_t)um.correctedPos.x * correctionAmount) >> 16); + um.predictedPos.y = (int32_t)(((int64_t)um.predictedPos.y * oneMinusCorrectionAmount + (int64_t)um.correctedPos.y * correctionAmount) >> 16); + //D2DX_DEBUG_LOG("Predicted %f %f", um.predictedPos.x / 65536.0f, um.predictedPos.y / 65536.0f); + + /* int32_t ex = um.correctedPos.x - um.predictedPos.x; + int32_t ey = um.correctedPos.y - um.predictedPos.y; + + if (unit->dwType == D2::UnitType::Player && (ex != 0 || ey != 0)) + { + D2DX_DEBUG_LOG("%f, %f, %f, %f, %f, %f, %f, %f", + pos.x / 65536.0f, + pos.y / 65536.0f, + um.correctedPos.x / 65536.0f, + um.correctedPos.y / 65536.0f, + um.predictedPos.x / 65536.0f, + um.predictedPos.y / 65536.0f, + ex / 65536.0f, + ey / 65536.0f); + }*/ + + um.predictedPos.x += vStep.x; + um.predictedPos.y += vStep.y; + + um.correctedPos.x += vStep.x; + um.correctedPos.y += vStep.y; + } + } + } + + // Gradually (one change per frame) compact the unit list. + if (_unitsCount > 1) + { + if (!_unitIdAndTypes.items[_unitsCount - 1].unitId) + { + // The last entry is expired. Shrink the list. + _unitMotions.items[_unitsCount - 1] = { }; + --_unitsCount; + } + else if (expiredUnitIndex >= 0 && expiredUnitIndex < (_unitsCount - 1)) + { + // Some entry is expired. Move the last entry to that place, and shrink the list. + _unitIdAndTypes.items[expiredUnitIndex] = _unitIdAndTypes.items[_unitsCount - 1]; + _unitMotions.items[expiredUnitIndex] = _unitMotions.items[_unitsCount - 1]; + _unitIdAndTypes.items[_unitsCount - 1] = { 0,0 }; + _unitMotions.items[_unitsCount - 1] = { }; + --_unitsCount; + } + } + + ++_frame; +} + +_Use_decl_annotations_ +Offset UnitMotionPredictor::GetOffset( + const D2::UnitAny* unit) +{ + int32_t unitIndex = -1; + + auto unitId = _gameHelper->GetUnitId(unit); + auto unitType = _gameHelper->GetUnitType(unit); + + for (int32_t i = 0; i < _unitsCount; ++i) + { + if (_unitIdAndTypes.items[i].unitId == (uint16_t)unitId && + _unitIdAndTypes.items[i].unitType == (uint16_t)unitType) + { + _unitMotions.items[i].lastUsedFrame = _frame; + unitIndex = i; + break; + } + } + + if (unitIndex < 0) + { + if (_unitsCount < (int32_t)_unitIdAndTypes.capacity) + { + unitIndex = _unitsCount++; + _unitIdAndTypes.items[unitIndex].unitId = (uint16_t)unitId; + _unitIdAndTypes.items[unitIndex].unitType = (uint16_t)unitType; + _unitMotions.items[unitIndex] = { }; + _unitMotions.items[unitIndex].lastUsedFrame = _frame; + } + else + { + D2DX_DEBUG_LOG("UMP: Too many units."); + } + } + + if (unitIndex < 0) + { + return { 0, 0 }; + } + + return _unitMotions.items[unitIndex].GetOffset(); +} + +_Use_decl_annotations_ +void UnitMotionPredictor::SetUnitScreenPos( + const D2::UnitAny* unit, + int32_t x, + int32_t y) +{ + auto unitId = _gameHelper->GetUnitId(unit); + auto unitType = _gameHelper->GetUnitType(unit); + + for (int32_t unitIndex = 0; unitIndex < _unitsCount; ++unitIndex) + { + if (_unitIdAndTypes.items[unitIndex].unitId == (uint16_t)unitId && + _unitIdAndTypes.items[unitIndex].unitType == (uint16_t)unitType) + { + _unitScreenPositions.items[unitIndex] = { x, y }; + break; + } + } +} + +_Use_decl_annotations_ +Offset UnitMotionPredictor::GetOffsetForShadow( + _In_ int32_t x, + _In_ int32_t y) +{ + for (int32_t i = 0; i < _unitsCount; ++i) + { + if (!_unitIdAndTypes.items[i].unitId) + { + continue; + } + + const int32_t dist = max(abs(_unitScreenPositions.items[i].x - x), abs(_unitScreenPositions.items[i].y - y)); + + if (dist < 8) + { + return _unitMotions.items[i].GetOffset(); + } + } + + return { 0, 0 }; +} + +Offset UnitMotionPredictor::UnitMotion::GetOffset() const +{ + const OffsetF offset{ (predictedPos.x - lastPos.x) / 65536.0f, (predictedPos.y - lastPos.y) / 65536.0f }; + const OffsetF scaleFactors{ 32.0f / sqrtf(2.0f), 16.0f / sqrtf(2.0f) }; + const OffsetF screenOffset = scaleFactors * OffsetF{ offset.x - offset.y, offset.x + offset.y } + 0.5f; + return { (int32_t)screenOffset.x, (int32_t)screenOffset.y }; +} diff --git a/src/d2dx/UnitMotionPredictor.h b/src/d2dx/UnitMotionPredictor.h new file mode 100644 index 0000000..54b18c0 --- /dev/null +++ b/src/d2dx/UnitMotionPredictor.h @@ -0,0 +1,73 @@ +/* + This file is part of D2DX. + + Copyright (C) 2021 Bolrog + + D2DX is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + D2DX is distributed in the hope that it will be 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 D2DX. If not, see . +*/ +#pragma once + +#include "IGameHelper.h" +#include "IRenderContext.h" + +namespace d2dx +{ + class UnitMotionPredictor final + { + public: + UnitMotionPredictor( + _In_ const std::shared_ptr& gameHelper); + + void Update( + _In_ IRenderContext* renderContext); + + Offset GetOffset( + _In_ const D2::UnitAny* unit); + + void SetUnitScreenPos( + _In_ const D2::UnitAny* unit, + _In_ int32_t x, + _In_ int32_t y); + + Offset GetOffsetForShadow( + _In_ int32_t x, + _In_ int32_t y); + + private: + struct UnitIdAndType final + { + uint16_t unitType = 0; + uint16_t unitId = 0; + }; + + struct UnitMotion final + { + Offset GetOffset() const; + + uint32_t lastUsedFrame = 0; + Offset lastPos = { 0, 0 }; + Offset velocity = { 0, 0 }; + Offset predictedPos = { 0, 0 }; + Offset correctedPos = { 0, 0 }; + int64_t dtLastPosChange = 0; + }; + + std::shared_ptr _gameHelper; + uint32_t _frame = 0; + Buffer _unitIdAndTypes; + Buffer _unitMotions; + Buffer _unitScreenPositions; + int32_t _unitsCount = 0; + }; +} diff --git a/src/d2dx/Utils.cpp b/src/d2dx/Utils.cpp index a2d2d08..c1f55f7 100644 --- a/src/d2dx/Utils.cpp +++ b/src/d2dx/Utils.cpp @@ -19,6 +19,14 @@ #include "pch.h" #include "Utils.h" +#define STB_IMAGE_WRITE_IMPLEMENTATION 1 +#include "../../thirdparty/stb_image/stb_image_write.h" + +#define POCKETLZMA_LZMA_C_DEFINE +#include "../../thirdparty/pocketlzma/pocketlzma.hpp" + +#pragma comment(lib, "Netapi32.lib") + using namespace d2dx; static bool _hasSetFreq = false; @@ -51,3 +59,278 @@ float d2dx::TimeEndMs(int64_t sinceThisTime) assert(_freq); return (float)(double(li.QuadPart - sinceThisTime) / _freq); } + +#define STATUS_SUCCESS (0x00000000) + +typedef NTSTATUS(WINAPI* RtlGetVersionPtr)(PRTL_OSVERSIONINFOW); + +static WindowsVersion* windowsVersion = nullptr; + +WindowsVersion d2dx::GetWindowsVersion() +{ + if (windowsVersion) + { + return *windowsVersion; + } + + HMODULE hMod = ::GetModuleHandleW(L"ntdll.dll"); + if (hMod) + { + RtlGetVersionPtr fxPtr = (RtlGetVersionPtr)::GetProcAddress(hMod, "RtlGetVersion"); + if (fxPtr != nullptr) + { + RTL_OSVERSIONINFOW rovi = { 0 }; + rovi.dwOSVersionInfoSize = sizeof(rovi); + if (STATUS_SUCCESS == fxPtr(&rovi)) + { + windowsVersion = new WindowsVersion(); + windowsVersion->major = rovi.dwMajorVersion; + windowsVersion->minor = rovi.dwMinorVersion; + windowsVersion->build = rovi.dwBuildNumber; + return *windowsVersion; + } + } + } + WindowsVersion wv = { 0,0,0 }; + return wv; +} + +WindowsVersion d2dx::GetActualWindowsVersion() +{ + LPSERVER_INFO_101 bufptr = nullptr; + + DWORD result = NetServerGetInfo(nullptr, 101, (LPBYTE*)&bufptr); + + if (!bufptr) + { + return { 0,0,0 }; + } + + WindowsVersion windowsVersion{ bufptr->sv101_version_major, bufptr->sv101_version_minor, 0 }; + NetApiBufferFree(bufptr); + return windowsVersion; +} + +static bool logFileOpened = false; +static FILE* logFile = nullptr; +static CRITICAL_SECTION logFileCS; + +static void EnsureLogFileOpened() +{ + if (!logFileOpened) + { + logFileOpened = true; + if (fopen_s(&logFile, "d2dx_log.txt", "w") != 0) + { + logFile = nullptr; + } + InitializeCriticalSection(&logFileCS); + } +} + +static DWORD WINAPI WriteToLogFileWorkItemFunc(PVOID pvContext) +{ + char* s = (char*)pvContext; + + OutputDebugStringA(s); + + EnterCriticalSection(&logFileCS); + + if (logFile) + { + fwrite(s, strlen(s), 1, logFile); + fflush(logFile); + } + + LeaveCriticalSection(&logFileCS); + + free(s); + + return 0; +} + +_Use_decl_annotations_ +void d2dx::detail::Log( + const char* s) +{ + EnsureLogFileOpened(); + QueueUserWorkItem(WriteToLogFileWorkItemFunc, _strdup(s), WT_EXECUTEDEFAULT); +} + +_Use_decl_annotations_ +Buffer d2dx::ReadTextFile( + const char* filename) +{ + FILE* cfgFile = nullptr; + + errno_t err = fopen_s(&cfgFile, "d2dx.cfg", "r"); + + if (err < 0 || !cfgFile) + { + Buffer str(1); + str.items[0] = 0; + return str; + } + + fseek(cfgFile, 0, SEEK_END); + + long size = ftell(cfgFile); + + if (size <= 0) + { + Buffer str(1); + str.items[0] = 0; + fclose(cfgFile); + return str; + } + + fseek(cfgFile, 0, SEEK_SET); + + Buffer str(size + 1, true); + + fread_s(str.items, str.capacity, size, 1, cfgFile); + + str.items[size] = 0; + + fclose(cfgFile); + + return str; +} + +_Use_decl_annotations_ +char* d2dx::detail::GetMessageForHRESULT( + HRESULT hr, + const char* func, + int32_t line) noexcept +{ + static Buffer buffer(4096); + auto msg = std::system_category().message(hr); + sprintf_s(buffer.items, buffer.capacity, "%s line %i\nHRESULT: 0x%08x\nMessage: %s", func, line, hr, msg.c_str()); + return buffer.items; +} + +_Use_decl_annotations_ +void d2dx::detail::ThrowFromHRESULT( + HRESULT hr, + const char* func, + int32_t line) +{ +#ifndef NDEBUG + __debugbreak(); +#endif + + throw ComException(hr, func, line); +} + +void d2dx::detail::FatalException() noexcept +{ + try + { + std::rethrow_exception(std::current_exception()); + } + catch (const std::exception& e) + { + D2DX_LOG("%s", e.what()); + MessageBoxA(nullptr, e.what(), "D2DX Fatal Error", MB_OK | MB_ICONSTOP); + TerminateProcess(GetCurrentProcess(), -1); + } +} + +_Use_decl_annotations_ +void d2dx::detail::FatalError( + const char* msg) noexcept +{ + D2DX_LOG("%s", msg); + MessageBoxA(nullptr, msg, "D2DX Fatal Error", MB_OK | MB_ICONSTOP); + TerminateProcess(GetCurrentProcess(), -1); +} + +_Use_decl_annotations_ +void d2dx::DumpTexture( + uint32_t hash, + int32_t w, + int32_t h, + const uint8_t* pixels, + uint32_t pixelsSize, + uint32_t textureCategory, + const uint32_t* palette) +{ + char s[256]; + + if (!std::filesystem::exists("dump")) + { + std::filesystem::create_directory("dump"); + } + + sprintf_s(s, "dump/%u", textureCategory); + + if (!std::filesystem::exists(s)) + { + std::filesystem::create_directory(s); + } + + sprintf_s(s, "dump/%u/%08x.bmp", textureCategory, hash); + + if (std::filesystem::exists(s)) + { + return; + } + + Buffer data(w * h * 4); + + for (int32_t j = 0; j < h; ++j) + { + for (int32_t i = 0; i < w; ++i) + { + uint8_t idx = pixels[i + j * w]; + uint32_t c = palette[idx] | 0xFF000000; + + c = (c & 0xFF00FF00) | ((c & 0xFF) << 16) | ((c >> 16) & 0xFF); + + data.items[i + j * w] = c; + } + } + stbi_write_bmp(s, w, h, 4, data.items); +} + +_Use_decl_annotations_ +bool d2dx::DecompressLZMAToFile( + const uint8_t* data, + uint32_t dataSize, + const char* filename) +{ + bool succeeded = true; + plz::PocketLzma p; + std::vector decompressed; + HANDLE file = nullptr; + DWORD bytesWritten = 0; + + auto status = p.decompress(data, dataSize, decompressed); + + if (status != plz::StatusCode::Ok) + { + succeeded = false; + goto end; + } + + file = CreateFileA(filename, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + if (!file) + { + succeeded = false; + goto end; + } + + if (!WriteFile(file, decompressed.data(), decompressed.size(), &bytesWritten, nullptr)) + { + succeeded = false; + goto end; + } + +end: + if (file) + { + CloseHandle(file); + } + + return succeeded; +} diff --git a/src/d2dx/Utils.h b/src/d2dx/Utils.h index 087052a..ac8d937 100644 --- a/src/d2dx/Utils.h +++ b/src/d2dx/Utils.h @@ -18,49 +18,63 @@ */ #pragma once +#include "ErrorHandling.h" +#include "Buffer.h" + namespace d2dx { + namespace detail + { + __declspec(noinline) void Log(_In_z_ const char* s); + } + int64_t TimeStart(); float TimeEndMs(int64_t start); + #ifdef NDEBUG -#define DEBUG_PRINT(fmt, ...) +#define D2DX_DEBUG_LOG(fmt, ...) #else -#define DEBUG_PRINT(fmt, ...) \ +#define D2DX_DEBUG_LOG(fmt, ...) \ { \ static char ss[256]; \ sprintf_s(ss, fmt "\n", __VA_ARGS__); \ - OutputDebugStringA(ss); \ + d2dx::detail::Log(ss); \ } #endif -#define ALWAYS_PRINT(fmt, ...) \ +#define D2DX_LOG(fmt, ...) \ { \ static char ssss[256]; \ sprintf_s(ssss, fmt "\n", __VA_ARGS__); \ - OutputDebugStringA(ssss); \ + d2dx::detail::Log(ssss); \ } - static void release_check(bool expr, const char* exprString) + struct WindowsVersion { - if (!expr) - { - OutputDebugStringA(exprString); - MessageBoxA(NULL, exprString, "Failed assertion", MB_OK); - PostQuitMessage(-1); - } - } + uint32_t major; + uint32_t minor; + uint32_t build; + }; - static void release_check_hr(HRESULT hr, const char* exprString) - { - if (FAILED(hr)) - { - OutputDebugStringA(exprString); - MessageBoxA(NULL, exprString, "Failed assertion", MB_OK); - PostQuitMessage(-1); - } - } + WindowsVersion GetWindowsVersion(); + + WindowsVersion GetActualWindowsVersion(); + + Buffer ReadTextFile( + _In_z_ const char* filename); + + void DumpTexture( + _In_ uint32_t hash, + _In_ int32_t w, + _In_ int32_t h, + _In_reads_(pixelsSize) const uint8_t* pixels, + _In_ uint32_t pixelsSize, + _In_ uint32_t textureCategory, + _In_reads_(256) const uint32_t* palette); - #define D2DX_RELEASE_CHECK(expr) release_check(expr, #expr) - #define D2DX_RELEASE_CHECK_HR(expr) release_check_hr(expr, #expr) + bool DecompressLZMAToFile( + _In_reads_(dataSize) const uint8_t* data, + _In_ uint32_t dataSize, + _In_z_ const char* filename); } diff --git a/src/d2dx/Vertex.h b/src/d2dx/Vertex.h index 9a39ab4..3514d3e 100644 --- a/src/d2dx/Vertex.h +++ b/src/d2dx/Vertex.h @@ -23,105 +23,121 @@ namespace d2dx class Vertex final { public: + Vertex() noexcept : + _x{ 0 }, + _y{ 0 }, + _s{ 0 }, + _t{ 0 }, + _color{ 0 }, + _isChromaKeyEnabled_surfaceId{ 0 }, + _paletteIndex_atlasIndex{ 0 } + { + } + Vertex( - float x, - float y, + int32_t x, + int32_t y, int32_t s, int32_t t, uint32_t color, - RgbCombine rgbCombine, - AlphaCombine alphaCombine, bool isChromaKeyEnabled, int32_t atlasIndex, - int32_t paletteIndex) : - _x(DirectX::PackedVector::XMConvertFloatToHalf(x)), - _y(DirectX::PackedVector::XMConvertFloatToHalf(y)), + int32_t paletteIndex, + int32_t surfaceId) noexcept : + _x(x), + _y(y), _s(s), _t(t), _color(color), - _paletteIndex_isChromaKeyEnabled_alphaCombine_rgbCombine((paletteIndex << 8) | (isChromaKeyEnabled ? (1U << 2U) : 0) | ((uint32_t)alphaCombine << 1U) | ((uint32_t)rgbCombine)), - _atlasIndex(atlasIndex) + _isChromaKeyEnabled_surfaceId((isChromaKeyEnabled ? 0x4000 : 0) | (surfaceId & 16383)), + _paletteIndex_atlasIndex((paletteIndex << 12) | (atlasIndex & 4095)) { + assert(x >= INT16_MIN && x <= INT16_MAX); + assert(y >= INT16_MIN && y <= INT16_MAX); assert(s >= INT16_MIN && s <= INT16_MAX); assert(t >= INT16_MIN && t <= INT16_MAX); + assert(paletteIndex >= 0 && paletteIndex < D2DX_MAX_PALETTES); assert(atlasIndex >= 0 && atlasIndex <= 4095); + assert(surfaceId >= 0 && surfaceId <= 16383); } - inline float GetX() const + inline void AddOffset( + _In_ int32_t x, + _In_ int32_t y) noexcept { - return DirectX::PackedVector::XMConvertHalfToFloat(_x); + _x += x; + _y += y; } - inline float GetY() const + inline int32_t GetX() const noexcept { - return DirectX::PackedVector::XMConvertHalfToFloat(_y); + return _x; } - void SetX(float x) + inline int32_t GetY() const noexcept { - _x = DirectX::PackedVector::XMConvertFloatToHalf(x); + return _y; } - void SetY(float y) + inline void SetPosition(int32_t x, int32_t y) noexcept { - _y = DirectX::PackedVector::XMConvertFloatToHalf(y); + _x = x; + _y = y; } - inline int32_t GetS() const + inline void SetSurfaceId(int32_t surfaceId) noexcept { - return _s; + assert(surfaceId >= 0 && surfaceId <= 16383); + _isChromaKeyEnabled_surfaceId &= ~16383; + _isChromaKeyEnabled_surfaceId |= surfaceId & 16383; } - inline void SetS(int32_t s) + inline int32_t GetSurfaceId() const noexcept { - assert(s >= INT16_MIN && s <= INT16_MAX); - _s = s; + return _isChromaKeyEnabled_surfaceId & 16383; + } + + inline int32_t GetS() const noexcept + { + return _s; } - inline int32_t GetT() const + inline int32_t GetT() const noexcept { return _t; } - inline void SetT(int32_t t) + inline void SetTexcoord(int32_t s, int32_t t) noexcept { - assert(t >= INT16_MIN && t <= INT16_MAX); + assert(s >= 0 && s <= 511); + assert(t >= 0 && t <= 511); + _s = s; _t = t; } - inline uint32_t GetColor() const + inline uint32_t GetColor() const noexcept { return _color; } - inline void SetColor(uint32_t color) + inline void SetColor(uint32_t color) noexcept { _color = color; } - inline RgbCombine GetRgbCombine() const - { - return (RgbCombine)(_paletteIndex_isChromaKeyEnabled_alphaCombine_rgbCombine & 0x01U); - } - - inline AlphaCombine GetAlphaCombine() const - { - return (AlphaCombine)((_paletteIndex_isChromaKeyEnabled_alphaCombine_rgbCombine >> 1U) & 0x01U); - } - - inline bool IsChromaKeyEnabled() const + inline bool IsChromaKeyEnabled() const noexcept { - return (_paletteIndex_isChromaKeyEnabled_alphaCombine_rgbCombine & (1U << 2U)) != 0; + return (_isChromaKeyEnabled_surfaceId & 0x4000) != 0; } private: - DirectX::PackedVector::HALF _x; - DirectX::PackedVector::HALF _y; + int16_t _x; + int16_t _y; int16_t _s; int16_t _t; uint32_t _color; - uint16_t _atlasIndex; - uint16_t _paletteIndex_isChromaKeyEnabled_alphaCombine_rgbCombine; + uint16_t _paletteIndex_atlasIndex; + uint16_t _isChromaKeyEnabled_surfaceId; }; static_assert(sizeof(Vertex) == 16, "sizeof(Vertex)"); diff --git a/src/d2dx/VideoPS.hlsl b/src/d2dx/VideoPS.hlsl index 3877d8a..3b6f332 100644 --- a/src/d2dx/VideoPS.hlsl +++ b/src/d2dx/VideoPS.hlsl @@ -17,11 +17,12 @@ along with D2DX. If not, see . */ #include "Constants.hlsli" +#include "Display.hlsli" Texture2D tex : register(t0); -half4 main(in float2 tc : TEXCOORD0) : SV_TARGET +float4 main( + in DisplayPSInput ps_in) : SV_TARGET { - half3 c = tex.Sample(BilinearSampler, tc/screenSize).rgb; - return half4(c, 1); + return tex.Sample(BilinearSampler, ps_in.tc) * float4(1, 1, 1, 0) + float4(0, 0, 0, 1); } diff --git a/src/d2dx/WeatherMotionPredictor.cpp b/src/d2dx/WeatherMotionPredictor.cpp new file mode 100644 index 0000000..2859760 --- /dev/null +++ b/src/d2dx/WeatherMotionPredictor.cpp @@ -0,0 +1,71 @@ +/* + This file is part of D2DX. + + Copyright (C) 2021 Bolrog + + D2DX is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + D2DX is distributed in the hope that it will be 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 D2DX. If not, see . +*/ +#include "pch.h" +#include "WeatherMotionPredictor.h" + +using namespace d2dx; +using namespace DirectX; + +_Use_decl_annotations_ +WeatherMotionPredictor::WeatherMotionPredictor( + const std::shared_ptr& gameHelper) : + _gameHelper{ gameHelper }, + _particleMotions{ 512, true } +{ +} + +_Use_decl_annotations_ +void WeatherMotionPredictor::Update( + IRenderContext* renderContext) +{ + _dt = _gameHelper->IsGameMenuOpen() ? 0.0f : renderContext->GetFrameTime(); + ++_frame; +} + +_Use_decl_annotations_ +OffsetF WeatherMotionPredictor::GetOffset( + int32_t particleIndex, + OffsetF posFromGame) +{ + ParticleMotion& pm = _particleMotions.items[particleIndex & 511]; + + const OffsetF diff = posFromGame - pm.lastPos; + const float error = max(abs(diff.x), abs(diff.y)); + + if (abs(_frame - pm.lastUsedFrame) > 2 || + error > 100.0f) + { + pm.velocity = { 0.0f, 0.0f }; + pm.lastPos = posFromGame; + pm.predictedPos = pm.lastPos; + } + else + { + if (error > 0.0f) + { + pm.velocity = diff * 25.0f; + pm.lastPos = posFromGame; + } + } + + pm.predictedPos += pm.velocity * _dt; + pm.lastUsedFrame = _frame; + + return pm.predictedPos - pm.lastPos; +} diff --git a/src/d2dx/WeatherMotionPredictor.h b/src/d2dx/WeatherMotionPredictor.h new file mode 100644 index 0000000..95ee480 --- /dev/null +++ b/src/d2dx/WeatherMotionPredictor.h @@ -0,0 +1,54 @@ +/* + This file is part of D2DX. + + Copyright (C) 2021 Bolrog + + D2DX is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + D2DX is distributed in the hope that it will be 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 D2DX. If not, see . +*/ +#pragma once + +#include "IGameHelper.h" +#include "IRenderContext.h" + +namespace d2dx +{ +#pragma once + class WeatherMotionPredictor + { + public: + WeatherMotionPredictor( + _In_ const std::shared_ptr& gameHelper); + + void Update( + _In_ IRenderContext* renderContext); + + OffsetF GetOffset( + _In_ int32_t particleIndex, + _In_ OffsetF posFromGame); + + private: + struct ParticleMotion final + { + OffsetF lastPos = { 0, 0 }; + OffsetF velocity = { 0, 0 }; + OffsetF predictedPos = { 0, 0 }; + int32_t lastUsedFrame = 0; + }; + + std::shared_ptr _gameHelper; + int32_t _frame = 0; + float _dt = 0; + Buffer _particleMotions; + }; +} diff --git a/src/d2dx/d2dx.rc b/src/d2dx/d2dx.rc index 8383529..93f878f 100644 --- a/src/d2dx/d2dx.rc +++ b/src/d2dx/d2dx.rc @@ -69,12 +69,12 @@ BEGIN BEGIN VALUE "CompanyName", "Bolrog" VALUE "FileDescription", "Glide wrapper for D2" - VALUE "FileVersion", "0.99.330.0" + VALUE "FileVersion", "0.99.529.0" VALUE "InternalName", "d2dx.dll" VALUE "LegalCopyright", "Copyright (C) 2021" VALUE "OriginalFilename", "d2dx.dll" VALUE "ProductName", "D2DX" - VALUE "ProductVersion", "0.99.330.0" + VALUE "ProductVersion", "0.99.529.0" END END BLOCK "VarFileInfo" @@ -83,6 +83,15 @@ BEGIN END END + +///////////////////////////////////////////////////////////////////////////// +// +// mpq +// + +IDR_SGD2FR_MPQ mpq "..\\..\\thirdparty\\sgd2freeres\\sgd2freeres.mpq.lzma" +IDR_SGD2FR_DLL dll "..\\..\\thirdparty\\sgd2freeres\\sgd2freeres.dll.lzma" + #endif // Swedish (Sweden) resources ///////////////////////////////////////////////////////////////////////////// diff --git a/src/d2dx/d2dx.vcxproj b/src/d2dx/d2dx.vcxproj index d5ac125..aa71fd5 100644 --- a/src/d2dx/d2dx.vcxproj +++ b/src/d2dx/d2dx.vcxproj @@ -34,6 +34,7 @@ + @@ -55,7 +56,7 @@ Level3 - WIN32;_DEBUG;D2DX_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + D2DX_EXPORT;WIN32;_DEBUG;D2DX_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true Use pch.h @@ -67,12 +68,17 @@ true + stdcpplatest + true + /Zc:__cplusplus Windows true false true + version.lib; kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + false copy $(OutDir)glide3x.dll "C:\games\Diablo II\" @@ -84,19 +90,22 @@ true true false - WIN32;NDEBUG;D2DX_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + D2DX_EXPORT;WIN32;NDEBUG;D2DX_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) true Use pch.h .;../../thirdparty/glide3/ AnySuitable Speed - Full + MaxSpeed MultiThreaded false StreamingSIMDExtensions2 Fast false + stdcpplatest + true + /Zc:__cplusplus Windows @@ -105,6 +114,8 @@ true false UseLinkTimeCodeGeneration + version.lib; kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + false copy $(OutDir)glide3x.dll "C:\games\Diablo II\" @@ -124,38 +135,71 @@ - + + + + + + + + + + + + + + + + + + + + + + + - + - + - + + - - + CompileAsCpp CompileAsCpp - - + + NotUsing + NotUsing + + + + + + + + + + + - + - AssemblyAndSourceCode AssemblyAndSourceCode @@ -169,15 +213,69 @@ AssemblyAndSourceCode - + + + - + + Pixel + 4.1 + Pixel + 4.1 + $(ProjectDir)%(Filename)_cso.h + + + $(ProjectDir)%(Filename)_cso.h + + + %(Filename)_cso + %(Filename)_cso + AssemblyCode + $(ProjectDir)%(Filename)_dxbc.txt + + + $(ProjectDir)%(Filename)_cso.h + + + $(ProjectDir)%(Filename)_cso.h + + + 4.1 + 4.1 + Pixel + Pixel + AssemblyCode + $(ProjectDir)%(Filename)_dxbc.txt + %(Filename)_cso + %(Filename)_cso + + + Pixel + 4.1 + Pixel + 4.1 + %(Filename)_cso + %(Filename)_cso + $(ProjectDir)%(Filename)_cso.h + + + $(ProjectDir)%(Filename)_cso.h + + + AssemblyCode + $(ProjectDir)%(Filename)_dxbc.txt + + + + + + Pixel - 4.0 + 4.1 Pixel - 4.0 + 4.1 $(ProjectDir)%(Filename)_cso.h %(Filename)_cso $(ProjectDir)%(Filename)_cso.h @@ -186,12 +284,18 @@ + AssemblyCode + $(ProjectDir)%(Filename)_dxbc.txt + + + + - + Vertex - 4.0 + 4.1 Vertex - 4.0 + 4.1 %(Filename)_cso $(ProjectDir)%(Filename)_cso.h @@ -200,8 +304,19 @@ $(ProjectDir)%(Filename)_cso.h + AssemblyCode + $(ProjectDir)%(Filename)_dxbc.txt - + + + + + + Pixel + Pixel + Document + + Pixel Pixel true @@ -215,12 +330,14 @@ + AssemblyCode + $(ProjectDir)%(Filename)_dxbc.txt - + Vertex Vertex true - 4.0 + 4.1 %(Filename)_cso $(ProjectDir)%(Filename)_cso.h %(Filename)_cso @@ -229,16 +346,51 @@ - w + + w - 4.0 + 4.1 + AssemblyCode + $(ProjectDir)%(Filename)_dxbc.txt + + + %(Filename)_cso + %(Filename)_cso + $(ProjectDir)%(Filename)_cso.h + + + $(ProjectDir)%(Filename)_cso.h + + + Pixel + 4.1 + Pixel + 4.1 + $(ProjectDir)%(Filename)_dxbc.txt + AssemblyCode + + + %(Filename)_cso + + + %(Filename)_cso + + + $(ProjectDir)%(Filename)_cso.h + $(ProjectDir)%(Filename)_cso.h + Pixel + 4.1 + Pixel + 4.1 + AssemblyCode + $(ProjectDir)%(Filename)_dxbc.txt - 4.0 + 4.1 true Pixel Pixel - 4.0 + 4.1 %(Filename)_cso $(ProjectDir)%(Filename)_cso.h @@ -247,7 +399,10 @@ $(ProjectDir)%(Filename)_cso.h + AssemblyCode + $(ProjectDir)%(Filename)_dxbc.txt + @@ -255,7 +410,20 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/d2dx/d2dx.vcxproj.filters b/src/d2dx/d2dx.vcxproj.filters index 0247b07..edda29f 100644 --- a/src/d2dx/d2dx.vcxproj.filters +++ b/src/d2dx/d2dx.vcxproj.filters @@ -1,58 +1,82 @@ īģŋ - + shaders - + shaders - + shaders - + shaders shaders + + shaders + + + shaders + + + shaders + + + shaders + + + shaders + - + - - thirdparty\fnv - - + + + + + + + + + + + + thirdparty\toml + + + + - + - + - - - thirdparty\glide3 @@ -93,8 +117,39 @@ thirdparty\detours - + + + + + + + + + + + + + + + + thirdparty\stb_image + + + + + + + + thirdparty\toml + + + + + + thirdparty\pocketlzma + + @@ -115,10 +170,72 @@ {8240f153-200d-47ad-b776-7f45ed2a1d3b} + + {99af7b84-eaea-4a5a-aa0e-b4ec344078ad} + + + {aca34c8e-6ba9-4e96-943a-d9172330fd27} + + + {58015682-6898-45e8-8ec5-6e3fbfdad713} + + + {23de30e8-2931-41c8-9f15-02f244de52be} + shaders + + shaders + + + shaders + + + shaders + + + thirdparty\SGD2FreeRes + + + thirdparty\SGD2FreeRes + + + thirdparty\SGD2FreeRes + + + + + shaders + + + shaders + + + shaders + + + shaders + + + shaders + + + shaders + + + shaders + + + shaders + + + shaders + + + shaders + \ No newline at end of file diff --git a/src/d2dx/d2dx.vcxproj.user b/src/d2dx/d2dx.vcxproj.user index b6f361a..35adca8 100644 --- a/src/d2dx/d2dx.vcxproj.user +++ b/src/d2dx/d2dx.vcxproj.user @@ -3,14 +3,14 @@ C:\games\Diablo II\Game.exe WindowsLocalDebugger - -ns -3dfx -w + -ns -3dfx -w -dxtestsleepfix C:\games\Diablo II\ NativeOnly - C:\games\Diablo II\Game.exe + C:\games\Diablo II\game.exe WindowsLocalDebugger - -3dfx -w + -3dfx -ns -w -dxnoclipcursor -dxtestsleepfix C:\games\Diablo II\ NativeOnly diff --git a/src/d2dx/dllmain.cpp b/src/d2dx/dllmain.cpp index 28a17b2..60bb21a 100644 --- a/src/d2dx/dllmain.cpp +++ b/src/d2dx/dllmain.cpp @@ -1,59 +1,51 @@ /* - This file is part of D2DX. + This file is part of D2DX. - Copyright (C) 2021 Bolrog + Copyright (C) 2021 Bolrog - D2DX is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. + D2DX is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. - D2DX is distributed in the hope that it will be 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. + D2DX is distributed in the hope that it will be 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 D2DX. If not, see . + You should have received a copy of the GNU General Public License + along with D2DX. If not, see . */ #include "pch.h" -#include "D2DXContext.h" -#include "D2DXDetours.h" +#include "Detours.h" #pragma comment(lib, "comctl32.lib") #pragma comment(lib, "dxgi.lib") #pragma comment(lib, "d3d11.lib") #pragma comment(lib, "d3dcompiler.lib") -#pragma comment(lib, "version.lib") using namespace d2dx; using namespace std; -BOOL APIENTRY DllMain( HMODULE hModule, - DWORD ul_reason_for_call, - LPVOID lpReserved - ) +BOOL APIENTRY DllMain( + _In_ HMODULE hModule, + _In_ DWORD ul_reason_for_call, + _In_ LPVOID lpReserved +) { - switch (ul_reason_for_call) - { - case DLL_PROCESS_ATTACH: - { - printf("DLL_PROCESS_ATTACH\n"); - SetProcessDPIAware(); - AttachDetours(); - LoadLibraryA("D2DX_SlashDiabloHD.dll"); - break; - } - case DLL_THREAD_ATTACH: - printf("DLL_THREAD_ATTACH\n"); - break; - case DLL_THREAD_DETACH: - printf("DLL_THREAD_DETACH\n"); - break; - case DLL_PROCESS_DETACH: - printf("DLL_PROCESS_DETACH\n"); - DetachDetours(); - break; - } - return TRUE; + switch (ul_reason_for_call) + { + case DLL_PROCESS_ATTACH: + SetProcessDPIAware(); + AttachDetours(); + break; + case DLL_THREAD_ATTACH: + break; + case DLL_THREAD_DETACH: + break; + case DLL_PROCESS_DETACH: + DetachDetours(); + break; + } + return TRUE; } diff --git a/src/d2dx/glide3x.cpp b/src/d2dx/glide3x.cpp index 5074df3..f27fd7b 100644 --- a/src/d2dx/glide3x.cpp +++ b/src/d2dx/glide3x.cpp @@ -17,154 +17,134 @@ along with D2DX. If not, see . */ #include "pch.h" -#include "GlideHelpers.h" -#include "D2DXContext.h" +#include "D2DXContextFactory.h" +#include "Utils.h" using namespace d2dx; using namespace std; -unique_ptr g_d2dxContext; +static void GetWidthHeightFromTexInfo(const GrTexInfo* info, FxU32* w, FxU32* h); static GrLfbInfo_t lfbInfo = { 0 }; static char tempString[2048]; static bool initialized = false; -#define D2DX_LOG(s, ...) \ - if (g_d2dxContext && g_d2dxContext->IsCapturingFrame()) { \ - sprintf_s(tempString, s, __VA_ARGS__); \ - g_d2dxContext->LogGlideCall(tempString); \ - } - -static void EnsureInitialized() -{ - /* The game is single threaded and there's no worry about synchronization. */ - - if (initialized) - { - return; - } - - initialized = true; - - g_d2dxContext = std::make_unique(); -} - extern "C" { FX_ENTRY void FX_CALL grDrawPoint(const void* pt) { - EnsureInitialized(); - D2DX_LOG("grDrawPoint pt=%p\n", pt); + try + { + const auto returnAddress = (uintptr_t)_ReturnAddress(); + D2DXContextFactory::GetInstance()->OnDrawPoint(pt, returnAddress); + } + catch (...) + { + D2DX_FATAL_EXCEPTION; + } } FX_ENTRY void FX_CALL grDrawLine(const void* v1, const void* v2) { - EnsureInitialized(); - - D2DX_LOG("grDrawLine v1=%p v2=%p\n", v1, v2); - - if (!g_d2dxContext) + try { - return; + const auto returnAddress = (uintptr_t)_ReturnAddress(); + D2DXContextFactory::GetInstance()->OnDrawLine(v1, v2, returnAddress); + } + catch (...) + { + D2DX_FATAL_EXCEPTION; } - - const auto returnAddress = (uintptr_t)_ReturnAddress(); - g_d2dxContext->OnDrawLine(v1, v2, returnAddress); } FX_ENTRY void FX_CALL grVertexLayout(FxU32 param, FxI32 offset, FxU32 mode) { - EnsureInitialized(); - - if (!g_d2dxContext) + try + { + D2DXContextFactory::GetInstance()->OnVertexLayout(param, mode ? offset : 0xFF); + } + catch (...) { - return; + D2DX_FATAL_EXCEPTION; } - - D2DX_LOG("grVertexLayout param=%s offset=%i mode=%u\n", GetVertexLayoutParamString(param), offset, mode); - g_d2dxContext->OnVertexLayout(param, mode ? offset : 0xFF); } FX_ENTRY void FX_CALL grDrawVertexArray(FxU32 mode, FxU32 Count, void* pointers) { - EnsureInitialized(); - D2DX_LOG("grDrawVertexArray mode=%s count=%u pointers=%p\n", GetPrimitiveTypeString(mode), Count, pointers); - - if (!g_d2dxContext) + const auto returnAddress = (uintptr_t)_ReturnAddress(); + try { - return; + D2DXContextFactory::GetInstance()->OnDrawVertexArray(mode, Count, (uint8_t**)pointers, returnAddress); + } + catch (...) + { + D2DX_FATAL_EXCEPTION; } - - const auto returnAddress = (uintptr_t)_ReturnAddress(); - g_d2dxContext->OnDrawVertexArray(mode, Count, (uint8_t**)pointers, returnAddress); } FX_ENTRY void FX_CALL grDrawVertexArrayContiguous(FxU32 mode, FxU32 Count, void* vertex, FxU32 stride) { - EnsureInitialized(); - D2DX_LOG("grDrawVertexArrayContiguous mode=%s count=%u vertex=%p stride=%u\n", GetPrimitiveTypeString(mode), Count, vertex, stride); + const auto returnAddress = (uintptr_t)_ReturnAddress(); - if (!g_d2dxContext) + try { - return; + D2DXContextFactory::GetInstance()->OnDrawVertexArrayContiguous(mode, Count, (uint8_t*)vertex, stride, returnAddress); + } + catch (...) + { + D2DX_FATAL_EXCEPTION; } - - const auto returnAddress = (uintptr_t)_ReturnAddress(); - g_d2dxContext->OnDrawVertexArrayContiguous(mode, Count, (uint8_t*)vertex, stride, returnAddress); } FX_ENTRY void FX_CALL grBufferClear(GrColor_t color, GrAlpha_t alpha, FxU32 depth) { - EnsureInitialized(); - D2DX_LOG("grBufferClear color=%u alpha=%u depth=%u\n", color, alpha, depth); + try + { + D2DXContextFactory::GetInstance()->OnBufferClear(); + } + catch (...) + { + D2DX_FATAL_EXCEPTION; + } } FX_ENTRY void FX_CALL grBufferSwap(FxU32 swap_interval) { - EnsureInitialized(); - D2DX_LOG("grBufferSwap swap_interval=%u\n", swap_interval); - - if (!g_d2dxContext) + try { - return; + D2DXContextFactory::GetInstance()->OnBufferSwap(); + } + catch (...) + { + D2DX_FATAL_EXCEPTION; } - - g_d2dxContext->OnBufferSwap(); } FX_ENTRY void FX_CALL grRenderBuffer(GrBuffer_t buffer) { - EnsureInitialized(); - D2DX_LOG("grRenderBuffer buffer=%u\n", buffer); } FX_ENTRY void FX_CALL grErrorSetCallback(GrErrorCallbackFnc_t fnc) { - EnsureInitialized(); - D2DX_LOG("grErrorSetCallback fnc=%p\n", fnc); } FX_ENTRY void FX_CALL grFinish(void) { - EnsureInitialized(); - D2DX_LOG("grFinish\n"); } FX_ENTRY void FX_CALL grFlush(void) { - EnsureInitialized(); - D2DX_LOG("grFlush\n"); } FX_ENTRY GrContext_t FX_CALL @@ -177,15 +157,7 @@ FX_ENTRY GrContext_t FX_CALL int nColBuffers, int nAuxBuffers) { - EnsureInitialized(); assert(color_format == GR_COLORFORMAT_RGBA); - D2DX_LOG("grSstWinOpen hWnd=%u screen_resolution=%u refresh_rate=%u color_format=%u origin_location=%u nColBuffers=%i nAuxBuffers=%i\n", - hWnd, screen_resolution, refresh_rate, color_format, origin_location, nColBuffers, nAuxBuffers); - - if (!g_d2dxContext) - { - return 0; - } if (lfbInfo.lfbPtr) { @@ -213,31 +185,33 @@ FX_ENTRY GrContext_t FX_CALL break; } - g_d2dxContext->OnSstWinOpen(hWnd, width, height); + try + { + D2DXContextFactory::GetInstance()->OnSstWinOpen(hWnd, width, height); + } + catch (...) + { + D2DX_FATAL_EXCEPTION; + } + return 1; } FX_ENTRY FxBool FX_CALL grSstWinClose(GrContext_t context) { - EnsureInitialized(); - D2DX_LOG("grSstWinClose context=%u\n", context); return FXTRUE; } FX_ENTRY FxBool FX_CALL grSelectContext(GrContext_t context) { - EnsureInitialized(); - D2DX_LOG("grSelectContext context=%u\n", context); return FXTRUE; } FX_ENTRY void FX_CALL grSstSelect(int which_sst) { - EnsureInitialized(); - D2DX_LOG("grSstSelect which_sst=%i\n", which_sst); } FX_ENTRY void FX_CALL @@ -246,19 +220,14 @@ FX_ENTRY void FX_CALL GrAlphaBlendFnc_t alpha_sf, GrAlphaBlendFnc_t alpha_df ) { - EnsureInitialized(); - D2DX_LOG("grAlphaBlendFunction rgb_sf=%s rgb_df=%s alpha_sf=%s alpha_df=%s\n", - GetAlphaBlendFncString(rgb_sf), - GetAlphaBlendFncString(rgb_df), - GetAlphaBlendFncString(alpha_sf), - GetAlphaBlendFncString(alpha_df)); - - if (!g_d2dxContext) + try { - return; + D2DXContextFactory::GetInstance()->OnAlphaBlendFunction(rgb_sf, rgb_df, alpha_sf, alpha_df); + } + catch (...) + { + D2DX_FATAL_EXCEPTION; } - - g_d2dxContext->OnAlphaBlendFunction(rgb_sf, rgb_df, alpha_sf, alpha_df); } FX_ENTRY void FX_CALL @@ -268,7 +237,6 @@ FX_ENTRY void FX_CALL FxBool invert ) { - EnsureInitialized(); assert(function != GR_COMBINE_FUNCTION_SCALE_MINUS_LOCAL_ADD_LOCAL_ALPHA); //sprintf(tempString, "grAlphaCombine function=%s factor=%s local=%s other=%s invert=%i\n", @@ -282,40 +250,32 @@ FX_ENTRY void FX_CALL assert( (function == GR_COMBINE_FUNCTION_ZERO && factor == GR_COMBINE_FACTOR_ZERO && local == GR_COMBINE_LOCAL_CONSTANT && other == GR_COMBINE_OTHER_CONSTANT && !invert) || (function == GR_COMBINE_FUNCTION_LOCAL && factor == GR_COMBINE_FACTOR_ZERO && local == GR_COMBINE_LOCAL_CONSTANT && other == GR_COMBINE_OTHER_CONSTANT && !invert)); - D2DX_LOG("grAlphaCombine function=%s factor=%s local=%s other=%s invert=%i\n", - GetCombineFunctionString(function), - GetCombineFactorString(factor), - GetCombineLocalString(local), - GetCombineOtherString(other), - invert ? 1 : 0); - - if (!g_d2dxContext) + try { - return; + D2DXContextFactory::GetInstance()->OnAlphaCombine(function, factor, local, other, invert); + } + catch (...) + { + D2DX_FATAL_EXCEPTION; } - - g_d2dxContext->OnAlphaCombine(function, factor, local, other, invert); } FX_ENTRY void FX_CALL grChromakeyMode(GrChromakeyMode_t mode) { - EnsureInitialized(); - D2DX_LOG("grChromakeyMode mode=%i\n", mode); - - if (!g_d2dxContext) + try { - return; + D2DXContextFactory::GetInstance()->OnChromakeyMode(mode); + } + catch (...) + { + D2DX_FATAL_EXCEPTION; } - - g_d2dxContext->OnChromakeyMode(mode); } FX_ENTRY void FX_CALL grChromakeyValue(GrColor_t value) { - EnsureInitialized(); - D2DX_LOG("grChromakeyValue value=%u\n", value); assert(value == 255); } @@ -325,7 +285,6 @@ FX_ENTRY void FX_CALL GrCombineLocal_t local, GrCombineOther_t other, FxBool invert) { - EnsureInitialized(); assert(function != GR_COMBINE_FUNCTION_SCALE_MINUS_LOCAL_ADD_LOCAL_ALPHA); //sprintf(tempString, "grColorCombine function=%s factor=%s local=%s other=%s invert=%i\n", @@ -341,111 +300,90 @@ FX_ENTRY void FX_CALL /* ingame */ (function == GR_COMBINE_FUNCTION_LOCAL && factor == GR_COMBINE_FACTOR_ZERO && local == GR_COMBINE_LOCAL_CONSTANT && other == GR_COMBINE_OTHER_CONSTANT && !invert) ); - D2DX_LOG("grColorCombine function=%s factor=%s local=%s other=%s invert=%i\n", - GetCombineFunctionString(function), - GetCombineFactorString(factor), - GetCombineLocalString(local), - GetCombineOtherString(other), - invert ? 1 : 0); - - if (!g_d2dxContext) + try { - return; + D2DXContextFactory::GetInstance()->OnColorCombine(function, factor, local, other, invert); + } + catch (...) + { + D2DX_FATAL_EXCEPTION; } - - g_d2dxContext->OnColorCombine(function, factor, local, other, invert); } FX_ENTRY void FX_CALL grColorMask(FxBool rgb, FxBool a) { - EnsureInitialized(); - D2DX_LOG("grColorMask rgb=%i a=%i\n", rgb ? 1 : 0, a ? 1 : 0); } FX_ENTRY void FX_CALL grConstantColorValue(GrColor_t value) { - EnsureInitialized(); - D2DX_LOG("grConstantColorValue value=%u\n", value); - - if (!g_d2dxContext) + try { - return; + D2DXContextFactory::GetInstance()->OnConstantColorValue((uint32_t)value); + } + catch (...) + { + D2DX_FATAL_EXCEPTION; } - - g_d2dxContext->OnConstantColorValue((uint32_t)value); } FX_ENTRY void FX_CALL grDepthMask(FxBool mask) { - EnsureInitialized(); - D2DX_LOG("grDepthMask mask=%i\n", mask ? 1 : 0); } FX_ENTRY void FX_CALL grDitherMode(GrDitherMode_t mode) { - EnsureInitialized(); - D2DX_LOG("grDitherMode mode=%i\n", mode); } FX_ENTRY void FX_CALL grLoadGammaTable(FxU32 nentries, FxU32* red, FxU32* green, FxU32* blue) { - EnsureInitialized(); - D2DX_LOG("grLoadGammaTable nentries=%u red=%p green=%p blue=%p\n", nentries, red, green, blue); - - if (!g_d2dxContext) + try { - return; + D2DXContextFactory::GetInstance()->OnLoadGammaTable(nentries, (uint32_t *)red, (uint32_t*)green, (uint32_t*)blue); + } + catch (...) + { + D2DX_FATAL_EXCEPTION; } - - g_d2dxContext->OnLoadGammaTable(nentries, (uint32_t *)red, (uint32_t*)green, (uint32_t*)blue); } FX_ENTRY FxU32 FX_CALL grGet(FxU32 pname, FxU32 plength, FxI32* params) { - EnsureInitialized(); - - D2DX_LOG("grGet pname=%u plength=%u params=%p\n", pname, plength, params); - - if (!g_d2dxContext) + try { - return 0; + return D2DXContextFactory::GetInstance()->OnGet(pname, plength, (int32_t*)params); + } + catch (...) + { + D2DX_FATAL_EXCEPTION; } - - return g_d2dxContext->OnGet(pname, plength, (int32_t *)params); } FX_ENTRY void FX_CALL grCoordinateSpace(GrCoordinateSpaceMode_t mode) { - EnsureInitialized(); - D2DX_LOG("grCoordinateSpace mode=%s\n", GetCoordinateSpaceModeString(mode)); } FX_ENTRY void FX_CALL grViewport(FxI32 x, FxI32 y, FxI32 width, FxI32 height) { - EnsureInitialized(); assert(x == 0 && y == 0); - D2DX_LOG("grViewport x=%i y=%i width=%i height=%i\n", x, y, width, height); } FX_ENTRY FxU32 FX_CALL grTexMinAddress(GrChipID_t tmu) { - EnsureInitialized(); return 0; } FX_ENTRY FxU32 FX_CALL grTexMaxAddress(GrChipID_t tmu) { - EnsureInitialized(); return tmu == 0 ? D2DX_TMU_MEMORY_SIZE - (2 * D2DX_TMU_ADDRESS_ALIGNMENT) : 0; } @@ -455,18 +393,17 @@ FX_ENTRY void FX_CALL FxU32 evenOdd, GrTexInfo* info) { - EnsureInitialized(); FxU32 w, h; GetWidthHeightFromTexInfo(info, &w, &h); - D2DX_LOG("grTexSource tmu=%i startAddress=%08x evenOdd=%u fmt=%s w=%u h=%u\n", - tmu, startAddress, evenOdd, GetTextureFormatString(info->format), w, h); - if (!g_d2dxContext) + try { - return; + D2DXContextFactory::GetInstance()->OnTexSource(tmu, startAddress, w, h); + } + catch (...) + { + D2DX_FATAL_EXCEPTION; } - - g_d2dxContext->OnTexSource(tmu, startAddress, w, h); } FX_ENTRY void FX_CALL @@ -476,9 +413,6 @@ FX_ENTRY void FX_CALL GrTextureClampMode_t t_clampmode ) { - EnsureInitialized(); - D2DX_LOG("grTexClampMode tmu=%i s_clampmode=%i t_clampmode=%i\n", - tmu, s_clampmode, t_clampmode); } FX_ENTRY void FX_CALL @@ -492,57 +426,42 @@ FX_ENTRY void FX_CALL FxBool alpha_invert ) { - EnsureInitialized(); assert(tmu == 0 && rgb_function == GR_COMBINE_FUNCTION_LOCAL && rgb_factor == GR_COMBINE_FACTOR_ZERO && alpha_function == GR_COMBINE_FUNCTION_LOCAL && alpha_factor == GR_COMBINE_FACTOR_ZERO && !rgb_invert && !alpha_invert); - - D2DX_LOG("grTexCombine tmu=%i rgb_function=%s rgb_factor=%s alpha_function=%s alpha_factor=%s rgb_invert=%i alpha_invert=%i\n", - tmu, - GetCombineFunctionString(rgb_function), - GetCombineFactorString(rgb_factor), - GetCombineFunctionString(alpha_function), - GetCombineFactorString(alpha_factor), - rgb_invert, - alpha_invert); } FX_ENTRY void FX_CALL grTexFilterMode(GrChipID_t tmu, GrTextureFilterMode_t minfilter_mode, GrTextureFilterMode_t magfilter_mode) { - EnsureInitialized(); - D2DX_LOG("grTexFilterMode tmu=%i minfilter_mode=%s magfilter_mode=%s\n", - tmu, GetStringForTextureFilterMode(minfilter_mode), GetStringForTextureFilterMode(magfilter_mode)); } FX_ENTRY void FX_CALL grTexDownloadMipMap(GrChipID_t tmu, FxU32 startAddress, FxU32 evenOdd, GrTexInfo* info) { - EnsureInitialized(); - D2DX_LOG("grTexDownloadMipMap tmu=%u startAddress=%08x evenOdd=%u info=%p\n", - tmu, startAddress, evenOdd, info); FxU32 width, height; GetWidthHeightFromTexInfo(info, &width, &height); - if (!g_d2dxContext) + try { - return; + D2DXContextFactory::GetInstance()->OnTexDownload(tmu, (const uint8_t*)info->data, startAddress, (int32_t)width, (int32_t)height); + } + catch (...) + { + D2DX_FATAL_EXCEPTION; } - - g_d2dxContext->OnTexDownload(tmu, (const uint8_t*)info->data, startAddress, (int32_t)width, (int32_t)height); } FX_ENTRY void FX_CALL grTexDownloadTable(GrTexTable_t type, void* data) { - EnsureInitialized(); - D2DX_LOG("grTexDownloadTable type=%i data=%p\n", type, data); - - if (!g_d2dxContext) + try { - return; + D2DXContextFactory::GetInstance()->OnTexDownloadTable(type, data); + } + catch (...) + { + D2DX_FATAL_EXCEPTION; } - - g_d2dxContext->OnTexDownloadTable(type, data); } FX_ENTRY void FX_CALL @@ -550,8 +469,6 @@ FX_ENTRY void FX_CALL GrMipMapMode_t mode, FxBool lodBlend) { - EnsureInitialized(); - D2DX_LOG("grTexMipMapMode tmu=%i mode=%i lodBlend=%i\n", tmu, mode, lodBlend ? 1 : 0); } FX_ENTRY FxBool FX_CALL @@ -559,97 +476,76 @@ FX_ENTRY FxBool FX_CALL GrOriginLocation_t origin, FxBool pixelPipeline, GrLfbInfo_t* info) { - EnsureInitialized(); - D2DX_LOG("grLfbLock\n"); - - if (!g_d2dxContext) + try { - return FXFALSE; - } - - if (type == GR_LFB_WRITE_ONLY && buffer == GR_BUFFER_FRONTBUFFER && writeMode == GR_LFBWRITEMODE_8888 && - origin == GR_ORIGIN_UPPER_LEFT && !pixelPipeline && info && info->size == 20) - { - if (!lfbInfo.lfbPtr) + if (type == GR_LFB_WRITE_ONLY && buffer == GR_BUFFER_FRONTBUFFER && writeMode == GR_LFBWRITEMODE_8888 && + origin == GR_ORIGIN_UPPER_LEFT && !pixelPipeline && info && info->size == 20) { - lfbInfo.lfbPtr = (uint8_t*)malloc(640 * 480 * 4); - lfbInfo.strideInBytes = 640 * 4; - } + if (!lfbInfo.lfbPtr) + { + lfbInfo.lfbPtr = (uint8_t*)malloc(640 * 480 * 4); + lfbInfo.strideInBytes = 640 * 4; + } - lfbInfo.writeMode = writeMode; - lfbInfo.origin = origin; - *info = lfbInfo; + lfbInfo.writeMode = writeMode; + lfbInfo.origin = origin; + *info = lfbInfo; - return FXTRUE; + return FXTRUE; + } + else + { + return FXFALSE; + } } - else + catch (...) { - return FXFALSE; + D2DX_FATAL_EXCEPTION; } } FX_ENTRY FxBool FX_CALL grLfbUnlock(GrLock_t type, GrBuffer_t buffer) { - EnsureInitialized(); - D2DX_LOG("grLfbUnlock\n"); - - if (!g_d2dxContext) - { - return FXFALSE; - } - - if (type == GR_LFB_WRITE_ONLY && buffer == GR_BUFFER_FRONTBUFFER) + try { - g_d2dxContext->OnLfbUnlock((const uint32_t*)lfbInfo.lfbPtr, lfbInfo.strideInBytes); - return FXTRUE; + if (type == GR_LFB_WRITE_ONLY && buffer == GR_BUFFER_FRONTBUFFER) + { + D2DXContextFactory::GetInstance()->OnLfbUnlock((const uint32_t*)lfbInfo.lfbPtr, lfbInfo.strideInBytes); + return FXTRUE; + } + else + { + return FXFALSE; + } } - else + catch (...) { - return FXFALSE; + D2DX_FATAL_EXCEPTION; } } FX_ENTRY void FX_CALL grGlideInit(void) { - EnsureInitialized(); - D2DX_LOG("grGlideInit\n"); - - if (!g_d2dxContext) - { - return; - } - - g_d2dxContext->OnGlideInit(); } FX_ENTRY void FX_CALL grGlideShutdown(void) { - EnsureInitialized(); - D2DX_LOG("grGlideShutdown\n"); - - if (!g_d2dxContext) - { - return; - } - - g_d2dxContext->OnGlideShutdown(); } FX_ENTRY void FX_CALL guGammaCorrectionRGB(FxFloat red, FxFloat green, FxFloat blue) { - EnsureInitialized(); - D2DX_LOG("guGammaCorrectionRGB\n"); - - if (!g_d2dxContext) + try { - return; + D2DXContextFactory::GetInstance()->OnGammaCorrectionRGB(red, green, blue); + } + catch (...) + { + D2DX_FATAL_EXCEPTION; } - - g_d2dxContext->OnGammaCorrectionRGB(red, green, blue); } /* @@ -660,8 +556,6 @@ FX_ENTRY FxI32 FX_CALL grQueryResolutions(const GrResolution* resTemplate, GrResolution* output) { assert(false && "grQueryResolutions: Unsupported"); - EnsureInitialized(); - D2DX_LOG("grQueryResolutions resTemplate=%p output=%p\n", resTemplate, output); return 0; } @@ -669,8 +563,6 @@ FX_ENTRY FxBool FX_CALL grReset(FxU32 what) { assert(false && "grReset: Unsupported"); - EnsureInitialized(); - D2DX_LOG("grReset what=%u\n", what); return FXTRUE; } @@ -678,59 +570,42 @@ FX_ENTRY void FX_CALL grEnable(GrEnableMode_t mode) { assert(false && "grEnable: Unsupported"); - EnsureInitialized(); - D2DX_LOG("grEnable mode=%u\n", mode); } FX_ENTRY void FX_CALL grDisable(GrEnableMode_t mode) { assert(false && "grDisable: Unsupported"); - EnsureInitialized(); - D2DX_LOG("grDisable mode=%u\n", mode); } FX_ENTRY void FX_CALL grGlideGetState(void* state) { assert(false && "grGlideGetState: Unsupported"); - EnsureInitialized(); - D2DX_LOG("grGlideGetState state=%p\n", state); - //(*pgrGlideGetState)(state); } FX_ENTRY void FX_CALL grGlideSetState(const void* state) { assert(false && "grGlideSetState: Unsupported"); - EnsureInitialized(); - D2DX_LOG("grGlideSetState state=%p\n", state); - //(*pgrGlideSetState)(state); } FX_ENTRY void FX_CALL grGlideGetVertexLayout(void* layout) { assert(false && "grGlideGetVertexLayout: Unsupported"); - EnsureInitialized(); - D2DX_LOG("grGlideGetVertexLayout layout=%p\n", layout); - //(*pgrGlideGetVertexLayout)(layout); } FX_ENTRY void FX_CALL grGlideSetVertexLayout(const void* layout) { assert(false && "grGlideSetVertexLayout: Unsupported"); - EnsureInitialized(); - D2DX_LOG("grGlideSetVertexLayout layout=%p\n", layout); - //(*pgrGlideSetVertexLayout)(layout); } FX_ENTRY float FX_CALL guFogTableIndexToW(int i) { assert(false && "guFogTableIndexToW: Unsupported"); - EnsureInitialized(); //float r = (*pguFogTableIndexToW)(i); return 0.0f; } @@ -739,7 +614,6 @@ FX_ENTRY void FX_CALL guFogGenerateExp(GrFog_t* fogtable, float density) { assert(false && "guFogGenerateExp: Unsupported"); - EnsureInitialized(); //(*pguFogGenerateExp)(fogtable, density); } @@ -747,7 +621,6 @@ FX_ENTRY void FX_CALL guFogGenerateExp2(GrFog_t* fogtable, float density) { assert(false && "guFogGenerateExp2: Unsupported"); - EnsureInitialized(); //(*pguFogGenerateExp2)(fogtable, density); } @@ -756,7 +629,6 @@ FX_ENTRY void FX_CALL float nearZ, float farZ) { assert(false && "guFogGenerateLinear: Unsupported"); - EnsureInitialized(); //(*pguFogGenerateLinear)(fogtable, nearZ, farZ); } @@ -764,7 +636,6 @@ FX_ENTRY FxBool FX_CALL gu3dfGetInfo(const char* filename, Gu3dfInfo* info) { assert(false && "gu3dfGetInfo: Unsupported"); - EnsureInitialized(); //FxBool r = (*pgu3dfGetInfo)(filename, info); return FXFALSE; } @@ -773,7 +644,6 @@ FX_ENTRY FxBool FX_CALL gu3dfLoad(const char* filename, Gu3dfInfo* data) { assert(false && "gu3dfLoad: Unsupported"); - EnsureInitialized(); //FxBool r = (*pgu3dfLoad)(filename, data); return FXFALSE; } @@ -782,9 +652,6 @@ FX_ENTRY void FX_CALL grCheckForRoom(FxI32 n) { assert(false && "grCheckForRoom: Unsupported"); - EnsureInitialized(); - D2DX_LOG("grCheckForRoom n=%i\n", n); - //(*pgrCheckForRoom)(n); } FX_ENTRY int FX_CALL @@ -794,9 +661,6 @@ FX_ENTRY int FX_CALL FxU32 height) { assert(false && "guEncodeRLE16: Unsupported"); - EnsureInitialized(); - //int r = (*pguEncodeRLE16)(dst, src, width, height); - //D2DX_LOG("guEncodeRLE16 dst=%p src=%p width=%u height=%u -> %i\n", dst, src, width, height, r); return 0; } @@ -804,7 +668,6 @@ FX_ENTRY void FX_CALL grSstVidMode(FxU32 whichSst, void* vidTimings) { assert(false && "grSstVidMode: Unsupported"); - EnsureInitialized(); //(*pgrSstVidMode)(whichSst, vidTimings); } @@ -813,63 +676,42 @@ FX_ENTRY void FX_CALL grAlphaControlsITRGBLighting(FxBool enable) { assert(false && "grAlphaControlsITRGBLighting: Unsupported"); - EnsureInitialized(); - D2DX_LOG("grAlphaControlsITRGBLighting enable=%i\n", enable ? 1 : 0); - //(*pgrAlphaControlsITRGBLighting)(enable); } FX_ENTRY void FX_CALL grAlphaTestFunction(GrCmpFnc_t function) { assert(false && "grAlphaTestFunction: Unsupported"); - EnsureInitialized(); - D2DX_LOG("grAlphaTestFunction function=%i\n", function); - //(*pgrAlphaTestFunction)(function); } FX_ENTRY void FX_CALL grAlphaTestReferenceValue(GrAlpha_t value) { assert(false && "grAlphaTestReferenceValue: Unsupported"); - EnsureInitialized(); - D2DX_LOG("grAlphaTestReferenceValue value=%u\n", value); - //(*pgrAlphaTestReferenceValue)(value); } FX_ENTRY void FX_CALL grLfbConstantAlpha(GrAlpha_t alpha) { assert(false && "grLfbConstantAlpha: Unsupported"); - EnsureInitialized(); - D2DX_LOG("grLfbConstantDepth alpha=%u\n", alpha); - //(*pgrLfbConstantAlpha)(alpha); } FX_ENTRY void FX_CALL grLfbConstantDepth(FxU32 depth) { assert(false && "grLfbConstantDepth: Unsupported"); - EnsureInitialized(); - D2DX_LOG("grLfbConstantDepth depth=%u\n", depth); - //(*pgrLfbConstantDepth)(depth); } FX_ENTRY void FX_CALL grLfbWriteColorSwizzle(FxBool swizzleBytes, FxBool swapWords) { assert(false && "grLfbWriteColorSwizzle: Unsupported"); - EnsureInitialized(); - D2DX_LOG("grLfbWriteColorSwizzle swizzleBytes=%i swapWords=%i\n", swizzleBytes ? 1 : 0, swapWords ? 1 : 0); - //(*pgrLfbWriteColorSwizzle)(swizzleBytes, swapWords); } FX_ENTRY void FX_CALL grLfbWriteColorFormat(GrColorFormat_t colorFormat) { assert(false && "grLfbWriteColorFormat: Unsupported"); - EnsureInitialized(); - D2DX_LOG("grLfbWriteColorFormat colorFormat=%i\n", colorFormat); - //(*pgrLfbWriteColorFormat)(colorFormat); } FX_ENTRY FxBool FX_CALL @@ -881,11 +723,6 @@ FX_ENTRY FxBool FX_CALL FxI32 src_stride, void* src_data) { assert(false && "grLfbWriteRegion: Unsupported"); - EnsureInitialized(); - D2DX_LOG("grLfbWriteRegion dst_buffer=%i dst_x=%u dst_y=%u src_format=%u src_width=%u src_height=%u pixelPipeline=%i src_stride=%i src_data=%p\n", - dst_buffer, dst_x, dst_y, src_format, src_width, src_height, pixelPipeline ? 1 : 0, src_stride, src_data); - //FxBool r = (*pgrLfbWriteRegion)(dst_buffer, dst_x, dst_y, src_format, src_width, src_height, pixelPipeline, src_stride, src_data); - //return r; return FXFALSE; } @@ -896,11 +733,6 @@ FX_ENTRY FxBool FX_CALL FxU32 dst_stride, void* dst_data) { assert(false && "grLfbReadRegion: Unsupported"); - EnsureInitialized(); - //FxBool r = (*pgrLfbReadRegion)(src_buffer, src_x, src_y, src_width, src_height, dst_stride, dst_data); - //D2DX_LOG("grLfbReadRegion src_buffer=%i src_x=%u src_y=%u src_width=%u src_height=%u dst_stride=%u dst_data=%p -> %i\n", - // src_buffer, src_x, src_y, src_width, src_height, dst_stride, dst_data, r ? 1 : 0); - //return r; return FXFALSE; } @@ -909,9 +741,6 @@ FX_ENTRY void FX_CALL FxBool enable) { assert(false && "grTexMultibase: Unsupported"); - EnsureInitialized(); - D2DX_LOG("grTexMultibase tmu=%i enable=%i\n", tmu, enable); - // (*pgrTexMultibase)(tmu, enable); } FX_ENTRY void FX_CALL @@ -922,24 +751,12 @@ FX_ENTRY void FX_CALL GrTexInfo* info) { assert(false && "grTexMultibaseAddress: Unsupported"); - EnsureInitialized(); - D2DX_LOG("grTexMultibaseAddress tmu=%i range=%u startAddress=%08x evenOdd=%u info=%p\n", tmu, range, startAddress, evenOdd, info); - //(*pgrTexMultibaseAddress)(tmu, range, startAddress, evenOdd, info); } FX_ENTRY void FX_CALL grDrawTriangle(const void* a, const void* b, const void* c) { assert(false && "grDrawTriangle: Unsupported"); - EnsureInitialized(); - D2DX_LOG("grDrawTriangle a=%p b=%p c=%p\n", a, b, c); - - if (g_d2dxContext->IsDrawingDisabled()) - { - return; - } - - //(*pgrDrawTriangle)(a, b, c); } FX_ENTRY void FX_CALL @@ -949,109 +766,72 @@ FX_ENTRY void FX_CALL ) { assert(false && "grAADrawTriangle: Unsupported"); - EnsureInitialized(); - D2DX_LOG("grAADrawTriangle a=%p b=%p c=%p ab_antialias=%i bc_antialias=%i ca_antialias=%i\n", a, b, c, ab_antialias ? 1 : 0, bc_antialias ? 1 : 0, ca_antialias ? 1 : 0); - //(*pgrAADrawTriangle)(a, b, c, ab_antialias, bc_antialias, ca_antialias); } FX_ENTRY void FX_CALL grClipWindow(FxU32 minx, FxU32 miny, FxU32 maxx, FxU32 maxy) { assert(false && "grClipWindow: Unsupported"); - EnsureInitialized(); - D2DX_LOG("grClipWindow minx=%u miny=%u maxx=%u maxy=%u\n", minx, miny, maxx, maxy); - //(*pgrClipWindow)(minx, miny, maxx, maxy); } FX_ENTRY void FX_CALL grSstOrigin(GrOriginLocation_t origin) { assert(false && "grSstOrigin: Unsupported"); - EnsureInitialized(); - D2DX_LOG("grSstOrigin origin=%i\n", origin); - //(*pgrSstOrigin)(origin); } FX_ENTRY void FX_CALL grCullMode(GrCullMode_t mode) { assert(false && "grCullMode: Unsupported"); - EnsureInitialized(); - D2DX_LOG("grCullMode mode=%i\n", mode); - //(*pgrCullMode)(mode); } FX_ENTRY void FX_CALL grDepthBiasLevel(FxI32 level) { assert(false && "grDepthBiasLevel: Unsupported"); - EnsureInitialized(); - D2DX_LOG("grDepthBiasLevel level=%i\n", level); - //(*pgrDepthBiasLevel)(level); } FX_ENTRY void FX_CALL grDepthBufferFunction(GrCmpFnc_t function) { assert(false && "grDepthBufferFunction: Unsupported"); - EnsureInitialized(); - D2DX_LOG("grDepthBufferFunction function=%i\n", function); - //(*pgrDepthBufferFunction)(function); } FX_ENTRY void FX_CALL grDepthBufferMode(GrDepthBufferMode_t mode) { assert(false && "grDepthBufferMode: Unsupported"); - EnsureInitialized(); - D2DX_LOG("grDepthBufferMode mode=%i\n", mode); - //(*pgrDepthBufferMode)(mode); } FX_ENTRY void FX_CALL grFogColorValue(GrColor_t fogcolor) { assert(false && "grFogColorValue: Unsupported"); - EnsureInitialized(); - D2DX_LOG("grFogColorValue fogcolor=%u\n", fogcolor); - //(*pgrFogColorValue)(fogcolor); } FX_ENTRY void FX_CALL grFogMode(GrFogMode_t mode) { assert(false && "grFogMode: Unsupported"); - EnsureInitialized(); - D2DX_LOG("grFogMode mode=%i\n", mode); - //(*pgrFogMode)(mode); } FX_ENTRY void FX_CALL grFogTable(const GrFog_t ft[]) { assert(false && "grFogTable: Unsupported"); - EnsureInitialized(); - D2DX_LOG("grFogTable ft=%p\n", ft); - //(*pgrFogTable)(ft); } FX_ENTRY void FX_CALL grSplash(float x, float y, float width, float height, FxU32 frame) { assert(false && "grSplash: Unsupported"); - EnsureInitialized(); - D2DX_LOG("grSplash x=%f y=%f width=%f height=%f frame=%u\n", x, y, width, height, frame); - //(*pgrSplash)(x, y, width, height, frame); } FX_ENTRY GrProc FX_CALL grGetProcAddress(char* procName) { assert(false && "grGetProcAddress: Unsupported"); - EnsureInitialized(); - D2DX_LOG("grGetProcAddress procName=%p\n", procName); - //GrProc r = (*pgrGetProcAddress)(procName); - //return r; return NULL; } @@ -1059,19 +839,12 @@ FX_ENTRY void FX_CALL grDepthRange(FxFloat n, FxFloat f) { assert(false && "grDepthRange: Unsupported"); - EnsureInitialized(); - D2DX_LOG("grDepthRange n=%f f=%f\n", n, f); - //(*pgrDepthRange)(n, f); } FX_ENTRY void FX_CALL grTexNCCTable(GrNCCTable_t table) { assert(false && "grTexNCCTable: Unsupported"); - EnsureInitialized(); - D2DX_LOG("grTexNCCTable table=%u\n", - table); - //(*pgrTexNCCTable)(table); } FX_ENTRY void FX_CALL @@ -1083,21 +856,12 @@ FX_ENTRY void FX_CALL ) { assert(false && "grTexDetailControl: Unsupported"); - EnsureInitialized(); - D2DX_LOG("grTexDetailControl tmu=%i lod_bias=%i detail_scale=%i detail_max=%f\n", - tmu, lod_bias, detail_scale, detail_max); - //(*pgrTexDetailControl)(tmu, lod_bias, detail_scale, detail_max); } FX_ENTRY void FX_CALL grTexLodBiasValue(GrChipID_t tmu, float bias) { assert(false && "grTexLodBiasValue: Unsupported"); - assert(false); - EnsureInitialized(); - D2DX_LOG("grTexLodBiasValue tmu=%u bias=%f\n", - tmu, bias); - //(*pgrTexLodBiasValue)(tmu, bias); } FX_ENTRY void FX_CALL @@ -1111,12 +875,6 @@ FX_ENTRY void FX_CALL void* data) { assert(false && "grTexDownloadMipMapLevel: Unsupported"); - EnsureInitialized(); - D2DX_LOG("grTexDownloadMipMapLevel tmu=%u startAddress=%08x thisLod=%i largeLod=%i aspectRatio=%i format=%i evenOdd=%u data=%p\n", - tmu, startAddress, thisLod, largeLod, aspectRatio, format, evenOdd, data); - //(*pgrTexDownloadMipMapLevel)(tmu, startAddress, thisLod, largeLod, aspectRatio, format, evenOdd, data); - //FxU32 memRequired = grTexTextureMemRequired(evenOdd, info); - //uiConnector->OnTexDownload(tmu, startAddress, ); } FX_ENTRY FxBool FX_CALL @@ -1124,12 +882,6 @@ FX_ENTRY FxBool FX_CALL FxU32 evenOdd, void* data, int start, int end) { assert(false && "grTexDownloadMipMapLevelPartial: Unsupported"); - EnsureInitialized(); - //FxBool r = (*pgrTexDownloadMipMapLevelPartial)(tmu, startAddress, thisLod, largeLod, aspectRatio, format, evenOdd, data, start, end); - //D2DX_LOG("grTexDownloadMipMapLevelPartial tmu=%u startAddress=%08x thisLod=%i largeLod=%i aspectRatio=%i format=%i evenOdd=%u data=%p start=%i end=%i -> %i\n", - //tmu, startAddress, thisLod, largeLod, aspectRatio, format, evenOdd, data, start, end, r ? 1 : 0); - // uiConnector->OnTexDownload(tmu, startAddress); - //return r; return FXFALSE; } @@ -1139,7 +891,6 @@ FX_ENTRY FxU32 FX_CALL GrAspectRatio_t aspect, GrTextureFormat_t fmt) { assert(false && "grTexCalcMemRequired: Unsupported"); - EnsureInitialized(); return 0; } @@ -1148,7 +899,6 @@ FX_ENTRY FxU32 FX_CALL GrTexInfo* info) { assert(false && "grTexTextureMemRequired: Unsupported"); - EnsureInitialized(); return 0; } @@ -1156,17 +906,12 @@ FX_ENTRY void FX_CALL grDisableAllEffects(void) { assert(false && "grDisableAllEffects: Unsupported"); - EnsureInitialized(); - D2DX_LOG("grDisableAllEffects\n"); } FX_ENTRY const char* FX_CALL grGetString(FxU32 pname) { - //assert(false && "grGetString: Unsupported"); - EnsureInitialized(); - D2DX_LOG("grGetString pname=%u\n", pname); - return g_d2dxContext->OnGetString(pname); + return D2DXContextFactory::GetInstance()->OnGetString(pname); } FX_ENTRY void FX_CALL @@ -1176,8 +921,46 @@ FX_ENTRY void FX_CALL int end) { assert(false); - EnsureInitialized(); - D2DX_LOG("grTexDownloadTablePartial type=%i data=%p start=%i end=%i\n", type, data, start, end); } } + +static void GetWidthHeightFromTexInfo(const GrTexInfo* info, FxU32* w, FxU32* h) +{ + FxU32 ww = 1 << info->largeLodLog2; + switch (info->aspectRatioLog2) + { + case GR_ASPECT_LOG2_1x1: + *w = ww; + *h = ww; + break; + case GR_ASPECT_LOG2_1x2: + *w = ww / 2; + *h = ww; + break; + case GR_ASPECT_LOG2_2x1: + *w = ww; + *h = ww / 2; + break; + case GR_ASPECT_LOG2_1x4: + *w = ww / 4; + *h = ww; + break; + case GR_ASPECT_LOG2_4x1: + *w = ww; + *h = ww / 4; + break; + case GR_ASPECT_LOG2_1x8: + *w = ww / 8; + *h = ww; + break; + case GR_ASPECT_LOG2_8x1: + *w = ww; + *h = ww / 8; + break; + default: + *w = 0; + *h = 0; + break; + } +} diff --git a/src/d2dx/pch.h b/src/d2dx/pch.h index 408c027..2ae293a 100644 --- a/src/d2dx/pch.h +++ b/src/d2dx/pch.h @@ -22,17 +22,23 @@ #define WIN32_LEAN_AND_MEAN #define __MSC__ -#include #include #include #include #include #include +#include #include +#include +#include #include #include +#include #include +#include +#include +#include #include #include @@ -49,4 +55,9 @@ #include #include +template +using ComPtr = Microsoft::WRL::ComPtr; + +using EventHandle = Microsoft::WRL::Wrappers::HandleT; + #endif //PCH_H diff --git a/src/d2dx/resource.h b/src/d2dx/resource.h index 6a319b1..fbf8098 100644 --- a/src/d2dx/resource.h +++ b/src/d2dx/resource.h @@ -1,12 +1,15 @@ //{{NO_DEPENDENCIES}} // Microsoft Visual C++ generated include file. // Used by d2dx.rc +// +#define IDR_SGD2FR_MPQ 101 +#define IDR_SGD2FR_DLL 102 // Next default values for new objects // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS -#define _APS_NEXT_RESOURCE_VALUE 101 +#define _APS_NEXT_RESOURCE_VALUE 102 #define _APS_NEXT_COMMAND_VALUE 40001 #define _APS_NEXT_CONTROL_VALUE 1001 #define _APS_NEXT_SYMED_VALUE 101 diff --git a/src/d2dxintegration/D2DXIntegration.cpp b/src/d2dxintegration/D2DXIntegration.cpp deleted file mode 100644 index 456111d..0000000 --- a/src/d2dxintegration/D2DXIntegration.cpp +++ /dev/null @@ -1,62 +0,0 @@ -#include "D2DXIntegration.h" -#include - -typedef void (*PD2DX_SetCustomResolution)(int32_t width, int32_t height); -typedef void (*PD2DX_GetSuggestedCustomResolution)(int32_t* width, int32_t* height); - -static HMODULE hGlide3x = NULL; -static bool initialized = false; -static PD2DX_SetCustomResolution d2dxSetCustomResolution; -static PD2DX_GetSuggestedCustomResolution d2dxGetSuggestedCustomResolution; - -static void EnsureInitialized() -{ - if (initialized) - { - return; - } - - hGlide3x = LoadLibraryW(L"glide3x.dll"); - - if (hGlide3x) - { - d2dxSetCustomResolution = (PD2DX_SetCustomResolution)GetProcAddress(hGlide3x, "D2DX_SetCustomResolution"); - d2dxGetSuggestedCustomResolution = (PD2DX_GetSuggestedCustomResolution)GetProcAddress(hGlide3x, "D2DX_GetSuggestedCustomResolution"); - } - - initialized = true; -} - -bool d2dx::IsD2DXLoaded() -{ - EnsureInitialized(); - - return d2dxSetCustomResolution != nullptr && - d2dxGetSuggestedCustomResolution != nullptr; -} - -void d2dx::SetCustomResolution(int32_t width, int32_t height) -{ - EnsureInitialized(); - - if (d2dxSetCustomResolution) - { - d2dxSetCustomResolution(width, height); - } -} - -void d2dx::GetSuggestedCustomResolution(int32_t* width, int32_t* height) -{ - EnsureInitialized(); - - if (d2dxGetSuggestedCustomResolution) - { - d2dxGetSuggestedCustomResolution(width, height); - } - else - { - *width = 0; - *height = 0; - } -} - diff --git a/src/d2dxintegration/D2DXIntegration.h b/src/d2dxintegration/D2DXIntegration.h deleted file mode 100644 index a567d4e..0000000 --- a/src/d2dxintegration/D2DXIntegration.h +++ /dev/null @@ -1,14 +0,0 @@ -#pragma once -#ifndef D2DX_INTEGRATION_H -#define D2DX_INTEGRATION_H - -#include - -namespace d2dx -{ - bool IsD2DXLoaded(); - void SetCustomResolution(int32_t width, int32_t height); - void GetSuggestedCustomResolution(int32_t* width, int32_t* height); -} - -#endif diff --git a/src/d2dxtests/TestBatch.cpp b/src/d2dxtests/TestBatch.cpp index 07020e7..c1a3ba9 100644 --- a/src/d2dxtests/TestBatch.cpp +++ b/src/d2dxtests/TestBatch.cpp @@ -55,15 +55,14 @@ namespace d2dxtests Assert::AreEqual(0U, batch.GetTextureIndex()); Assert::AreEqual(GameAddress::Unknown, batch.GetGameAddress()); Assert::AreEqual(0U, batch.GetHash()); - Assert::AreEqual(2, batch.GetHeight()); + Assert::AreEqual(2, batch.GetTextureHeight()); Assert::AreEqual(0, batch.GetPaletteIndex()); - Assert::AreEqual(PrimitiveType::Points, batch.GetPrimitiveType()); Assert::AreEqual(RgbCombine::ColorMultipliedByTexture, batch.GetRgbCombine()); Assert::AreEqual(0, batch.GetStartVertex()); Assert::AreEqual(TextureCategory::Unknown, batch.GetTextureCategory()); Assert::AreEqual(-D2DX_TMU_ADDRESS_ALIGNMENT, batch.GetTextureStartAddress()); Assert::AreEqual(0U, batch.GetVertexCount()); - Assert::AreEqual(2, batch.GetWidth()); + Assert::AreEqual(2, batch.GetTextureWidth()); } TEST_METHOD(SetAlphaBlend) @@ -79,15 +78,14 @@ namespace d2dxtests Assert::AreEqual(0U, batch.GetTextureIndex()); Assert::AreEqual(GameAddress::Unknown, batch.GetGameAddress()); Assert::AreEqual(0U, batch.GetHash()); - Assert::AreEqual(2, batch.GetHeight()); + Assert::AreEqual(2, batch.GetTextureHeight()); Assert::AreEqual(0, batch.GetPaletteIndex()); - Assert::AreEqual(PrimitiveType::Points, batch.GetPrimitiveType()); Assert::AreEqual(RgbCombine::ColorMultipliedByTexture, batch.GetRgbCombine()); Assert::AreEqual(0, batch.GetStartVertex()); Assert::AreEqual(TextureCategory::Unknown, batch.GetTextureCategory()); Assert::AreEqual(-D2DX_TMU_ADDRESS_ALIGNMENT, batch.GetTextureStartAddress()); Assert::AreEqual(0U, batch.GetVertexCount()); - Assert::AreEqual(2, batch.GetWidth()); + Assert::AreEqual(2, batch.GetTextureWidth()); } } @@ -104,15 +102,14 @@ namespace d2dxtests Assert::AreEqual(0U, batch.GetTextureIndex()); Assert::AreEqual(GameAddress::Unknown, batch.GetGameAddress()); Assert::AreEqual(0U, batch.GetHash()); - Assert::AreEqual(2, batch.GetHeight()); + Assert::AreEqual(2, batch.GetTextureHeight()); Assert::AreEqual(0, batch.GetPaletteIndex()); - Assert::AreEqual(PrimitiveType::Points, batch.GetPrimitiveType()); Assert::AreEqual(RgbCombine::ColorMultipliedByTexture, batch.GetRgbCombine()); Assert::AreEqual(0, batch.GetStartVertex()); Assert::AreEqual(TextureCategory::Unknown, batch.GetTextureCategory()); Assert::AreEqual(-D2DX_TMU_ADDRESS_ALIGNMENT, batch.GetTextureStartAddress()); Assert::AreEqual(0U, batch.GetVertexCount()); - Assert::AreEqual(2, batch.GetWidth()); + Assert::AreEqual(2, batch.GetTextureWidth()); } } @@ -130,15 +127,14 @@ namespace d2dxtests Assert::AreEqual(i & 511, batch.GetTextureIndex()); Assert::AreEqual(GameAddress::Unknown, batch.GetGameAddress()); Assert::AreEqual(0U, batch.GetHash()); - Assert::AreEqual(2, batch.GetHeight()); + Assert::AreEqual(2, batch.GetTextureHeight()); Assert::AreEqual(0, batch.GetPaletteIndex()); - Assert::AreEqual(PrimitiveType::Points, batch.GetPrimitiveType()); Assert::AreEqual(RgbCombine::ColorMultipliedByTexture, batch.GetRgbCombine()); Assert::AreEqual(0, batch.GetStartVertex()); Assert::AreEqual(TextureCategory::Unknown, batch.GetTextureCategory()); Assert::AreEqual(-D2DX_TMU_ADDRESS_ALIGNMENT, batch.GetTextureStartAddress()); Assert::AreEqual(0U, batch.GetVertexCount()); - Assert::AreEqual(2, batch.GetWidth()); + Assert::AreEqual(2, batch.GetTextureWidth()); } } @@ -155,15 +151,14 @@ namespace d2dxtests Assert::AreEqual(0U, batch.GetTextureIndex()); Assert::AreEqual((GameAddress)i, batch.GetGameAddress()); Assert::AreEqual(0U, batch.GetHash()); - Assert::AreEqual(2, batch.GetHeight()); + Assert::AreEqual(2, batch.GetTextureHeight()); Assert::AreEqual(0, batch.GetPaletteIndex()); - Assert::AreEqual(PrimitiveType::Points, batch.GetPrimitiveType()); Assert::AreEqual(RgbCombine::ColorMultipliedByTexture, batch.GetRgbCombine()); Assert::AreEqual(0, batch.GetStartVertex()); Assert::AreEqual(TextureCategory::Unknown, batch.GetTextureCategory()); Assert::AreEqual(-D2DX_TMU_ADDRESS_ALIGNMENT, batch.GetTextureStartAddress()); Assert::AreEqual(0U, batch.GetVertexCount()); - Assert::AreEqual(2, batch.GetWidth()); + Assert::AreEqual(2, batch.GetTextureWidth()); } } @@ -180,15 +175,14 @@ namespace d2dxtests Assert::AreEqual(0U, batch.GetTextureIndex()); Assert::AreEqual(GameAddress::Unknown, batch.GetGameAddress()); Assert::AreEqual((uint32_t)i, batch.GetHash()); - Assert::AreEqual(2, batch.GetHeight()); + Assert::AreEqual(2, batch.GetTextureHeight()); Assert::AreEqual(0, batch.GetPaletteIndex()); - Assert::AreEqual(PrimitiveType::Points, batch.GetPrimitiveType()); Assert::AreEqual(RgbCombine::ColorMultipliedByTexture, batch.GetRgbCombine()); Assert::AreEqual(0, batch.GetStartVertex()); Assert::AreEqual(TextureCategory::Unknown, batch.GetTextureCategory()); Assert::AreEqual(-D2DX_TMU_ADDRESS_ALIGNMENT, batch.GetTextureStartAddress()); Assert::AreEqual(0U, batch.GetVertexCount()); - Assert::AreEqual(2, batch.GetWidth()); + Assert::AreEqual(2, batch.GetTextureWidth()); } } @@ -207,15 +201,14 @@ namespace d2dxtests Assert::AreEqual(0U, batch.GetTextureIndex()); Assert::AreEqual(GameAddress::Unknown, batch.GetGameAddress()); Assert::AreEqual(0U, batch.GetHash()); - Assert::AreEqual(1 << h, batch.GetHeight()); + Assert::AreEqual(1 << h, batch.GetTextureHeight()); Assert::AreEqual(0, batch.GetPaletteIndex()); - Assert::AreEqual(PrimitiveType::Points, batch.GetPrimitiveType()); Assert::AreEqual(RgbCombine::ColorMultipliedByTexture, batch.GetRgbCombine()); Assert::AreEqual(0, batch.GetStartVertex()); Assert::AreEqual(TextureCategory::Unknown, batch.GetTextureCategory()); Assert::AreEqual(-D2DX_TMU_ADDRESS_ALIGNMENT, batch.GetTextureStartAddress()); Assert::AreEqual(0U, batch.GetVertexCount()); - Assert::AreEqual(1 << w, batch.GetWidth()); + Assert::AreEqual(1 << w, batch.GetTextureWidth()); } } } @@ -233,40 +226,14 @@ namespace d2dxtests Assert::AreEqual(0U, batch.GetTextureIndex()); Assert::AreEqual(GameAddress::Unknown, batch.GetGameAddress()); Assert::AreEqual(0U, batch.GetHash()); - Assert::AreEqual(2, batch.GetHeight()); + Assert::AreEqual(2, batch.GetTextureHeight()); Assert::AreEqual(i, batch.GetPaletteIndex()); - Assert::AreEqual(PrimitiveType::Points, batch.GetPrimitiveType()); Assert::AreEqual(RgbCombine::ColorMultipliedByTexture, batch.GetRgbCombine()); Assert::AreEqual(0, batch.GetStartVertex()); Assert::AreEqual(TextureCategory::Unknown, batch.GetTextureCategory()); Assert::AreEqual(-D2DX_TMU_ADDRESS_ALIGNMENT, batch.GetTextureStartAddress()); Assert::AreEqual(0U, batch.GetVertexCount()); - Assert::AreEqual(2, batch.GetWidth()); - } - } - - TEST_METHOD(SetPrimitiveType) - { - Batch batch; - for (int32_t i = 0; i < (int32_t)PrimitiveType::Count; ++i) - { - batch.SetPrimitiveType((PrimitiveType)i); - Assert::IsFalse(batch.IsValid()); - Assert::AreEqual(AlphaBlend::Opaque, batch.GetAlphaBlend()); - Assert::AreEqual(AlphaCombine::One, batch.GetAlphaCombine()); - Assert::AreEqual(0U, batch.GetTextureAtlas()); - Assert::AreEqual(0U, batch.GetTextureIndex()); - Assert::AreEqual(GameAddress::Unknown, batch.GetGameAddress()); - Assert::AreEqual(0U, batch.GetHash()); - Assert::AreEqual(2, batch.GetHeight()); - Assert::AreEqual(0, batch.GetPaletteIndex()); - Assert::AreEqual((PrimitiveType)i, batch.GetPrimitiveType()); - Assert::AreEqual(RgbCombine::ColorMultipliedByTexture, batch.GetRgbCombine()); - Assert::AreEqual(0, batch.GetStartVertex()); - Assert::AreEqual(TextureCategory::Unknown, batch.GetTextureCategory()); - Assert::AreEqual(-D2DX_TMU_ADDRESS_ALIGNMENT, batch.GetTextureStartAddress()); - Assert::AreEqual(0U, batch.GetVertexCount()); - Assert::AreEqual(2, batch.GetWidth()); + Assert::AreEqual(2, batch.GetTextureWidth()); } } @@ -283,15 +250,14 @@ namespace d2dxtests Assert::AreEqual(0U, batch.GetTextureIndex()); Assert::AreEqual(GameAddress::Unknown, batch.GetGameAddress()); Assert::AreEqual(0U, batch.GetHash()); - Assert::AreEqual(2, batch.GetHeight()); + Assert::AreEqual(2, batch.GetTextureHeight()); Assert::AreEqual(0, batch.GetPaletteIndex()); - Assert::AreEqual(PrimitiveType::Points, batch.GetPrimitiveType()); Assert::AreEqual((RgbCombine)i, batch.GetRgbCombine()); Assert::AreEqual(0, batch.GetStartVertex()); Assert::AreEqual(TextureCategory::Unknown, batch.GetTextureCategory()); Assert::AreEqual(-D2DX_TMU_ADDRESS_ALIGNMENT, batch.GetTextureStartAddress()); Assert::AreEqual(0U, batch.GetVertexCount()); - Assert::AreEqual(2, batch.GetWidth()); + Assert::AreEqual(2, batch.GetTextureWidth()); } } @@ -308,15 +274,14 @@ namespace d2dxtests Assert::AreEqual(0U, batch.GetTextureIndex()); Assert::AreEqual(GameAddress::Unknown, batch.GetGameAddress()); Assert::AreEqual(0U, batch.GetHash()); - Assert::AreEqual(2, batch.GetHeight()); + Assert::AreEqual(2, batch.GetTextureHeight()); Assert::AreEqual(0, batch.GetPaletteIndex()); - Assert::AreEqual(PrimitiveType::Points, batch.GetPrimitiveType()); Assert::AreEqual(RgbCombine::ColorMultipliedByTexture, batch.GetRgbCombine()); Assert::AreEqual(i, batch.GetStartVertex()); Assert::AreEqual(TextureCategory::Unknown, batch.GetTextureCategory()); Assert::AreEqual(-D2DX_TMU_ADDRESS_ALIGNMENT, batch.GetTextureStartAddress()); Assert::AreEqual(0U, batch.GetVertexCount()); - Assert::AreEqual(2, batch.GetWidth()); + Assert::AreEqual(2, batch.GetTextureWidth()); } } @@ -333,15 +298,14 @@ namespace d2dxtests Assert::AreEqual(0U, batch.GetTextureIndex()); Assert::AreEqual(GameAddress::Unknown, batch.GetGameAddress()); Assert::AreEqual(0U, batch.GetHash()); - Assert::AreEqual(2, batch.GetHeight()); + Assert::AreEqual(2, batch.GetTextureHeight()); Assert::AreEqual(0, batch.GetPaletteIndex()); - Assert::AreEqual(PrimitiveType::Points, batch.GetPrimitiveType()); Assert::AreEqual(RgbCombine::ColorMultipliedByTexture, batch.GetRgbCombine()); Assert::AreEqual(0, batch.GetStartVertex()); Assert::AreEqual((TextureCategory)i, batch.GetTextureCategory()); Assert::AreEqual(-D2DX_TMU_ADDRESS_ALIGNMENT, batch.GetTextureStartAddress()); Assert::AreEqual(0U, batch.GetVertexCount()); - Assert::AreEqual(2, batch.GetWidth()); + Assert::AreEqual(2, batch.GetTextureWidth()); } } @@ -358,15 +322,14 @@ namespace d2dxtests Assert::AreEqual(0U, batch.GetTextureIndex()); Assert::AreEqual(GameAddress::Unknown, batch.GetGameAddress()); Assert::AreEqual(0U, batch.GetHash()); - Assert::AreEqual(2, batch.GetHeight()); + Assert::AreEqual(2, batch.GetTextureHeight()); Assert::AreEqual(0, batch.GetPaletteIndex()); - Assert::AreEqual(PrimitiveType::Points, batch.GetPrimitiveType()); Assert::AreEqual(RgbCombine::ColorMultipliedByTexture, batch.GetRgbCombine()); Assert::AreEqual(0, batch.GetStartVertex()); Assert::AreEqual(TextureCategory::Unknown, batch.GetTextureCategory()); Assert::AreEqual(i, batch.GetTextureStartAddress()); Assert::AreEqual(0U, batch.GetVertexCount()); - Assert::AreEqual(2, batch.GetWidth()); + Assert::AreEqual(2, batch.GetTextureWidth()); } } @@ -383,15 +346,14 @@ namespace d2dxtests Assert::AreEqual(0U, batch.GetTextureIndex()); Assert::AreEqual(GameAddress::Unknown, batch.GetGameAddress()); Assert::AreEqual(0U, batch.GetHash()); - Assert::AreEqual(2, batch.GetHeight()); + Assert::AreEqual(2, batch.GetTextureHeight()); Assert::AreEqual(0, batch.GetPaletteIndex()); - Assert::AreEqual(PrimitiveType::Points, batch.GetPrimitiveType()); Assert::AreEqual(RgbCombine::ColorMultipliedByTexture, batch.GetRgbCombine()); Assert::AreEqual(0, batch.GetStartVertex()); Assert::AreEqual(TextureCategory::Unknown, batch.GetTextureCategory()); Assert::AreEqual(-D2DX_TMU_ADDRESS_ALIGNMENT, batch.GetTextureStartAddress()); Assert::AreEqual(i, batch.GetVertexCount()); - Assert::AreEqual(2, batch.GetWidth()); + Assert::AreEqual(2, batch.GetTextureWidth()); } } }; diff --git a/src/d2dxtests/TestMetrics.cpp b/src/d2dxtests/TestMetrics.cpp new file mode 100644 index 0000000..35804b5 --- /dev/null +++ b/src/d2dxtests/TestMetrics.cpp @@ -0,0 +1,123 @@ +/* + This file is part of D2DX. + + Copyright (C) 2021 Bolrog + + D2DX is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + D2DX is distributed in the hope that it will be 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 D2DX. If not, see . +*/ +#include "pch.h" +#include +#include "CppUnitTest.h" +#include "../d2dx/Metrics.h" + +using namespace Microsoft::VisualStudio::CppUnitTestFramework; +using namespace d2dx; +using namespace DirectX; + +namespace Microsoft +{ + namespace VisualStudio + { + namespace CppUnitTestFramework + { + template<> static std::wstring ToString(const Offset& t) { return ToString(t.x) + L", " + ToString(t.y); } + template<> static std::wstring ToString(const Size& t) { return ToString(t.width) + L"x" + ToString(t.height); } + } + } +} + +namespace d2dxtests +{ + TEST_CLASS(TestStandardMetrics) + { + public: + TEST_METHOD(TestGetStandardDesktopSizes) + { + auto standardDesktopSizes = d2dx::Metrics::GetStandardDesktopSizes(); + Assert::IsTrue(standardDesktopSizes.capacity > 8); + } + + TEST_METHOD(TestGetGameSize) + { + auto suggestedGameSize = d2dx::Metrics::GetSuggestedGameSize({ 1920, 1080 }, false); + Assert::AreEqual(Size(720, 540), suggestedGameSize); + + // Odd desktop size that isn't large enough to divide down. + suggestedGameSize = d2dx::Metrics::GetSuggestedGameSize({ 1000, 500 }, true); + Assert::AreEqual(Size(1000, 500), suggestedGameSize); + + // Odd desktop size where a 2x integer scale would result in a too small game size. + suggestedGameSize = d2dx::Metrics::GetSuggestedGameSize({ 2001, 1003 }, true); + Assert::AreEqual(Size(1436, 720), suggestedGameSize); + } + + void AssertThatGameSizeIsIntegerScale(Size desktopSize, bool wide, bool lenient) + { + auto suggestedGameSize = d2dx::Metrics::GetSuggestedGameSize(desktopSize, wide); + auto renderRect = d2dx::Metrics::GetRenderRect(suggestedGameSize, desktopSize, wide); + Assert::IsTrue(renderRect.offset.x >= 0); + Assert::IsTrue(renderRect.offset.y >= 0); + Assert::IsTrue(renderRect.size.width > 0); + Assert::IsTrue(renderRect.size.height > 0); + Assert::IsTrue((renderRect.offset.x + renderRect.size.width) <= desktopSize.width); + Assert::IsTrue((renderRect.offset.y + renderRect.size.height) <= desktopSize.height); + + if (renderRect.offset.x > 0 && renderRect.offset.y > 0) + { + Assert::IsTrue(renderRect.offset.x < 16 || renderRect.offset.y < 16); + } + + int32_t reconstructedDesktopWidth = renderRect.size.width + renderRect.offset.x * 2; + int32_t reconstructedDesktopHeight = renderRect.size.height + renderRect.offset.y * 2; + + if (lenient) + { + /* The "remainder" on the right may not be exactly equal to desktop width. */ + Assert::IsTrue(desktopSize.width == reconstructedDesktopWidth || desktopSize.width == reconstructedDesktopWidth + 1); + + /* The "remainder" on the bottom may not be exactly equal to desktop height. */ + Assert::IsTrue(desktopSize.height == reconstructedDesktopHeight || desktopSize.height == reconstructedDesktopHeight + 1); + } + else + { + Assert::AreEqual(desktopSize.width, reconstructedDesktopWidth); + Assert::AreEqual(desktopSize.height, reconstructedDesktopHeight); + } + } + + TEST_METHOD(TestRenderRectsAreGoodForStandardDesktopSizes) + { + auto standardDesktopSizes = d2dx::Metrics::GetStandardDesktopSizes(); + for (uint32_t i = 0; i < standardDesktopSizes.capacity; ++i) + { + AssertThatGameSizeIsIntegerScale(standardDesktopSizes.items[i], false, false); + AssertThatGameSizeIsIntegerScale(standardDesktopSizes.items[i], true, false); + } + } + + TEST_METHOD(TestRenderRectsAreGoodForNonStandardDesktopSizes) + { + for (int32_t width = 50; width < 1600; ++width) + { + AssertThatGameSizeIsIntegerScale({ width, 503 }, false, true); + AssertThatGameSizeIsIntegerScale({ width, 503 }, true, true); + } + for (int32_t height = 50; height < 1600; ++height) + { + AssertThatGameSizeIsIntegerScale({ 614, height }, false, true); + AssertThatGameSizeIsIntegerScale({ 614, height }, true, true); + } + } + }; +} diff --git a/src/d2dxtests/TestSimd.cpp b/src/d2dxtests/TestSimd.cpp index 24e254a..891f94b 100644 --- a/src/d2dxtests/TestSimd.cpp +++ b/src/d2dxtests/TestSimd.cpp @@ -19,8 +19,9 @@ #include "pch.h" #include #include "CppUnitTest.h" -#include "../d2dx/Simd.h" +#include "../d2dx/SimdSse2.h" +using namespace Microsoft::WRL; using namespace Microsoft::VisualStudio::CppUnitTestFramework; using namespace d2dx; @@ -31,12 +32,12 @@ namespace d2dxtests public: TEST_METHOD(Create) { - auto simd = Simd::Create(); + auto simd = std::make_shared(); } TEST_METHOD(FindUInt32) { - auto simd = Simd::Create(); + auto simd = std::make_shared(); alignas(64) std::array items; diff --git a/src/d2dxtests/TestTextureCache.cpp b/src/d2dxtests/TestTextureCache.cpp index fbcec0c..0b4fdcc 100644 --- a/src/d2dxtests/TestTextureCache.cpp +++ b/src/d2dxtests/TestTextureCache.cpp @@ -21,7 +21,7 @@ #include "CppUnitTest.h" #include "../d2dx/Batch.h" -#include "../d2dx/Simd.h" +#include "../d2dx/SimdSse2.h" #include "../d2dx/Types.h" #include "../d2dx/TextureCache.h" @@ -35,49 +35,47 @@ namespace d2dxtests public: TEST_METHOD(CreateAtlas) { - auto simd = Simd::Create(); - auto textureProcessor = std::make_shared(); + auto simd = std::make_shared(); for (int32_t h = 3; h <= 8; ++h) { for (int32_t w = 3; w <= 8; ++w) { - TextureCache textureCache(1 << w, 1 << h, 1024, 512, NULL, simd, textureProcessor); + auto textureCache = std::make_unique( + 1 << w, 1 << h, 1024, 512, (ID3D11Device*)nullptr, simd); } } } TEST_METHOD(FindNonExistentTexture) { - auto simd = Simd::Create(); - auto textureProcessor = std::make_shared(); - TextureCache textureCache(256, 128, 2048, 512, NULL, simd, textureProcessor); - auto tcl = textureCache.FindTexture(0x12345678, -1); + auto simd = std::make_shared(); + auto textureCache = std::make_unique(256, 128, 2048, 512, (ID3D11Device*)nullptr, simd); + auto tcl = textureCache->FindTexture(0x12345678, -1); Assert::AreEqual((int16_t)-1, tcl._textureAtlas); Assert::AreEqual((int16_t)-1, tcl._textureIndex); } TEST_METHOD(InsertAndFindTextures) { - auto simd = Simd::Create(); - auto textureProcessor = std::make_shared(); + auto simd = std::make_shared(); std::array tmuData; Batch batch; batch.SetTextureStartAddress(0); batch.SetTextureSize(256, 128); - TextureCache textureCache(256, 128, 64, 512, NULL, simd, textureProcessor); + auto textureCache = std::make_unique(256, 128, 64, 512, (ID3D11Device*)nullptr, simd); for (uint32_t i = 0; i < 64; ++i) { uint32_t hash = (0xFF << 24) | (i << 16) | (i << 8) | i; - textureCache.InsertTexture(hash, batch, (const uint8_t*)tmuData.data()); + textureCache->InsertTexture(hash, batch, (const uint8_t*)tmuData.data(), (uint32_t)tmuData.size()); } for (uint32_t i = 0; i < 64; ++i) { uint32_t hash = (0xFF << 24) | (i << 16) | (i << 8) | i; - auto tcl = textureCache.FindTexture(hash, -1); + auto tcl = textureCache->FindTexture(hash, -1); Assert::AreEqual((int16_t)0, tcl._textureAtlas); Assert::AreEqual((int16_t)i, tcl._textureIndex); } @@ -85,20 +83,19 @@ namespace d2dxtests TEST_METHOD(FirstInsertedTextureIsReplaced) { - auto simd = Simd::Create(); - auto textureProcessor = std::make_shared(); + auto simd = std::make_shared(); std::array tmuData; Batch batch; batch.SetTextureStartAddress(0); batch.SetTextureSize(256, 128); - TextureCache textureCache(256, 128, 64, 512, NULL, simd, textureProcessor); + auto textureCache = std::make_unique(256, 128, 64, 512, (ID3D11Device*)nullptr, simd); for (uint32_t i = 0; i < 65; ++i) { uint32_t hash = (0xFF << 24) | (i << 16) | (i << 8) | i; - auto tcl = textureCache.InsertTexture(hash, batch, (const uint8_t*)tmuData.data()); + auto tcl = textureCache->InsertTexture(hash, batch, (const uint8_t*)tmuData.data(), (uint32_t)tmuData.size()); if (i == 64) { @@ -115,7 +112,7 @@ namespace d2dxtests for (uint32_t i = 0; i < 64; ++i) { uint32_t hash = (0xFF << 24) | (i << 16) | (i << 8) | i; - auto tcl = textureCache.FindTexture(hash, -1); + auto tcl = textureCache->FindTexture(hash, -1); int16_t expectedTextureAtlas = 0; int16_t expectedTextureIndex = i; @@ -137,15 +134,14 @@ namespace d2dxtests TEST_METHOD(SecondInsertedTextureIsReplacedIfFirstOneWasUsedInFrame) { - auto simd = Simd::Create(); - auto textureProcessor = std::make_shared(); + auto simd = std::make_shared(); std::array tmuData; Batch batch; batch.SetTextureStartAddress(0); batch.SetTextureSize(256, 128); - TextureCache textureCache(256, 128, 64, 512, NULL, simd, textureProcessor); + auto textureCache = std::make_unique(256, 128, 64, 512, (ID3D11Device*)nullptr, simd); for (uint32_t i = 0; i < 65; ++i) { @@ -154,13 +150,13 @@ namespace d2dxtests if (i == 64) { // Simulate new frame and use of texture in slot 0 - textureCache.OnNewFrame(); - auto tcl = textureCache.FindTexture(0xFF000000, -1); + textureCache->OnNewFrame(); + auto tcl = textureCache->FindTexture(0xFF000000, -1); Assert::AreEqual((int16_t)0, tcl._textureAtlas); Assert::AreEqual((int16_t)0, tcl._textureIndex); } - auto tcl = textureCache.InsertTexture(hash, batch, (const uint8_t*)tmuData.data()); + auto tcl = textureCache->InsertTexture(hash, batch, (const uint8_t*)tmuData.data(), (uint32_t)tmuData.size()); if (i == 64) { @@ -177,7 +173,7 @@ namespace d2dxtests for (uint32_t i = 0; i < 64; ++i) { uint32_t hash = (0xFF << 24) | (i << 16) | (i << 8) | i; - auto tcl = textureCache.FindTexture(hash, -1); + auto tcl = textureCache->FindTexture(hash, -1); int16_t expectedTextureAtlas = 0; int16_t expectedTextureIndex = i; diff --git a/src/d2dxtests/d2dxtests.vcxproj b/src/d2dxtests/d2dxtests.vcxproj index a4bfbf7..a3ecf27 100644 --- a/src/d2dxtests/d2dxtests.vcxproj +++ b/src/d2dxtests/d2dxtests.vcxproj @@ -61,6 +61,8 @@ WIN32;_DEBUG;%(PreprocessorDefinitions);D2DX_UNITTEST true pch.h + /Zc:__cplusplus + stdcpplatest Windows @@ -79,6 +81,8 @@ WIN32;NDEBUG;%(PreprocessorDefinitions);D2DX_UNITTEST true pch.h + /Zc:__cplusplus + stdcpplatest Windows @@ -93,19 +97,13 @@ CompileAsCpp CompileAsCpp - - - - - - - + - + Create @@ -117,14 +115,14 @@ - - + - + + + - diff --git a/src/d2dxtests/d2dxtests.vcxproj.filters b/src/d2dxtests/d2dxtests.vcxproj.filters index 1cd252f..5f0a73a 100644 --- a/src/d2dxtests/d2dxtests.vcxproj.filters +++ b/src/d2dxtests/d2dxtests.vcxproj.filters @@ -9,15 +9,6 @@ - - d2dx - - - d2dx - - - d2dx - d2dx @@ -27,28 +18,17 @@ d2dx - - d2dx - d2dx - - d2dx - - - d2dx - - - d2dx - - - d2dx - d2dx + + d2dx + + @@ -60,37 +40,37 @@ d2dx - + d2dx - + d2dx - + d2dx - + d2dx - + d2dx - + d2dx - + d2dx - + d2dx - + d2dx - + d2dx - + d2dx diff --git a/thirdparty/SlashDiablo-HD b/thirdparty/SlashDiablo-HD deleted file mode 160000 index 2de56f1..0000000 --- a/thirdparty/SlashDiablo-HD +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 2de56f13f2264caac99377afd585a93ccf8f5467 diff --git a/thirdparty/pocketlzma/pocketlzma-LICENSE.txt b/thirdparty/pocketlzma/pocketlzma-LICENSE.txt new file mode 100644 index 0000000..7c21a98 --- /dev/null +++ b/thirdparty/pocketlzma/pocketlzma-LICENSE.txt @@ -0,0 +1,25 @@ +BSD 2-Clause License + +Copyright (c) 2020, Robin Berg Pettersen +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/thirdparty/pocketlzma/pocketlzma.hpp b/thirdparty/pocketlzma/pocketlzma.hpp new file mode 100644 index 0000000..893b6cc --- /dev/null +++ b/thirdparty/pocketlzma/pocketlzma.hpp @@ -0,0 +1,7525 @@ +/*! + * BSD 2-Clause License + + Copyright (c) 2020, Robin Berg Pettersen + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + IMPORTANT - YOU MUST #define POCKETLZMA_LZMA_C_DEFINE exactly ONCE before the first time you include "pocketlzma.hpp" + The reason for this is that the base logic used by PocketLzma is written i C (by Igor Pavlov), thus this is required + to make sure the implementations are included only once. In other words: If pocketlzma.hpp is included several places, + the #define must not be included anywhere but in one of them. + + Example: + #define POCKETLZMA_LZMA_C_DEFINE + #include "pocketlzma.hpp" + + */ + +#ifndef POCKETLZMA_POCKETLZMA_H +#define POCKETLZMA_POCKETLZMA_H + +//LZMA C INCLUDES +#ifdef _WIN32 +#include +#endif + +#include +#include +#include +#include + + +/*** Start of inlined file: PocketLzmaConfig.h ***/ +#define POCKETLZMA_VERSION_MAJOR 1 +#define POCKETLZMA_VERSION_MINOR 0 +#define POCKETLZMA_VERSION_PATCH 0 + +/*** End of inlined file: PocketLzmaConfig.h ***/ + +namespace plz +{ + namespace c + { + +/*** Start of inlined file: 7zTypes.h ***/ +#ifndef __7Z_TYPES_H +#define __7Z_TYPES_H + +//#ifdef _WIN32 +///* #include */ +//#endif + +//#include + +#ifndef EXTERN_C_BEGIN +#ifdef __cplusplus +#define EXTERN_C_BEGIN extern "C" { +#define EXTERN_C_END } +#else +#define EXTERN_C_BEGIN +#define EXTERN_C_END +#endif +#endif + +EXTERN_C_BEGIN + +#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; + +#ifdef _WIN32 + +/* typedef DWORD WRes; */ +typedef unsigned WRes; +#define MY_SRes_HRESULT_FROM_WRes(x) HRESULT_FROM_WIN32(x) + +#else + +typedef int WRes; +#define MY__FACILITY_WIN32 7 +#define MY__FACILITY__WRes MY__FACILITY_WIN32 +#define MY_SRes_HRESULT_FROM_WRes(x) ((HRESULT)(x) <= 0 ? ((HRESULT)(x)) : ((HRESULT) (((x) & 0x0000FFFF) | (MY__FACILITY__WRes << 16) | 0x80000000))) + +#endif + +#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; +#define UINT64_CONST(n) n +#else +typedef long long int Int64; +typedef unsigned long long int UInt64; +#define UINT64_CONST(n) n ## ULL +#endif + +#endif + +#ifdef _LZMA_NO_SYSTEM_SIZE_T +typedef UInt32 SizeT; +#else +typedef size_t SizeT; +#endif + +typedef int BoolInt; +/* typedef BoolInt Bool; */ +#define True 1 +#define False 0 + +#ifdef _WIN32 +#define MY_STD_CALL __stdcall +#else +#define MY_STD_CALL +#endif + +#ifdef _MSC_VER + +#if _MSC_VER >= 1300 +#define MY_NO_INLINE __declspec(noinline) +#else +#define MY_NO_INLINE +#endif + +#define MY_FORCE_INLINE __forceinline + +#define MY_CDECL __cdecl +#define MY_FAST_CALL __fastcall + +#else + +#define MY_NO_INLINE +#define MY_FORCE_INLINE +#define MY_CDECL +#define MY_FAST_CALL + +/* inline keyword : for C++ / C99 */ + +/* GCC, clang: */ +/* +#if defined (__GNUC__) && (__GNUC__ >= 4) +#define MY_FORCE_INLINE __attribute__((always_inline)) +#define MY_NO_INLINE __attribute__((noinline)) +#endif +*/ + +#endif + +/* The following interfaces use first parameter as pointer to structure */ + +typedef struct IByteIn IByteIn; +struct IByteIn +{ + Byte (*Read)(const IByteIn *p); /* reads one byte, returns 0 in case of EOF or error */ +}; +#define IByteIn_Read(p) (p)->Read(p) + +typedef struct IByteOut IByteOut; +struct IByteOut +{ + void (*Write)(const IByteOut *p, Byte b); +}; +#define IByteOut_Write(p, b) (p)->Write(p, b) + +typedef struct ISeqInStream ISeqInStream; +struct ISeqInStream +{ + SRes (*Read)(const ISeqInStream *p, void *buf, size_t *size); + /* if (input(*size) != 0 && output(*size) == 0) means end_of_stream. + (output(*size) < input(*size)) is allowed */ +}; +#define ISeqInStream_Read(p, buf, size) (p)->Read(p, buf, size) + +/* it can return SZ_ERROR_INPUT_EOF */ +SRes SeqInStream_Read(const ISeqInStream *stream, void *buf, size_t size); +SRes SeqInStream_Read2(const ISeqInStream *stream, void *buf, size_t size, SRes errorType); +SRes SeqInStream_ReadByte(const ISeqInStream *stream, Byte *buf); + +typedef struct ISeqOutStream ISeqOutStream; +struct ISeqOutStream +{ + size_t (*Write)(const ISeqOutStream *p, const void *buf, size_t size); + /* Returns: result - the number of actually written bytes. + (result < size) means error */ +}; +#define ISeqOutStream_Write(p, buf, size) (p)->Write(p, buf, size) + +typedef enum +{ + SZ_SEEK_SET = 0, + SZ_SEEK_CUR = 1, + SZ_SEEK_END = 2 +} ESzSeek; + +typedef struct ISeekInStream ISeekInStream; +struct ISeekInStream +{ + SRes (*Read)(const ISeekInStream *p, void *buf, size_t *size); /* same as ISeqInStream::Read */ + SRes (*Seek)(const ISeekInStream *p, Int64 *pos, ESzSeek origin); +}; +#define ISeekInStream_Read(p, buf, size) (p)->Read(p, buf, size) +#define ISeekInStream_Seek(p, pos, origin) (p)->Seek(p, pos, origin) + +typedef struct ILookInStream ILookInStream; +struct ILookInStream +{ + SRes (*Look)(const ILookInStream *p, const 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)(const ILookInStream *p, size_t offset); + /* offset must be <= output(*size) of Look */ + + SRes (*Read)(const ILookInStream *p, void *buf, size_t *size); + /* reads directly (without buffer). It's same as ISeqInStream::Read */ + SRes (*Seek)(const ILookInStream *p, Int64 *pos, ESzSeek origin); +}; + +#define ILookInStream_Look(p, buf, size) (p)->Look(p, buf, size) +#define ILookInStream_Skip(p, offset) (p)->Skip(p, offset) +#define ILookInStream_Read(p, buf, size) (p)->Read(p, buf, size) +#define ILookInStream_Seek(p, pos, origin) (p)->Seek(p, pos, origin) + +SRes LookInStream_LookRead(const ILookInStream *stream, void *buf, size_t *size); +SRes LookInStream_SeekTo(const ILookInStream *stream, UInt64 offset); + +/* reads via ILookInStream::Read */ +SRes LookInStream_Read2(const ILookInStream *stream, void *buf, size_t size, SRes errorType); +SRes LookInStream_Read(const ILookInStream *stream, void *buf, size_t size); + +typedef struct +{ + ILookInStream vt; + const ISeekInStream *realStream; + + size_t pos; + size_t size; /* it's data size */ + + /* the following variables must be set outside */ + Byte *buf; + size_t bufSize; +} CLookToRead2; + +void LookToRead2_CreateVTable(CLookToRead2 *p, int lookahead); + +#define LookToRead2_Init(p) { (p)->pos = (p)->size = 0; } + +typedef struct +{ + ISeqInStream vt; + const ILookInStream *realStream; +} CSecToLook; + +void SecToLook_CreateVTable(CSecToLook *p); + +typedef struct +{ + ISeqInStream vt; + const ILookInStream *realStream; +} CSecToRead; + +void SecToRead_CreateVTable(CSecToRead *p); + +typedef struct ICompressProgress ICompressProgress; + +struct ICompressProgress +{ + SRes (*Progress)(const ICompressProgress *p, UInt64 inSize, UInt64 outSize); + /* Returns: result. (result != SZ_OK) means break. + Value (UInt64)(Int64)-1 for size means unknown value. */ +}; +#define ICompressProgress_Progress(p, inSize, outSize) (p)->Progress(p, inSize, outSize) + +typedef struct ISzAlloc ISzAlloc; +typedef const ISzAlloc * ISzAllocPtr; + +struct ISzAlloc +{ + void *(*Alloc)(ISzAllocPtr p, size_t size); + void (*Free)(ISzAllocPtr p, void *address); /* address can be 0 */ +}; + +#define ISzAlloc_Alloc(p, size) (p)->Alloc(p, size) +#define ISzAlloc_Free(p, a) (p)->Free(p, a) + +/* deprecated */ +#define IAlloc_Alloc(p, size) ISzAlloc_Alloc(p, size) +#define IAlloc_Free(p, a) ISzAlloc_Free(p, a) + +#ifndef MY_offsetof + #ifdef offsetof + #define MY_offsetof(type, m) offsetof(type, m) + /* + #define MY_offsetof(type, m) FIELD_OFFSET(type, m) + */ + #else + #define MY_offsetof(type, m) ((size_t)&(((type *)0)->m)) + #endif +#endif + +#ifndef MY_container_of + +/* +#define MY_container_of(ptr, type, m) container_of(ptr, type, m) +#define MY_container_of(ptr, type, m) CONTAINING_RECORD(ptr, type, m) +#define MY_container_of(ptr, type, m) ((type *)((char *)(ptr) - offsetof(type, m))) +#define MY_container_of(ptr, type, m) (&((type *)0)->m == (ptr), ((type *)(((char *)(ptr)) - MY_offsetof(type, m)))) +*/ + +/* + GCC shows warning: "perhaps the 'offsetof' macro was used incorrectly" + GCC 3.4.4 : classes with constructor + GCC 4.8.1 : classes with non-public variable members" +*/ + +#define MY_container_of(ptr, type, m) ((type *)((char *)(1 ? (ptr) : &((type *)0)->m) - MY_offsetof(type, m))) + +#endif + +#define CONTAINER_FROM_VTBL_SIMPLE(ptr, type, m) ((type *)(ptr)) + +/* +#define CONTAINER_FROM_VTBL(ptr, type, m) CONTAINER_FROM_VTBL_SIMPLE(ptr, type, m) +*/ +#define CONTAINER_FROM_VTBL(ptr, type, m) MY_container_of(ptr, type, m) + +#define CONTAINER_FROM_VTBL_CLS(ptr, type, m) CONTAINER_FROM_VTBL_SIMPLE(ptr, type, m) +/* +#define CONTAINER_FROM_VTBL_CLS(ptr, type, m) CONTAINER_FROM_VTBL(ptr, type, m) +*/ + +#ifdef _WIN32 + +#define CHAR_PATH_SEPARATOR '\\' +#define WCHAR_PATH_SEPARATOR L'\\' +#define STRING_PATH_SEPARATOR "\\" +#define WSTRING_PATH_SEPARATOR L"\\" + +#else + +#define CHAR_PATH_SEPARATOR '/' +#define WCHAR_PATH_SEPARATOR L'/' +#define STRING_PATH_SEPARATOR "/" +#define WSTRING_PATH_SEPARATOR L"/" + +#endif + +EXTERN_C_END + +#endif + +/*** End of inlined file: 7zTypes.h ***/ + + + +/*** Start of inlined file: Precomp.h ***/ +#ifndef __7Z_PRECOMP_H +#define __7Z_PRECOMP_H + + +/*** Start of inlined file: Compiler.h ***/ +#ifndef __7Z_COMPILER_H +#define __7Z_COMPILER_H + +#ifdef _MSC_VER + + #ifdef UNDER_CE + #define RPC_NO_WINDOWS_H + /* #pragma warning(disable : 4115) // '_RPC_ASYNC_STATE' : named type definition in parentheses */ + #pragma warning(disable : 4201) // nonstandard extension used : nameless struct/union + #pragma warning(disable : 4214) // nonstandard extension used : bit field types other than int + #endif + + #if _MSC_VER >= 1300 + #pragma warning(disable : 4996) // This function or variable may be unsafe + #else + #pragma warning(disable : 4511) // copy constructor could not be generated + #pragma warning(disable : 4512) // assignment operator could not be generated + #pragma warning(disable : 4514) // unreferenced inline function has been removed + #pragma warning(disable : 4702) // unreachable code + #pragma warning(disable : 4710) // not inlined + #pragma warning(disable : 4714) // function marked as __forceinline not inlined + #pragma warning(disable : 4786) // identifier was truncated to '255' characters in the debug information + #endif + +#endif + +#define UNUSED_VAR(x) (void)x; +/* #define UNUSED_VAR(x) x=x; */ + +#endif + +/*** End of inlined file: Compiler.h ***/ + +/* #include "7zTypes.h" */ + +#endif + +/*** End of inlined file: Precomp.h ***/ + + +/*** Start of inlined file: Alloc.h ***/ +#ifndef __COMMON_ALLOC_H +#define __COMMON_ALLOC_H + +EXTERN_C_BEGIN + +void *MyAlloc(size_t size); +void MyFree(void *address); + +#ifdef _WIN32 + +void SetLargePageSize(); + +void *MidAlloc(size_t size); +void MidFree(void *address); +void *BigAlloc(size_t size); +void BigFree(void *address); + +#else + +#define MidAlloc(size) MyAlloc(size) +#define MidFree(address) MyFree(address) +#define BigAlloc(size) MyAlloc(size) +#define BigFree(address) MyFree(address) + +#endif + +extern const ISzAlloc g_Alloc; +extern const ISzAlloc g_BigAlloc; +extern const ISzAlloc g_MidAlloc; +extern const ISzAlloc g_AlignedAlloc; + +typedef struct +{ + ISzAlloc vt; + ISzAllocPtr baseAlloc; + unsigned numAlignBits; /* ((1 << numAlignBits) >= sizeof(void *)) */ + size_t offset; /* (offset == (k * sizeof(void *)) && offset < (1 << numAlignBits) */ +} CAlignOffsetAlloc; + +void AlignOffsetAlloc_CreateVTable(CAlignOffsetAlloc *p); + +EXTERN_C_END + +#endif + +/*** End of inlined file: Alloc.h ***/ + + +/*** Start of inlined file: LzmaDec.h ***/ +#ifndef __LZMA_DEC_H +#define __LZMA_DEC_H + +EXTERN_C_BEGIN + +/* #define _LZMA_PROB32 */ +/* _LZMA_PROB32 can increase the speed on some CPUs, + but memory usage for CLzmaDec::probs will be doubled in that case */ + +typedef +#ifdef _LZMA_PROB32 + UInt32 +#else + UInt16 +#endif + CLzmaProb; + +/* ---------- LZMA Properties ---------- */ + +#define LZMA_PROPS_SIZE 5 + +typedef struct _CLzmaProps +{ + Byte lc; + Byte lp; + Byte pb; + Byte _pad_; + 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 +{ + /* Don't change this structure. ASM code can use it. */ + CLzmaProps prop; + CLzmaProb *probs; + CLzmaProb *probs_1664; + Byte *dic; + SizeT dicBufSize; + SizeT dicPos; + const Byte *buf; + UInt32 range; + UInt32 code; + UInt32 processedPos; + UInt32 checkDicSize; + UInt32 reps[4]; + UInt32 state; + UInt32 remainLen; + + UInt32 numProbs; + unsigned tempBufSize; + Byte tempBuf[LZMA_REQUIRED_INPUT_MAX]; +} CLzmaDec; + +#define LzmaDec_Construct(p) { (p)->dic = NULL; (p)->probs = NULL; } + +void LzmaDec_Init(CLzmaDec *p); + +/* There are two types of LZMA streams: + - Stream with end mark. That end mark adds about 6 bytes to compressed size. + - 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, ISzAllocPtr alloc); +void LzmaDec_FreeProbs(CLzmaDec *p, ISzAllocPtr alloc); + +SRes LzmaDec_Allocate(CLzmaDec *p, const Byte *props, unsigned propsSize, ISzAllocPtr alloc); +void LzmaDec_Free(CLzmaDec *p, ISzAllocPtr 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_Construct() + 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, ISzAllocPtr alloc); + +EXTERN_C_END + +#endif + +/*** End of inlined file: LzmaDec.h ***/ + + +/*** Start of inlined file: LzmaEnc.h ***/ +#ifndef __LZMA_ENC_H +#define __LZMA_ENC_H + +EXTERN_C_BEGIN + +#define LZMA_PROPS_SIZE 5 + +typedef struct _CLzmaEncProps +{ + int level; /* 0 <= level <= 9 */ + UInt32 dictSize; /* (1 << 12) <= dictSize <= (1 << 27) for 32-bit version + (1 << 12) <= dictSize <= (3 << 29) for 64-bit version + default = (1 << 24) */ + int lc; /* 0 <= lc <= 8, default = 3 */ + int lp; /* 0 <= lp <= 4, default = 0 */ + int pb; /* 0 <= pb <= 4, default = 2 */ + int algo; /* 0 - fast, 1 - normal, default = 1 */ + int fb; /* 5 <= fb <= 273, default = 32 */ + int btMode; /* 0 - hashChain Mode, 1 - binTree mode - normal, default = 1 */ + int numHashBytes; /* 2, 3 or 4, default = 4 */ + UInt32 mc; /* 1 <= mc <= (1 << 30), default = 32 */ + unsigned writeEndMark; /* 0 - do not write EOPM, 1 - write EOPM, default = 0 */ + int numThreads; /* 1 or 2, default = 2 */ + + UInt64 reduceSize; /* estimated size of data that will be compressed. default = (UInt64)(Int64)-1. + Encoder uses this value to reduce dictionary size */ +} CLzmaEncProps; + +void LzmaEncProps_Init(CLzmaEncProps *p); +void LzmaEncProps_Normalize(CLzmaEncProps *p); +UInt32 LzmaEncProps_GetDictSize(const CLzmaEncProps *props2); + +/* ---------- CLzmaEncHandle Interface ---------- */ + +/* LzmaEnc* functions can return the following exit codes: +SRes: + SZ_OK - OK + SZ_ERROR_MEM - Memory allocation error + SZ_ERROR_PARAM - Incorrect paramater in props + SZ_ERROR_WRITE - ISeqOutStream write callback error + SZ_ERROR_OUTPUT_EOF - output buffer overflow - version with (Byte *) output + SZ_ERROR_PROGRESS - some break from progress callback + SZ_ERROR_THREAD - error in multithreading functions (only for Mt version) +*/ + +typedef void * CLzmaEncHandle; + +CLzmaEncHandle LzmaEnc_Create(ISzAllocPtr alloc); +void LzmaEnc_Destroy(CLzmaEncHandle p, ISzAllocPtr alloc, ISzAllocPtr allocBig); + +SRes LzmaEnc_SetProps(CLzmaEncHandle p, const CLzmaEncProps *props); +void LzmaEnc_SetDataSize(CLzmaEncHandle p, UInt64 expectedDataSiize); +SRes LzmaEnc_WriteProperties(CLzmaEncHandle p, Byte *properties, SizeT *size); +unsigned LzmaEnc_IsWriteEndMark(CLzmaEncHandle p); + +SRes LzmaEnc_Encode(CLzmaEncHandle p, ISeqOutStream *outStream, ISeqInStream *inStream, + ICompressProgress *progress, ISzAllocPtr alloc, ISzAllocPtr allocBig); +SRes LzmaEnc_MemEncode(CLzmaEncHandle p, Byte *dest, SizeT *destLen, const Byte *src, SizeT srcLen, + int writeEndMark, ICompressProgress *progress, ISzAllocPtr alloc, ISzAllocPtr allocBig); + +/* ---------- One Call Interface ---------- */ + +SRes LzmaEncode(Byte *dest, SizeT *destLen, const Byte *src, SizeT srcLen, + const CLzmaEncProps *props, Byte *propsEncoded, SizeT *propsSize, int writeEndMark, + ICompressProgress *progress, ISzAllocPtr alloc, ISzAllocPtr allocBig); + +EXTERN_C_END + +#endif + +/*** End of inlined file: LzmaEnc.h ***/ + + +/*** Start of inlined file: LzmaLib.h ***/ +#ifndef __LZMA_LIB_H +#define __LZMA_LIB_H + +EXTERN_C_BEGIN + +#define MY_STDAPI int MY_STD_CALL + +#define LZMA_PROPS_SIZE 5 + +/* +RAM requirements for LZMA: + for compression: (dictSize * 11.5 + 6 MB) + state_size + for decompression: dictSize + state_size + state_size = (4 + (1.5 << (lc + lp))) KB + by default (lc=3, lp=0), state_size = 16 KB. + +LZMA properties (5 bytes) format + Offset Size Description + 0 1 lc, lp and pb in encoded form. + 1 4 dictSize (little endian). +*/ + +/* +LzmaCompress +------------ + +outPropsSize - + In: the pointer to the size of outProps buffer; *outPropsSize = LZMA_PROPS_SIZE = 5. + Out: the pointer to the size of written properties in outProps buffer; *outPropsSize = LZMA_PROPS_SIZE = 5. + + LZMA Encoder will use defult values for any parameter, if it is + -1 for any from: level, loc, lp, pb, fb, numThreads + 0 for dictSize + +level - compression level: 0 <= level <= 9; + + level dictSize algo fb + 0: 16 KB 0 32 + 1: 64 KB 0 32 + 2: 256 KB 0 32 + 3: 1 MB 0 32 + 4: 4 MB 0 32 + 5: 16 MB 1 32 + 6: 32 MB 1 32 + 7+: 64 MB 1 64 + + The default value for "level" is 5. + + algo = 0 means fast method + algo = 1 means normal method + +dictSize - The dictionary size in bytes. The maximum value is + 128 MB = (1 << 27) bytes for 32-bit version + 1 GB = (1 << 30) bytes for 64-bit version + The default value is 16 MB = (1 << 24) bytes. + It's recommended to use the dictionary that is larger than 4 KB and + that can be calculated as (1 << N) or (3 << N) sizes. + +lc - The number of literal context bits (high bits of previous literal). + It can be in the range from 0 to 8. The default value is 3. + Sometimes lc=4 gives the gain for big files. + +lp - The number of literal pos bits (low bits of current position for literals). + It can be in the range from 0 to 4. The default value is 0. + The lp switch is intended for periodical data when the period is equal to 2^lp. + For example, for 32-bit (4 bytes) periodical data you can use lp=2. Often it's + better to set lc=0, if you change lp switch. + +pb - The number of pos bits (low bits of current position). + It can be in the range from 0 to 4. The default value is 2. + The pb switch is intended for periodical data when the period is equal 2^pb. + +fb - Word size (the number of fast bytes). + It can be in the range from 5 to 273. The default value is 32. + Usually, a big number gives a little bit better compression ratio and + slower compression process. + +numThreads - The number of thereads. 1 or 2. The default value is 2. + Fast mode (algo = 0) can use only 1 thread. + +Out: + destLen - processed output size +Returns: + 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) +*/ + +MY_STDAPI LzmaCompress(unsigned char *dest, size_t *destLen, const unsigned char *src, size_t srcLen, + unsigned char *outProps, size_t *outPropsSize, /* *outPropsSize must be = 5 */ + int level, /* 0 <= level <= 9, default = 5 */ + unsigned dictSize, /* default = (1 << 24) */ + int lc, /* 0 <= lc <= 8, default = 3 */ + int lp, /* 0 <= lp <= 4, default = 0 */ + int pb, /* 0 <= pb <= 4, default = 2 */ + int fb, /* 5 <= fb <= 273, default = 32 */ + int numThreads /* 1 or 2, default = 2 */ + ); + +/* +LzmaUncompress +-------------- +In: + dest - output data + destLen - output data size + src - input data + srcLen - input data size +Out: + destLen - processed output size + srcLen - processed input size +Returns: + SZ_OK - OK + SZ_ERROR_DATA - Data error + SZ_ERROR_MEM - Memory allocation arror + SZ_ERROR_UNSUPPORTED - Unsupported properties + SZ_ERROR_INPUT_EOF - it needs more bytes in input buffer (src) +*/ + +MY_STDAPI LzmaUncompress(unsigned char *dest, size_t *destLen, const unsigned char *src, SizeT *srcLen, + const unsigned char *props, size_t propsSize); + +EXTERN_C_END + +#endif + +/*** End of inlined file: LzmaLib.h ***/ + + #ifdef POCKETLZMA_LZMA_C_DEFINE + +/*** Start of inlined file: Alloc.c ***/ +//#include +// +//#ifdef _WIN32 +//#include +//#endif +//#include + +/* #define _SZ_ALLOC_DEBUG */ + +/* use _SZ_ALLOC_DEBUG to debug alloc/free operations */ +#ifdef _SZ_ALLOC_DEBUG + +#include +int g_allocCount = 0; +int g_allocCountMid = 0; +int g_allocCountBig = 0; + +#define CONVERT_INT_TO_STR(charType, tempSize) \ + unsigned char temp[tempSize]; unsigned i = 0; \ + while (val >= 10) { temp[i++] = (unsigned char)('0' + (unsigned)(val % 10)); val /= 10; } \ + *s++ = (charType)('0' + (unsigned)val); \ + while (i != 0) { i--; *s++ = temp[i]; } \ + *s = 0; + +static void ConvertUInt64ToString(UInt64 val, char *s) +{ + CONVERT_INT_TO_STR(char, 24); +} + +#define GET_HEX_CHAR(t) ((char)(((t < 10) ? ('0' + t) : ('A' + (t - 10))))) + +static void ConvertUInt64ToHex(UInt64 val, char *s) +{ + UInt64 v = val; + unsigned i; + for (i = 1;; i++) + { + v >>= 4; + if (v == 0) + break; + } + s[i] = 0; + do + { + unsigned t = (unsigned)(val & 0xF); + val >>= 4; + s[--i] = GET_HEX_CHAR(t); + } + while (i); +} + +#define DEBUG_OUT_STREAM stderr + +static void Print(const char *s) +{ + fputs(s, DEBUG_OUT_STREAM); +} + +static void PrintAligned(const char *s, size_t align) +{ + size_t len = strlen(s); + for(;;) + { + fputc(' ', DEBUG_OUT_STREAM); + if (len >= align) + break; + ++len; + } + Print(s); +} + +static void PrintLn() +{ + Print("\n"); +} + +static void PrintHex(UInt64 v, size_t align) +{ + char s[32]; + ConvertUInt64ToHex(v, s); + PrintAligned(s, align); +} + +static void PrintDec(UInt64 v, size_t align) +{ + char s[32]; + ConvertUInt64ToString(v, s); + PrintAligned(s, align); +} + +static void PrintAddr(void *p) +{ + PrintHex((UInt64)(size_t)(ptrdiff_t)p, 12); +} + +#define PRINT_ALLOC(name, cnt, size, ptr) \ + Print(name " "); \ + PrintDec(cnt++, 10); \ + PrintHex(size, 10); \ + PrintAddr(ptr); \ + PrintLn(); + +#define PRINT_FREE(name, cnt, ptr) if (ptr) { \ + Print(name " "); \ + PrintDec(--cnt, 10); \ + PrintAddr(ptr); \ + PrintLn(); } + +#else + +#define PRINT_ALLOC(name, cnt, size, ptr) +#define PRINT_FREE(name, cnt, ptr) +#define Print(s) +#define PrintLn() +#define PrintHex(v, align) +#define PrintDec(v, align) +#define PrintAddr(p) + +#endif + +void *MyAlloc(size_t size) +{ + if (size == 0) + return NULL; + #ifdef _SZ_ALLOC_DEBUG + { + void *p = malloc(size); + PRINT_ALLOC("Alloc ", g_allocCount, size, p); + return p; + } + #else + return malloc(size); + #endif +} + +void MyFree(void *address) +{ + PRINT_FREE("Free ", g_allocCount, address); + + free(address); +} + +#ifdef _WIN32 + +void *MidAlloc(size_t size) +{ + if (size == 0) + return NULL; + + PRINT_ALLOC("Alloc-Mid", g_allocCountMid, size, NULL); + + return VirtualAlloc(NULL, size, MEM_COMMIT, PAGE_READWRITE); +} + +void MidFree(void *address) +{ + PRINT_FREE("Free-Mid", g_allocCountMid, address); + + if (!address) + return; + VirtualFree(address, 0, MEM_RELEASE); +} + +#ifndef MEM_LARGE_PAGES +#undef _7ZIP_LARGE_PAGES +#endif + +#ifdef _7ZIP_LARGE_PAGES +SIZE_T g_LargePageSize = 0; +typedef SIZE_T (WINAPI *GetLargePageMinimumP)(); +#endif + +void SetLargePageSize() +{ + #ifdef _7ZIP_LARGE_PAGES + SIZE_T size; + GetLargePageMinimumP largePageMinimum = (GetLargePageMinimumP) + GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")), "GetLargePageMinimum"); + if (!largePageMinimum) + return; + size = largePageMinimum(); + if (size == 0 || (size & (size - 1)) != 0) + return; + g_LargePageSize = size; + #endif +} + +void *BigAlloc(size_t size) +{ + if (size == 0) + return NULL; + + PRINT_ALLOC("Alloc-Big", g_allocCountBig, size, NULL); + + #ifdef _7ZIP_LARGE_PAGES + { + SIZE_T ps = g_LargePageSize; + if (ps != 0 && ps <= (1 << 30) && size > (ps / 2)) + { + size_t size2; + ps--; + size2 = (size + ps) & ~ps; + if (size2 >= size) + { + void *res = VirtualAlloc(NULL, size2, MEM_COMMIT | MEM_LARGE_PAGES, PAGE_READWRITE); + if (res) + return res; + } + } + } + #endif + + return VirtualAlloc(NULL, size, MEM_COMMIT, PAGE_READWRITE); +} + +void BigFree(void *address) +{ + PRINT_FREE("Free-Big", g_allocCountBig, address); + + if (!address) + return; + VirtualFree(address, 0, MEM_RELEASE); +} + +#endif + +static void *SzAlloc(ISzAllocPtr p, size_t size) { UNUSED_VAR(p); return MyAlloc(size); } +static void SzFree(ISzAllocPtr p, void *address) { UNUSED_VAR(p); MyFree(address); } +const ISzAlloc g_Alloc = { SzAlloc, SzFree }; + +static void *SzMidAlloc(ISzAllocPtr p, size_t size) { UNUSED_VAR(p); return MidAlloc(size); } +static void SzMidFree(ISzAllocPtr p, void *address) { UNUSED_VAR(p); MidFree(address); } +const ISzAlloc g_MidAlloc = { SzMidAlloc, SzMidFree }; + +static void *SzBigAlloc(ISzAllocPtr p, size_t size) { UNUSED_VAR(p); return BigAlloc(size); } +static void SzBigFree(ISzAllocPtr p, void *address) { UNUSED_VAR(p); BigFree(address); } +const ISzAlloc g_BigAlloc = { SzBigAlloc, SzBigFree }; + +/* + uintptr_t : C99 (optional) + : unsupported in VS6 +*/ + +#ifdef _WIN32 + typedef UINT_PTR UIntPtr; +#else + /* + typedef uintptr_t UIntPtr; + */ + typedef ptrdiff_t UIntPtr; +#endif + +#define ADJUST_ALLOC_SIZE 0 +/* +#define ADJUST_ALLOC_SIZE (sizeof(void *) - 1) +*/ +/* + Use (ADJUST_ALLOC_SIZE = (sizeof(void *) - 1)), if + MyAlloc() can return address that is NOT multiple of sizeof(void *). +*/ + +/* +#define MY_ALIGN_PTR_DOWN(p, align) ((void *)((char *)(p) - ((size_t)(UIntPtr)(p) & ((align) - 1)))) +*/ +#define MY_ALIGN_PTR_DOWN(p, align) ((void *)((((UIntPtr)(p)) & ~((UIntPtr)(align) - 1)))) + +#define MY_ALIGN_PTR_UP_PLUS(p, align) MY_ALIGN_PTR_DOWN(((char *)(p) + (align) + ADJUST_ALLOC_SIZE), align) + +#if (_POSIX_C_SOURCE >= 200112L) && !defined(_WIN32) + #define USE_posix_memalign +#endif + +/* + This posix_memalign() is for test purposes only. + We also need special Free() function instead of free(), + if this posix_memalign() is used. +*/ + +/* +static int posix_memalign(void **ptr, size_t align, size_t size) +{ + size_t newSize = size + align; + void *p; + void *pAligned; + *ptr = NULL; + if (newSize < size) + return 12; // ENOMEM + p = MyAlloc(newSize); + if (!p) + return 12; // ENOMEM + pAligned = MY_ALIGN_PTR_UP_PLUS(p, align); + ((void **)pAligned)[-1] = p; + *ptr = pAligned; + return 0; +} +*/ + +/* + ALLOC_ALIGN_SIZE >= sizeof(void *) + ALLOC_ALIGN_SIZE >= cache_line_size +*/ + +#define ALLOC_ALIGN_SIZE ((size_t)1 << 7) + +static void *SzAlignedAlloc(ISzAllocPtr pp, size_t size) +{ + #ifndef USE_posix_memalign + + void *p; + void *pAligned; + size_t newSize; + UNUSED_VAR(pp); + + /* also we can allocate additional dummy ALLOC_ALIGN_SIZE bytes after aligned + block to prevent cache line sharing with another allocated blocks */ + + newSize = size + ALLOC_ALIGN_SIZE * 1 + ADJUST_ALLOC_SIZE; + if (newSize < size) + return NULL; + + p = MyAlloc(newSize); + + if (!p) + return NULL; + pAligned = MY_ALIGN_PTR_UP_PLUS(p, ALLOC_ALIGN_SIZE); + + Print(" size="); PrintHex(size, 8); + Print(" a_size="); PrintHex(newSize, 8); + Print(" ptr="); PrintAddr(p); + Print(" a_ptr="); PrintAddr(pAligned); + PrintLn(); + + ((void **)pAligned)[-1] = p; + + return pAligned; + + #else + + void *p; + UNUSED_VAR(pp); + if (posix_memalign(&p, ALLOC_ALIGN_SIZE, size)) + return NULL; + + Print(" posix_memalign="); PrintAddr(p); + PrintLn(); + + return p; + + #endif +} + +static void SzAlignedFree(ISzAllocPtr pp, void *address) +{ + UNUSED_VAR(pp); + #ifndef USE_posix_memalign + if (address) + MyFree(((void **)address)[-1]); + #else + free(address); + #endif +} + +const ISzAlloc g_AlignedAlloc = { SzAlignedAlloc, SzAlignedFree }; + +#define MY_ALIGN_PTR_DOWN_1(p) MY_ALIGN_PTR_DOWN(p, sizeof(void *)) + +/* we align ptr to support cases where CAlignOffsetAlloc::offset is not multiply of sizeof(void *) */ +#define REAL_BLOCK_PTR_VAR(p) ((void **)MY_ALIGN_PTR_DOWN_1(p))[-1] +/* +#define REAL_BLOCK_PTR_VAR(p) ((void **)(p))[-1] +*/ + +static void *AlignOffsetAlloc_Alloc(ISzAllocPtr pp, size_t size) +{ + CAlignOffsetAlloc *p = CONTAINER_FROM_VTBL(pp, CAlignOffsetAlloc, vt); + void *adr; + void *pAligned; + size_t newSize; + size_t extra; + size_t alignSize = (size_t)1 << p->numAlignBits; + + if (alignSize < sizeof(void *)) + alignSize = sizeof(void *); + + if (p->offset >= alignSize) + return NULL; + + /* also we can allocate additional dummy ALLOC_ALIGN_SIZE bytes after aligned + block to prevent cache line sharing with another allocated blocks */ + extra = p->offset & (sizeof(void *) - 1); + newSize = size + alignSize + extra + ADJUST_ALLOC_SIZE; + if (newSize < size) + return NULL; + + adr = ISzAlloc_Alloc(p->baseAlloc, newSize); + + if (!adr) + return NULL; + + pAligned = (char *)MY_ALIGN_PTR_DOWN((char *)adr + + alignSize - p->offset + extra + ADJUST_ALLOC_SIZE, alignSize) + p->offset; + + PrintLn(); + Print("- Aligned: "); + Print(" size="); PrintHex(size, 8); + Print(" a_size="); PrintHex(newSize, 8); + Print(" ptr="); PrintAddr(adr); + Print(" a_ptr="); PrintAddr(pAligned); + PrintLn(); + + REAL_BLOCK_PTR_VAR(pAligned) = adr; + + return pAligned; +} + +static void AlignOffsetAlloc_Free(ISzAllocPtr pp, void *address) +{ + if (address) + { + CAlignOffsetAlloc *p = CONTAINER_FROM_VTBL(pp, CAlignOffsetAlloc, vt); + PrintLn(); + Print("- Aligned Free: "); + PrintLn(); + ISzAlloc_Free(p->baseAlloc, REAL_BLOCK_PTR_VAR(address)); + } +} + +void AlignOffsetAlloc_CreateVTable(CAlignOffsetAlloc *p) +{ + p->vt.Alloc = AlignOffsetAlloc_Alloc; + p->vt.Free = AlignOffsetAlloc_Free; +} + +/*** End of inlined file: Alloc.c ***/ + + + +/*** Start of inlined file: LzmaDec.c ***/ +//#include + +/* #include "CpuArch.h" */ + +#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) * (UInt32)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 TREE_GET_BIT(probs, i) { GET_BIT2(probs + i, i, ;, ;); } + +#define REV_BIT(p, i, A0, A1) IF_BIT_0(p + i) \ + { UPDATE_0(p + i); A0; } else \ + { UPDATE_1(p + i); A1; } +#define REV_BIT_VAR( p, i, m) REV_BIT(p, i, i += m; m += m, m += m; i += m; ) +#define REV_BIT_CONST(p, i, m) REV_BIT(p, i, i += m; , i += m * 2; ) +#define REV_BIT_LAST( p, i, m) REV_BIT(p, i, i -= m , ; ) + +#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 NORMAL_LITER_DEC TREE_GET_BIT(prob, symbol) +#define MATCHED_LITER_DEC \ + matchByte += matchByte; \ + bit = offs; \ + offs &= matchByte; \ + probLit = prob + (offs + bit + symbol); \ + GET_BIT2(probLit, symbol, offs ^= bit; , ;) + +#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) * (UInt32)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 REV_BIT_CHECK(p, i, m) IF_BIT_0_CHECK(p + i) \ + { UPDATE_0_CHECK; i += m; m += m; } else \ + { UPDATE_1_CHECK; m += m; i += m; } + +#define kNumPosBitsMax 4 +#define kNumPosStatesMax (1 << kNumPosBitsMax) + +#define kLenNumLowBits 3 +#define kLenNumLowSymbols (1 << kLenNumLowBits) +#define kLenNumHighBits 8 +#define kLenNumHighSymbols (1 << kLenNumHighBits) + +#define LenLow 0 +#define LenHigh (LenLow + 2 * (kNumPosStatesMax << kLenNumLowBits)) +#define kNumLenProbs (LenHigh + kLenNumHighSymbols) + +#define LenChoice LenLow +#define LenChoice2 (LenLow + (1 << kLenNumLowBits)) + +#define kNumStates 12 +#define kNumStates2 16 +#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 * 2 + kLenNumHighSymbols) + +/* External ASM code needs same CLzmaProb array layout. So don't change it. */ + +/* (probs_1664) is faster and better for code size at some platforms */ +/* +#ifdef MY_CPU_X86_OR_AMD64 +*/ +#define kStartOffset 1664 +#define GET_PROBS p->probs_1664 +/* +#define GET_PROBS p->probs + kStartOffset +#else +#define kStartOffset 0 +#define GET_PROBS p->probs +#endif +*/ + +#define SpecPos (-kStartOffset) +#define IsRep0Long (SpecPos + kNumFullDistances) +#define RepLenCoder (IsRep0Long + (kNumStates2 << kNumPosBitsMax)) +#define LenCoder (RepLenCoder + kNumLenProbs) +#define IsMatch (LenCoder + kNumLenProbs) +#define Align (IsMatch + (kNumStates2 << kNumPosBitsMax)) +#define IsRep (Align + kAlignTableSize) +#define IsRepG0 (IsRep + kNumStates) +#define IsRepG1 (IsRepG0 + kNumStates) +#define IsRepG2 (IsRepG1 + kNumStates) +#define PosSlot (IsRepG2 + kNumStates) +#define Literal (PosSlot + (kNumLenToPosStates << kNumPosSlotBits)) +#define NUM_BASE_PROBS (Literal + kStartOffset) + +#if Align != 0 && kStartOffset != 0 + #error Stop_Compiling_Bad_LZMA_kAlign +#endif + +#if NUM_BASE_PROBS != 1984 + #error Stop_Compiling_Bad_LZMA_PROBS +#endif + +#define LZMA_LIT_SIZE 0x300 + +#define LzmaProps_GetNumProbs(p) (NUM_BASE_PROBS + ((UInt32)LZMA_LIT_SIZE << ((p)->lc + (p)->lp))) + +#define CALC_POS_STATE(processedPos, pbMask) (((processedPos) & (pbMask)) << 4) +#define COMBINED_PS_STATE (posState + state) +#define GET_LEN_STATE (posState) + +#define LZMA_DIC_MIN (1 << 12) + +/* +p->remainLen : shows status of LZMA decoder: + < kMatchSpecLenStart : normal remain + = kMatchSpecLenStart : finished + = kMatchSpecLenStart + 1 : need init range coder + = kMatchSpecLenStart + 2 : need init range coder and state +*/ + +/* ---------- LZMA_DECODE_REAL ---------- */ +/* +LzmaDec_DecodeReal_3() can be implemented in external ASM file. +3 - is the code compatibility version of that function for check at link time. +*/ + +#define LZMA_DECODE_REAL LzmaDec_DecodeReal_3 + +/* +LZMA_DECODE_REAL() +In: + RangeCoder is normalized + if (p->dicPos == limit) + { + LzmaDec_TryDummy() was called before to exclude LITERAL and MATCH-REP cases. + So first symbol can be only MATCH-NON-REP. And if that MATCH-NON-REP symbol + is not END_OF_PAYALOAD_MARKER, then function returns error code. + } + +Processing: + first LZMA symbol will be decoded in any case + All checks for limits are at the end of main loop, + It will decode new LZMA-symbols while (p->buf < bufLimit && dicPos < limit), + RangeCoder is still without last normalization when (p->buf < bufLimit) is being checked. + +Out: + RangeCoder is normalized + Result: + SZ_OK - OK + SZ_ERROR_DATA - Error + p->remainLen: + < kMatchSpecLenStart : normal remain + = kMatchSpecLenStart : finished +*/ + +#ifdef _LZMA_DEC_OPT + +int MY_FAST_CALL LZMA_DECODE_REAL(CLzmaDec *p, SizeT limit, const Byte *bufLimit); + +#else + +static +int MY_FAST_CALL LZMA_DECODE_REAL(CLzmaDec *p, SizeT limit, const Byte *bufLimit) +{ + CLzmaProb *probs = GET_PROBS; + unsigned state = (unsigned)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 lc = p->prop.lc; + unsigned lpMask = ((unsigned)0x100 << p->prop.lp) - ((unsigned)0x100 >> 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 = CALC_POS_STATE(processedPos, pbMask); + + prob = probs + IsMatch + COMBINED_PS_STATE; + IF_BIT_0(prob) + { + unsigned symbol; + UPDATE_0(prob); + prob = probs + Literal; + if (processedPos != 0 || checkDicSize != 0) + prob += (UInt32)3 * ((((processedPos << 8) + dic[(dicPos == 0 ? dicBufSize : dicPos) - 1]) & lpMask) << lc); + processedPos++; + + if (state < kNumLitStates) + { + state -= (state < 4) ? state : 3; + symbol = 1; + #ifdef _LZMA_SIZE_OPT + do { NORMAL_LITER_DEC } while (symbol < 0x100); + #else + NORMAL_LITER_DEC + NORMAL_LITER_DEC + NORMAL_LITER_DEC + NORMAL_LITER_DEC + NORMAL_LITER_DEC + NORMAL_LITER_DEC + NORMAL_LITER_DEC + NORMAL_LITER_DEC + #endif + } + else + { + unsigned matchByte = dic[dicPos - rep0 + (dicPos < rep0 ? dicBufSize : 0)]; + unsigned offs = 0x100; + state -= (state < 10) ? 3 : 6; + symbol = 1; + #ifdef _LZMA_SIZE_OPT + do + { + unsigned bit; + CLzmaProb *probLit; + MATCHED_LITER_DEC + } + while (symbol < 0x100); + #else + { + unsigned bit; + CLzmaProb *probLit; + MATCHED_LITER_DEC + MATCHED_LITER_DEC + MATCHED_LITER_DEC + MATCHED_LITER_DEC + MATCHED_LITER_DEC + MATCHED_LITER_DEC + MATCHED_LITER_DEC + MATCHED_LITER_DEC + } + #endif + } + + dic[dicPos++] = (Byte)symbol; + continue; + } + + { + UPDATE_1(prob); + prob = probs + IsRep + state; + IF_BIT_0(prob) + { + UPDATE_0(prob); + state += kNumStates; + prob = probs + LenCoder; + } + else + { + UPDATE_1(prob); + /* + // that case was checked before with kBadRepCode + if (checkDicSize == 0 && processedPos == 0) + return SZ_ERROR_DATA; + */ + prob = probs + IsRepG0 + state; + IF_BIT_0(prob) + { + UPDATE_0(prob); + prob = probs + IsRep0Long + COMBINED_PS_STATE; + 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; + } + + #ifdef _LZMA_SIZE_OPT + { + unsigned lim, offset; + CLzmaProb *probLen = prob + LenChoice; + IF_BIT_0(probLen) + { + UPDATE_0(probLen); + probLen = prob + LenLow + GET_LEN_STATE; + offset = 0; + lim = (1 << kLenNumLowBits); + } + else + { + UPDATE_1(probLen); + probLen = prob + LenChoice2; + IF_BIT_0(probLen) + { + UPDATE_0(probLen); + probLen = prob + LenLow + GET_LEN_STATE + (1 << kLenNumLowBits); + offset = kLenNumLowSymbols; + lim = (1 << kLenNumLowBits); + } + else + { + UPDATE_1(probLen); + probLen = prob + LenHigh; + offset = kLenNumLowSymbols * 2; + lim = (1 << kLenNumHighBits); + } + } + TREE_DECODE(probLen, lim, len); + len += offset; + } + #else + { + CLzmaProb *probLen = prob + LenChoice; + IF_BIT_0(probLen) + { + UPDATE_0(probLen); + probLen = prob + LenLow + GET_LEN_STATE; + len = 1; + TREE_GET_BIT(probLen, len); + TREE_GET_BIT(probLen, len); + TREE_GET_BIT(probLen, len); + len -= 8; + } + else + { + UPDATE_1(probLen); + probLen = prob + LenChoice2; + IF_BIT_0(probLen) + { + UPDATE_0(probLen); + probLen = prob + LenLow + GET_LEN_STATE + (1 << kLenNumLowBits); + len = 1; + TREE_GET_BIT(probLen, len); + TREE_GET_BIT(probLen, len); + TREE_GET_BIT(probLen, len); + } + else + { + UPDATE_1(probLen); + probLen = prob + LenHigh; + TREE_DECODE(probLen, (1 << kLenNumHighBits), len); + len += kLenNumLowSymbols * 2; + } + } + } + #endif + + 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; + unsigned numDirectBits = (unsigned)(((distance >> 1) - 1)); + distance = (2 | (distance & 1)); + if (posSlot < kEndPosModelIndex) + { + distance <<= numDirectBits; + prob = probs + SpecPos; + { + UInt32 m = 1; + distance++; + do + { + REV_BIT_VAR(prob, distance, m); + } + while (--numDirectBits); + distance -= m; + } + } + 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); + prob = probs + Align; + distance <<= kNumAlignBits; + { + unsigned i = 1; + REV_BIT_CONST(prob, i, 1); + REV_BIT_CONST(prob, i, 2); + REV_BIT_CONST(prob, i, 4); + REV_BIT_LAST (prob, i, 8); + distance |= i; + } + if (distance == (UInt32)0xFFFFFFFF) + { + len = kMatchSpecLenStart; + state -= kNumStates; + break; + } + } + } + + rep3 = rep2; + rep2 = rep1; + rep1 = rep0; + rep0 = distance + 1; + state = (state < kNumStates + kNumLitStates) ? kNumLitStates : kNumLitStates + 3; + if (distance >= (checkDicSize == 0 ? processedPos: checkDicSize)) + { + p->dicPos = dicPos; + return SZ_ERROR_DATA; + } + } + + len += kMatchMinLen; + + { + SizeT rem; + unsigned curLen; + SizeT pos; + + if ((rem = limit - dicPos) == 0) + { + p->dicPos = dicPos; + return SZ_ERROR_DATA; + } + + curLen = ((rem < len) ? (unsigned)rem : len); + pos = dicPos - rep0 + (dicPos < rep0 ? dicBufSize : 0); + + processedPos += (UInt32)curLen; + + len -= curLen; + if (curLen <= dicBufSize - pos) + { + Byte *dest = dic + dicPos; + ptrdiff_t src = (ptrdiff_t)pos - (ptrdiff_t)dicPos; + const Byte *lim = dest + curLen; + dicPos += (SizeT)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 = (UInt32)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 = (UInt32)state; + + return SZ_OK; +} +#endif + +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 = (unsigned)p->remainLen; + SizeT rep0 = p->reps[0]; /* we use SizeT to avoid the BUG of VC14 for AMD64 */ + SizeT rem = limit - dicPos; + if (rem < len) + len = (unsigned)(rem); + + if (p->checkDicSize == 0 && p->prop.dicSize - p->processedPos <= len) + p->checkDicSize = p->prop.dicSize; + + p->processedPos += (UInt32)len; + p->remainLen -= (UInt32)len; + while (len != 0) + { + len--; + dic[dicPos] = dic[dicPos - rep0 + (dicPos < rep0 ? dicBufSize : 0)]; + dicPos++; + } + p->dicPos = dicPos; + } +} + +#define kRange0 0xFFFFFFFF +#define kBound0 ((kRange0 >> kNumBitModelTotalBits) << (kNumBitModelTotalBits - 1)) +#define kBadRepCode (kBound0 + (((kRange0 - kBound0) >> kNumBitModelTotalBits) << (kNumBitModelTotalBits - 1))) +#if kBadRepCode != (0xC0000000 - 0x400) + #error Stop_Compiling_Bad_LZMA_Check +#endif + +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; + + if (p->processedPos == 0) + if (p->code >= kBadRepCode) + return SZ_ERROR_DATA; + } + + RINOK(LZMA_DECODE_REAL(p, limit2, bufLimit)); + + if (p->checkDicSize == 0 && p->processedPos >= p->prop.dicSize) + p->checkDicSize = p->prop.dicSize; + + LzmaDec_WriteRem(p, limit); + } + while (p->dicPos < limit && p->buf < bufLimit && 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; + const CLzmaProb *probs = GET_PROBS; + unsigned state = (unsigned)p->state; + ELzmaDummy res; + + { + const CLzmaProb *prob; + UInt32 bound; + unsigned ttt; + unsigned posState = CALC_POS_STATE(p->processedPos, (1 << p->prop.pb) - 1); + + prob = probs + IsMatch + COMBINED_PS_STATE; + 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 += ((UInt32)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; + const CLzmaProb *probLit; + matchByte += matchByte; + bit = offs; + offs &= matchByte; + probLit = prob + (offs + bit + symbol); + GET_BIT2_CHECK(probLit, symbol, 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 + COMBINED_PS_STATE; + 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; + const CLzmaProb *probLen = prob + LenChoice; + IF_BIT_0_CHECK(probLen) + { + UPDATE_0_CHECK; + probLen = prob + LenLow + GET_LEN_STATE; + offset = 0; + limit = 1 << kLenNumLowBits; + } + else + { + UPDATE_1_CHECK; + probLen = prob + LenChoice2; + IF_BIT_0_CHECK(probLen) + { + UPDATE_0_CHECK; + probLen = prob + LenLow + GET_LEN_STATE + (1 << kLenNumLowBits); + offset = kLenNumLowSymbols; + limit = 1 << kLenNumLowBits; + } + else + { + UPDATE_1_CHECK; + probLen = prob + LenHigh; + offset = kLenNumLowSymbols * 2; + limit = 1 << kLenNumHighBits; + } + } + TREE_DECODE_CHECK(probLen, limit, len); + len += offset; + } + + if (state < 4) + { + unsigned posSlot; + prob = probs + PosSlot + + ((len < kNumLenToPosStates - 1 ? len : kNumLenToPosStates - 1) << + kNumPosSlotBits); + TREE_DECODE_CHECK(prob, 1 << kNumPosSlotBits, posSlot); + if (posSlot >= kStartPosModelIndex) + { + unsigned numDirectBits = ((posSlot >> 1) - 1); + + /* if (bufLimit - buf >= 8) return DUMMY_MATCH; */ + + if (posSlot < kEndPosModelIndex) + { + prob = probs + SpecPos + ((2 | (posSlot & 1)) << numDirectBits); + } + else + { + numDirectBits -= kNumAlignBits; + do + { + NORMALIZE_CHECK + range >>= 1; + code -= range & (((code - range) >> 31) - 1); + /* if (code >= range) code -= range; */ + } + while (--numDirectBits); + prob = probs + Align; + numDirectBits = kNumAlignBits; + } + { + unsigned i = 1; + unsigned m = 1; + do + { + REV_BIT_CHECK(prob, i, m); + } + while (--numDirectBits); + } + } + } + } + } + NORMALIZE_CHECK; + return res; +} + +void LzmaDec_InitDicAndState(CLzmaDec *p, BoolInt initDic, BoolInt initState) +{ + p->remainLen = kMatchSpecLenStart + 1; + p->tempBufSize = 0; + + if (initDic) + { + p->processedPos = 0; + p->checkDicSize = 0; + p->remainLen = kMatchSpecLenStart + 2; + } + if (initState) + p->remainLen = kMatchSpecLenStart + 2; +} + +void LzmaDec_Init(CLzmaDec *p) +{ + p->dicPos = 0; + LzmaDec_InitDicAndState(p, True, True); +} + +SRes LzmaDec_DecodeToDic(CLzmaDec *p, SizeT dicLimit, const Byte *src, SizeT *srcLen, + ELzmaFinishMode finishMode, ELzmaStatus *status) +{ + SizeT inSize = *srcLen; + (*srcLen) = 0; + + *status = LZMA_STATUS_NOT_SPECIFIED; + + if (p->remainLen > kMatchSpecLenStart) + { + for (; inSize > 0 && p->tempBufSize < RC_INIT_SIZE; (*srcLen)++, inSize--) + p->tempBuf[p->tempBufSize++] = *src++; + if (p->tempBufSize != 0 && p->tempBuf[0] != 0) + return SZ_ERROR_DATA; + if (p->tempBufSize < RC_INIT_SIZE) + { + *status = LZMA_STATUS_NEEDS_MORE_INPUT; + return SZ_OK; + } + p->code = + ((UInt32)p->tempBuf[1] << 24) + | ((UInt32)p->tempBuf[2] << 16) + | ((UInt32)p->tempBuf[3] << 8) + | ((UInt32)p->tempBuf[4]); + p->range = 0xFFFFFFFF; + p->tempBufSize = 0; + + if (p->remainLen > kMatchSpecLenStart + 1) + { + SizeT numProbs = LzmaProps_GetNumProbs(&p->prop); + SizeT 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->remainLen = 0; + } + + LzmaDec_WriteRem(p, dicLimit); + + while (p->remainLen != kMatchSpecLenStart) + { + int 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->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, (SizeT)rem); + if (dummyRes == DUMMY_ERROR) + { + (*srcLen) += (SizeT)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; + + { + unsigned kkk = (unsigned)(p->buf - p->tempBuf); + if (rem < kkk) + return SZ_ERROR_FAIL; /* some internal error */ + rem -= kkk; + if (lookAhead < rem) + return SZ_ERROR_FAIL; /* some internal error */ + lookAhead -= rem; + } + (*srcLen) += (SizeT)lookAhead; + src += lookAhead; + inSize -= (SizeT)lookAhead; + p->tempBufSize = 0; + } + } + + if (p->code != 0) + return SZ_ERROR_DATA; + *status = LZMA_STATUS_FINISHED_WITH_MARK; + return SZ_OK; +} + +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; + } +} + +void LzmaDec_FreeProbs(CLzmaDec *p, ISzAllocPtr alloc) +{ + ISzAlloc_Free(alloc, p->probs); + p->probs = NULL; +} + +static void LzmaDec_FreeDict(CLzmaDec *p, ISzAllocPtr alloc) +{ + ISzAlloc_Free(alloc, p->dic); + p->dic = NULL; +} + +void LzmaDec_Free(CLzmaDec *p, ISzAllocPtr 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 = (Byte)(d % 9); + d /= 9; + p->pb = (Byte)(d / 5); + p->lp = (Byte)(d % 5); + + return SZ_OK; +} + +static SRes LzmaDec_AllocateProbs2(CLzmaDec *p, const CLzmaProps *propNew, ISzAllocPtr alloc) +{ + UInt32 numProbs = LzmaProps_GetNumProbs(propNew); + if (!p->probs || numProbs != p->numProbs) + { + LzmaDec_FreeProbs(p, alloc); + p->probs = (CLzmaProb *)ISzAlloc_Alloc(alloc, numProbs * sizeof(CLzmaProb)); + if (!p->probs) + return SZ_ERROR_MEM; + p->probs_1664 = p->probs + 1664; + p->numProbs = numProbs; + } + return SZ_OK; +} + +SRes LzmaDec_AllocateProbs(CLzmaDec *p, const Byte *props, unsigned propsSize, ISzAllocPtr 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, ISzAllocPtr alloc) +{ + CLzmaProps propNew; + SizeT dicBufSize; + RINOK(LzmaProps_Decode(&propNew, props, propsSize)); + RINOK(LzmaDec_AllocateProbs2(p, &propNew, alloc)); + + { + UInt32 dictSize = propNew.dicSize; + SizeT mask = ((UInt32)1 << 12) - 1; + if (dictSize >= ((UInt32)1 << 30)) mask = ((UInt32)1 << 22) - 1; + else if (dictSize >= ((UInt32)1 << 22)) mask = ((UInt32)1 << 20) - 1;; + dicBufSize = ((SizeT)dictSize + mask) & ~mask; + if (dicBufSize < dictSize) + dicBufSize = dictSize; + } + + if (!p->dic || dicBufSize != p->dicBufSize) + { + LzmaDec_FreeDict(p, alloc); + p->dic = (Byte *)ISzAlloc_Alloc(alloc, dicBufSize); + if (!p->dic) + { + 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, ISzAllocPtr alloc) +{ + CLzmaDec p; + SRes res; + SizeT outSize = *destLen, inSize = *srcLen; + *destLen = *srcLen = 0; + *status = LZMA_STATUS_NOT_SPECIFIED; + if (inSize < RC_INIT_SIZE) + return SZ_ERROR_INPUT_EOF; + LzmaDec_Construct(&p); + RINOK(LzmaDec_AllocateProbs(&p, propData, propSize, alloc)); + p.dic = dest; + p.dicBufSize = outSize; + LzmaDec_Init(&p); + *srcLen = inSize; + res = LzmaDec_DecodeToDic(&p, outSize, src, srcLen, finishMode, status); + *destLen = p.dicPos; + if (res == SZ_OK && *status == LZMA_STATUS_NEEDS_MORE_INPUT) + res = SZ_ERROR_INPUT_EOF; + LzmaDec_FreeProbs(&p, alloc); + return res; +} + +/*** End of inlined file: LzmaDec.c ***/ + + +/*** Start of inlined file: LzmaEnc.c ***/ +//#include + +/* #define SHOW_STAT */ +/* #define SHOW_STAT2 */ + +//#if defined(SHOW_STAT) || defined(SHOW_STAT2) +//#include +//#endif + + +/*** Start of inlined file: LzFind.h ***/ +#ifndef __LZ_FIND_H +#define __LZ_FIND_H + +EXTERN_C_BEGIN + +typedef UInt32 CLzRef; + +typedef struct _CMatchFinder +{ + Byte *buffer; + UInt32 pos; + UInt32 posLimit; + UInt32 streamPos; + UInt32 lenLimit; + + UInt32 cyclicBufferPos; + UInt32 cyclicBufferSize; /* it must be = (historySize + 1) */ + + Byte streamEndWasReached; + Byte btMode; + Byte bigHash; + Byte directInput; + + UInt32 matchMaxLen; + CLzRef *hash; + CLzRef *son; + UInt32 hashMask; + UInt32 cutValue; + + Byte *bufferBase; + ISeqInStream *stream; + + UInt32 blockSize; + UInt32 keepSizeBefore; + UInt32 keepSizeAfter; + + UInt32 numHashBytes; + size_t directInputRem; + UInt32 historySize; + UInt32 fixedHashSize; + UInt32 hashSizeSum; + SRes result; + UInt32 crc[256]; + size_t numRefs; + + UInt64 expectedDataSize; +} CMatchFinder; + +#define Inline_MatchFinder_GetPointerToCurrentPos(p) ((p)->buffer) + +#define Inline_MatchFinder_GetNumAvailableBytes(p) ((p)->streamPos - (p)->pos) + +#define Inline_MatchFinder_IsFinishedOK(p) \ + ((p)->streamEndWasReached \ + && (p)->streamPos == (p)->pos \ + && (!(p)->directInput || (p)->directInputRem == 0)) + +int MatchFinder_NeedMove(CMatchFinder *p); +Byte *MatchFinder_GetPointerToCurrentPos(CMatchFinder *p); +void MatchFinder_MoveBlock(CMatchFinder *p); +void MatchFinder_ReadIfRequired(CMatchFinder *p); + +void MatchFinder_Construct(CMatchFinder *p); + +/* Conditions: + historySize <= 3 GB + keepAddBufferBefore + matchMaxLen + keepAddBufferAfter < 511MB +*/ +int MatchFinder_Create(CMatchFinder *p, UInt32 historySize, + UInt32 keepAddBufferBefore, UInt32 matchMaxLen, UInt32 keepAddBufferAfter, + ISzAllocPtr alloc); +void MatchFinder_Free(CMatchFinder *p, ISzAllocPtr alloc); +void MatchFinder_Normalize3(UInt32 subValue, CLzRef *items, size_t numItems); +void MatchFinder_ReduceOffsets(CMatchFinder *p, UInt32 subValue); + +UInt32 * GetMatchesSpec1(UInt32 lenLimit, UInt32 curMatch, UInt32 pos, const Byte *buffer, CLzRef *son, + UInt32 _cyclicBufferPos, UInt32 _cyclicBufferSize, UInt32 _cutValue, + UInt32 *distances, UInt32 maxLen); + +/* +Conditions: + Mf_GetNumAvailableBytes_Func must be called before each Mf_GetMatchLen_Func. + Mf_GetPointerToCurrentPos_Func's result must be used only before any other function +*/ + +typedef void (*Mf_Init_Func)(void *object); +typedef UInt32 (*Mf_GetNumAvailableBytes_Func)(void *object); +typedef const Byte * (*Mf_GetPointerToCurrentPos_Func)(void *object); +typedef UInt32 (*Mf_GetMatches_Func)(void *object, UInt32 *distances); +typedef void (*Mf_Skip_Func)(void *object, UInt32); + +typedef struct _IMatchFinder +{ + Mf_Init_Func Init; + Mf_GetNumAvailableBytes_Func GetNumAvailableBytes; + Mf_GetPointerToCurrentPos_Func GetPointerToCurrentPos; + Mf_GetMatches_Func GetMatches; + Mf_Skip_Func Skip; +} IMatchFinder; + +void MatchFinder_CreateVTable(CMatchFinder *p, IMatchFinder *vTable); + +void MatchFinder_Init_LowHash(CMatchFinder *p); +void MatchFinder_Init_HighHash(CMatchFinder *p); +void MatchFinder_Init_3(CMatchFinder *p, int readData); +void MatchFinder_Init(CMatchFinder *p); + +UInt32 Bt3Zip_MatchFinder_GetMatches(CMatchFinder *p, UInt32 *distances); +UInt32 Hc3Zip_MatchFinder_GetMatches(CMatchFinder *p, UInt32 *distances); + +void Bt3Zip_MatchFinder_Skip(CMatchFinder *p, UInt32 num); +void Hc3Zip_MatchFinder_Skip(CMatchFinder *p, UInt32 num); + +EXTERN_C_END + +#endif + +/*** End of inlined file: LzFind.h ***/ + +#ifdef SHOW_STAT +static unsigned g_STAT_OFFSET = 0; +#endif + +#define kLzmaMaxHistorySize ((UInt32)3 << 29) +/* #define kLzmaMaxHistorySize ((UInt32)7 << 29) */ + +#define kNumTopBits 24 +#define kTopValue ((UInt32)1 << kNumTopBits) + +#define kNumBitModelTotalBits 11 +#define kBitModelTotal (1 << kNumBitModelTotalBits) +#define kNumMoveBits 5 +#define kProbInitValue (kBitModelTotal >> 1) + +#define kNumMoveReducingBits 4 +#define kNumBitPriceShiftBits 4 +#define kBitPrice (1 << kNumBitPriceShiftBits) + +#define REP_LEN_COUNT 64 + +void LzmaEncProps_Init(CLzmaEncProps *p) +{ + p->level = 5; + p->dictSize = p->mc = 0; + p->reduceSize = (UInt64)(Int64)-1; + p->lc = p->lp = p->pb = p->algo = p->fb = p->btMode = p->numHashBytes = p->numThreads = -1; + p->writeEndMark = 0; +} + +void LzmaEncProps_Normalize(CLzmaEncProps *p) +{ + int level = p->level; + if (level < 0) level = 5; + p->level = level; + + if (p->dictSize == 0) p->dictSize = (level <= 5 ? (1 << (level * 2 + 14)) : (level <= 7 ? (1 << 25) : (1 << 26))); + if (p->dictSize > p->reduceSize) + { + unsigned i; + UInt32 reduceSize = (UInt32)p->reduceSize; + for (i = 11; i <= 30; i++) + { + if (reduceSize <= ((UInt32)2 << i)) { p->dictSize = ((UInt32)2 << i); break; } + if (reduceSize <= ((UInt32)3 << i)) { p->dictSize = ((UInt32)3 << i); break; } + } + } + + if (p->lc < 0) p->lc = 3; + if (p->lp < 0) p->lp = 0; + if (p->pb < 0) p->pb = 2; + + if (p->algo < 0) p->algo = (level < 5 ? 0 : 1); + if (p->fb < 0) p->fb = (level < 7 ? 32 : 64); + if (p->btMode < 0) p->btMode = (p->algo == 0 ? 0 : 1); + if (p->numHashBytes < 0) p->numHashBytes = 4; + if (p->mc == 0) p->mc = (16 + (p->fb >> 1)) >> (p->btMode ? 0 : 1); + + if (p->numThreads < 0) + p->numThreads = 1; + +} + +UInt32 LzmaEncProps_GetDictSize(const CLzmaEncProps *props2) +{ + CLzmaEncProps props = *props2; + LzmaEncProps_Normalize(&props); + return props.dictSize; +} + +#if (_MSC_VER >= 1400) +/* BSR code is fast for some new CPUs */ +/* #define LZMA_LOG_BSR */ +#endif + +#ifdef LZMA_LOG_BSR + +#define kDicLogSizeMaxCompress 32 + +#define BSR2_RET(pos, res) { unsigned long zz; _BitScanReverse(&zz, (pos)); res = (zz + zz) + ((pos >> (zz - 1)) & 1); } + +static unsigned GetPosSlot1(UInt32 pos) +{ + unsigned res; + BSR2_RET(pos, res); + return res; +} +#define GetPosSlot2(pos, res) { BSR2_RET(pos, res); } +#define GetPosSlot(pos, res) { if (pos < 2) res = pos; else BSR2_RET(pos, res); } + +#else + +#define kNumLogBits (9 + sizeof(size_t) / 2) +/* #define kNumLogBits (11 + sizeof(size_t) / 8 * 3) */ + +#define kDicLogSizeMaxCompress ((kNumLogBits - 1) * 2 + 7) + +static void LzmaEnc_FastPosInit(Byte *g_FastPos) +{ + unsigned slot; + g_FastPos[0] = 0; + g_FastPos[1] = 1; + g_FastPos += 2; + + for (slot = 2; slot < kNumLogBits * 2; slot++) + { + size_t k = ((size_t)1 << ((slot >> 1) - 1)); + size_t j; + for (j = 0; j < k; j++) + g_FastPos[j] = (Byte)slot; + g_FastPos += k; + } +} + +/* we can use ((limit - pos) >> 31) only if (pos < ((UInt32)1 << 31)) */ +/* +#define BSR2_RET(pos, res) { unsigned zz = 6 + ((kNumLogBits - 1) & \ + (0 - (((((UInt32)1 << (kNumLogBits + 6)) - 1) - pos) >> 31))); \ + res = p->g_FastPos[pos >> zz] + (zz * 2); } +*/ + +/* +#define BSR2_RET(pos, res) { unsigned zz = 6 + ((kNumLogBits - 1) & \ + (0 - (((((UInt32)1 << (kNumLogBits)) - 1) - (pos >> 6)) >> 31))); \ + res = p->g_FastPos[pos >> zz] + (zz * 2); } +*/ + +#define BSR2_RET(pos, res) { unsigned zz = (pos < (1 << (kNumLogBits + 6))) ? 6 : 6 + kNumLogBits - 1; \ + res = p->g_FastPos[pos >> zz] + (zz * 2); } + +/* +#define BSR2_RET(pos, res) { res = (pos < (1 << (kNumLogBits + 6))) ? \ + p->g_FastPos[pos >> 6] + 12 : \ + p->g_FastPos[pos >> (6 + kNumLogBits - 1)] + (6 + (kNumLogBits - 1)) * 2; } +*/ + +#define GetPosSlot1(pos) p->g_FastPos[pos] +#define GetPosSlot2(pos, res) { BSR2_RET(pos, res); } +#define GetPosSlot(pos, res) { if (pos < kNumFullDistances) res = p->g_FastPos[pos & (kNumFullDistances - 1)]; else BSR2_RET(pos, res); } + +#endif + +#define LZMA_NUM_REPS 4 + +typedef UInt16 CState; +typedef UInt16 CExtra; + +typedef struct +{ + UInt32 price; + CState state; + CExtra extra; + // 0 : normal + // 1 : LIT : MATCH + // > 1 : MATCH (extra-1) : LIT : REP0 (len) + UInt32 len; + UInt32 dist; + UInt32 reps[LZMA_NUM_REPS]; +} COptimal; + +// 18.06 +#define kNumOpts (1 << 11) +#define kPackReserve (kNumOpts * 8) +// #define kNumOpts (1 << 12) +// #define kPackReserve (1 + kNumOpts * 2) + +#define kNumLenToPosStates 4 +#define kNumPosSlotBits 6 +#define kDicLogSizeMin 0 +#define kDicLogSizeMax 32 +#define kDistTableSizeMax (kDicLogSizeMax * 2) + +#define kNumAlignBits 4 +#define kAlignTableSize (1 << kNumAlignBits) +#define kAlignMask (kAlignTableSize - 1) + +#define kStartPosModelIndex 4 +#define kEndPosModelIndex 14 +#define kNumFullDistances (1 << (kEndPosModelIndex >> 1)) + +typedef +#ifdef _LZMA_PROB32 + UInt32 +#else + UInt16 +#endif + CLzmaProb; + +#define LZMA_PB_MAX 4 +#define LZMA_LC_MAX 8 +#define LZMA_LP_MAX 4 + +#define LZMA_NUM_PB_STATES_MAX (1 << LZMA_PB_MAX) + +#define kLenNumLowBits 3 +#define kLenNumLowSymbols (1 << kLenNumLowBits) +#define kLenNumHighBits 8 +#define kLenNumHighSymbols (1 << kLenNumHighBits) +#define kLenNumSymbolsTotal (kLenNumLowSymbols * 2 + kLenNumHighSymbols) + +#define LZMA_MATCH_LEN_MIN 2 +#define LZMA_MATCH_LEN_MAX (LZMA_MATCH_LEN_MIN + kLenNumSymbolsTotal - 1) + +#define kNumStates 12 + +typedef struct +{ + CLzmaProb low[LZMA_NUM_PB_STATES_MAX << (kLenNumLowBits + 1)]; + CLzmaProb high[kLenNumHighSymbols]; +} CLenEnc; + +typedef struct +{ + unsigned tableSize; + UInt32 prices[LZMA_NUM_PB_STATES_MAX][kLenNumSymbolsTotal]; + // UInt32 prices1[LZMA_NUM_PB_STATES_MAX][kLenNumLowSymbols * 2]; + // UInt32 prices2[kLenNumSymbolsTotal]; +} CLenPriceEnc; + +#define GET_PRICE_LEN(p, posState, len) \ + ((p)->prices[posState][(size_t)(len) - LZMA_MATCH_LEN_MIN]) + +/* +#define GET_PRICE_LEN(p, posState, len) \ + ((p)->prices2[(size_t)(len) - 2] + ((p)->prices1[posState][((len) - 2) & (kLenNumLowSymbols * 2 - 1)] & (((len) - 2 - kLenNumLowSymbols * 2) >> 9))) +*/ + +typedef struct +{ + UInt32 range; + unsigned cache; + UInt64 low; + UInt64 cacheSize; + Byte *buf; + Byte *bufLim; + Byte *bufBase; + ISeqOutStream *outStream; + UInt64 processed; + SRes res; +} CRangeEnc; + +typedef struct +{ + CLzmaProb *litProbs; + + unsigned state; + UInt32 reps[LZMA_NUM_REPS]; + + CLzmaProb posAlignEncoder[1 << kNumAlignBits]; + CLzmaProb isRep[kNumStates]; + CLzmaProb isRepG0[kNumStates]; + CLzmaProb isRepG1[kNumStates]; + CLzmaProb isRepG2[kNumStates]; + CLzmaProb isMatch[kNumStates][LZMA_NUM_PB_STATES_MAX]; + CLzmaProb isRep0Long[kNumStates][LZMA_NUM_PB_STATES_MAX]; + + CLzmaProb posSlotEncoder[kNumLenToPosStates][1 << kNumPosSlotBits]; + CLzmaProb posEncoders[kNumFullDistances]; + + CLenEnc lenProbs; + CLenEnc repLenProbs; + +} CSaveState; + +typedef UInt32 CProbPrice; + +typedef struct +{ + void *matchFinderObj; + IMatchFinder matchFinder; + + unsigned optCur; + unsigned optEnd; + + unsigned longestMatchLen; + unsigned numPairs; + UInt32 numAvail; + + unsigned state; + unsigned numFastBytes; + unsigned additionalOffset; + UInt32 reps[LZMA_NUM_REPS]; + unsigned lpMask, pbMask; + CLzmaProb *litProbs; + CRangeEnc rc; + + UInt32 backRes; + + unsigned lc, lp, pb; + unsigned lclp; + + BoolInt fastMode; + BoolInt writeEndMark; + BoolInt finished; + BoolInt multiThread; + BoolInt needInit; + // BoolInt _maxMode; + + UInt64 nowPos64; + + unsigned matchPriceCount; + // unsigned alignPriceCount; + int repLenEncCounter; + + unsigned distTableSize; + + UInt32 dictSize; + SRes result; + + CMatchFinder matchFinderBase; + + // LZ thread + CProbPrice ProbPrices[kBitModelTotal >> kNumMoveReducingBits]; + + UInt32 matches[LZMA_MATCH_LEN_MAX * 2 + 2 + 1]; + + UInt32 alignPrices[kAlignTableSize]; + UInt32 posSlotPrices[kNumLenToPosStates][kDistTableSizeMax]; + UInt32 distancesPrices[kNumLenToPosStates][kNumFullDistances]; + + CLzmaProb posAlignEncoder[1 << kNumAlignBits]; + CLzmaProb isRep[kNumStates]; + CLzmaProb isRepG0[kNumStates]; + CLzmaProb isRepG1[kNumStates]; + CLzmaProb isRepG2[kNumStates]; + CLzmaProb isMatch[kNumStates][LZMA_NUM_PB_STATES_MAX]; + CLzmaProb isRep0Long[kNumStates][LZMA_NUM_PB_STATES_MAX]; + CLzmaProb posSlotEncoder[kNumLenToPosStates][1 << kNumPosSlotBits]; + CLzmaProb posEncoders[kNumFullDistances]; + + CLenEnc lenProbs; + CLenEnc repLenProbs; + + #ifndef LZMA_LOG_BSR + Byte g_FastPos[1 << kNumLogBits]; + #endif + + CLenPriceEnc lenEnc; + CLenPriceEnc repLenEnc; + + COptimal opt[kNumOpts]; + + CSaveState saveState; + +} CLzmaEnc; + +#define COPY_ARR(dest, src, arr) memcpy(dest->arr, src->arr, sizeof(src->arr)); + +void LzmaEnc_SaveState(CLzmaEncHandle pp) +{ + CLzmaEnc *p = (CLzmaEnc *)pp; + CSaveState *dest = &p->saveState; + + dest->state = p->state; + + dest->lenProbs = p->lenProbs; + dest->repLenProbs = p->repLenProbs; + + COPY_ARR(dest, p, reps); + + COPY_ARR(dest, p, posAlignEncoder); + COPY_ARR(dest, p, isRep); + COPY_ARR(dest, p, isRepG0); + COPY_ARR(dest, p, isRepG1); + COPY_ARR(dest, p, isRepG2); + COPY_ARR(dest, p, isMatch); + COPY_ARR(dest, p, isRep0Long); + COPY_ARR(dest, p, posSlotEncoder); + COPY_ARR(dest, p, posEncoders); + + memcpy(dest->litProbs, p->litProbs, ((UInt32)0x300 << p->lclp) * sizeof(CLzmaProb)); +} + +void LzmaEnc_RestoreState(CLzmaEncHandle pp) +{ + CLzmaEnc *dest = (CLzmaEnc *)pp; + const CSaveState *p = &dest->saveState; + + dest->state = p->state; + + dest->lenProbs = p->lenProbs; + dest->repLenProbs = p->repLenProbs; + + COPY_ARR(dest, p, reps); + + COPY_ARR(dest, p, posAlignEncoder); + COPY_ARR(dest, p, isRep); + COPY_ARR(dest, p, isRepG0); + COPY_ARR(dest, p, isRepG1); + COPY_ARR(dest, p, isRepG2); + COPY_ARR(dest, p, isMatch); + COPY_ARR(dest, p, isRep0Long); + COPY_ARR(dest, p, posSlotEncoder); + COPY_ARR(dest, p, posEncoders); + + memcpy(dest->litProbs, p->litProbs, ((UInt32)0x300 << dest->lclp) * sizeof(CLzmaProb)); +} + +SRes LzmaEnc_SetProps(CLzmaEncHandle pp, const CLzmaEncProps *props2) +{ + CLzmaEnc *p = (CLzmaEnc *)pp; + CLzmaEncProps props = *props2; + LzmaEncProps_Normalize(&props); + + if (props.lc > LZMA_LC_MAX + || props.lp > LZMA_LP_MAX + || props.pb > LZMA_PB_MAX + || props.dictSize > ((UInt64)1 << kDicLogSizeMaxCompress) + || props.dictSize > kLzmaMaxHistorySize) + return SZ_ERROR_PARAM; + + p->dictSize = props.dictSize; + { + unsigned fb = props.fb; + if (fb < 5) + fb = 5; + if (fb > LZMA_MATCH_LEN_MAX) + fb = LZMA_MATCH_LEN_MAX; + p->numFastBytes = fb; + } + p->lc = props.lc; + p->lp = props.lp; + p->pb = props.pb; + p->fastMode = (props.algo == 0); + // p->_maxMode = True; + p->matchFinderBase.btMode = (Byte)(props.btMode ? 1 : 0); + { + unsigned numHashBytes = 4; + if (props.btMode) + { + if (props.numHashBytes < 2) + numHashBytes = 2; + else if (props.numHashBytes < 4) + numHashBytes = props.numHashBytes; + } + p->matchFinderBase.numHashBytes = numHashBytes; + } + + p->matchFinderBase.cutValue = props.mc; + + p->writeEndMark = props.writeEndMark; + + return SZ_OK; +} + +void LzmaEnc_SetDataSize(CLzmaEncHandle pp, UInt64 expectedDataSiize) +{ + CLzmaEnc *p = (CLzmaEnc *)pp; + p->matchFinderBase.expectedDataSize = expectedDataSiize; +} + +#define kState_Start 0 +#define kState_LitAfterMatch 4 +#define kState_LitAfterRep 5 +#define kState_MatchAfterLit 7 +#define kState_RepAfterLit 8 + +static const Byte kLiteralNextStates[kNumStates] = {0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 4, 5}; +static const Byte kMatchNextStates[kNumStates] = {7, 7, 7, 7, 7, 7, 7, 10, 10, 10, 10, 10}; +static const Byte kRepNextStates[kNumStates] = {8, 8, 8, 8, 8, 8, 8, 11, 11, 11, 11, 11}; +static const Byte kShortRepNextStates[kNumStates]= {9, 9, 9, 9, 9, 9, 9, 11, 11, 11, 11, 11}; + +#define IsLitState(s) ((s) < 7) +#define GetLenToPosState2(len) (((len) < kNumLenToPosStates - 1) ? (len) : kNumLenToPosStates - 1) +#define GetLenToPosState(len) (((len) < kNumLenToPosStates + 1) ? (len) - 2 : kNumLenToPosStates - 1) + +#define kInfinityPrice (1 << 30) + +static void RangeEnc_Construct(CRangeEnc *p) +{ + p->outStream = NULL; + p->bufBase = NULL; +} + +#define RangeEnc_GetProcessed(p) ((p)->processed + ((p)->buf - (p)->bufBase) + (p)->cacheSize) +#define RangeEnc_GetProcessed_sizet(p) ((size_t)(p)->processed + ((p)->buf - (p)->bufBase) + (size_t)(p)->cacheSize) + +#define RC_BUF_SIZE (1 << 16) + +static int RangeEnc_Alloc(CRangeEnc *p, ISzAllocPtr alloc) +{ + if (!p->bufBase) + { + p->bufBase = (Byte *)ISzAlloc_Alloc(alloc, RC_BUF_SIZE); + if (!p->bufBase) + return 0; + p->bufLim = p->bufBase + RC_BUF_SIZE; + } + return 1; +} + +static void RangeEnc_Free(CRangeEnc *p, ISzAllocPtr alloc) +{ + ISzAlloc_Free(alloc, p->bufBase); + p->bufBase = 0; +} + +static void RangeEnc_Init(CRangeEnc *p) +{ + /* Stream.Init(); */ + p->range = 0xFFFFFFFF; + p->cache = 0; + p->low = 0; + p->cacheSize = 0; + + p->buf = p->bufBase; + + p->processed = 0; + p->res = SZ_OK; +} + +MY_NO_INLINE static void RangeEnc_FlushStream(CRangeEnc *p) +{ + size_t num; + if (p->res != SZ_OK) + return; + num = p->buf - p->bufBase; + if (num != ISeqOutStream_Write(p->outStream, p->bufBase, num)) + p->res = SZ_ERROR_WRITE; + p->processed += num; + p->buf = p->bufBase; +} + +MY_NO_INLINE static void MY_FAST_CALL RangeEnc_ShiftLow(CRangeEnc *p) +{ + UInt32 low = (UInt32)p->low; + unsigned high = (unsigned)(p->low >> 32); + p->low = (UInt32)(low << 8); + if (low < (UInt32)0xFF000000 || high != 0) + { + { + Byte *buf = p->buf; + *buf++ = (Byte)(p->cache + high); + p->cache = (unsigned)(low >> 24); + p->buf = buf; + if (buf == p->bufLim) + RangeEnc_FlushStream(p); + if (p->cacheSize == 0) + return; + } + high += 0xFF; + for (;;) + { + Byte *buf = p->buf; + *buf++ = (Byte)(high); + p->buf = buf; + if (buf == p->bufLim) + RangeEnc_FlushStream(p); + if (--p->cacheSize == 0) + return; + } + } + p->cacheSize++; +} + +static void RangeEnc_FlushData(CRangeEnc *p) +{ + int i; + for (i = 0; i < 5; i++) + RangeEnc_ShiftLow(p); +} + +#define RC_NORM(p) if (range < kTopValue) { range <<= 8; RangeEnc_ShiftLow(p); } + +#define RC_BIT_PRE(p, prob) \ + ttt = *(prob); \ + newBound = (range >> kNumBitModelTotalBits) * ttt; + +// #define _LZMA_ENC_USE_BRANCH + +#ifdef _LZMA_ENC_USE_BRANCH + +#define RC_BIT(p, prob, bit) { \ + RC_BIT_PRE(p, prob) \ + if (bit == 0) { range = newBound; ttt += (kBitModelTotal - ttt) >> kNumMoveBits; } \ + else { (p)->low += newBound; range -= newBound; ttt -= ttt >> kNumMoveBits; } \ + *(prob) = (CLzmaProb)ttt; \ + RC_NORM(p) \ + } + +#else + +#define RC_BIT(p, prob, bit) { \ + UInt32 mask; \ + RC_BIT_PRE(p, prob) \ + mask = 0 - (UInt32)bit; \ + range &= mask; \ + mask &= newBound; \ + range -= mask; \ + (p)->low += mask; \ + mask = (UInt32)bit - 1; \ + range += newBound & mask; \ + mask &= (kBitModelTotal - ((1 << kNumMoveBits) - 1)); \ + mask += ((1 << kNumMoveBits) - 1); \ + ttt += (Int32)(mask - ttt) >> kNumMoveBits; \ + *(prob) = (CLzmaProb)ttt; \ + RC_NORM(p) \ + } + +#endif + +#define RC_BIT_0_BASE(p, prob) \ + range = newBound; *(prob) = (CLzmaProb)(ttt + ((kBitModelTotal - ttt) >> kNumMoveBits)); + +#define RC_BIT_1_BASE(p, prob) \ + range -= newBound; (p)->low += newBound; *(prob) = (CLzmaProb)(ttt - (ttt >> kNumMoveBits)); \ + +#define RC_BIT_0(p, prob) \ + RC_BIT_0_BASE(p, prob) \ + RC_NORM(p) + +#define RC_BIT_1(p, prob) \ + RC_BIT_1_BASE(p, prob) \ + RC_NORM(p) + +static void RangeEnc_EncodeBit_0(CRangeEnc *p, CLzmaProb *prob) +{ + UInt32 range, ttt, newBound; + range = p->range; + RC_BIT_PRE(p, prob) + RC_BIT_0(p, prob) + p->range = range; +} + +static void LitEnc_Encode(CRangeEnc *p, CLzmaProb *probs, UInt32 sym) +{ + UInt32 range = p->range; + sym |= 0x100; + do + { + UInt32 ttt, newBound; + // RangeEnc_EncodeBit(p, probs + (sym >> 8), (sym >> 7) & 1); + CLzmaProb *prob = probs + (sym >> 8); + UInt32 bit = (sym >> 7) & 1; + sym <<= 1; + RC_BIT(p, prob, bit); + } + while (sym < 0x10000); + p->range = range; +} + +static void LitEnc_EncodeMatched(CRangeEnc *p, CLzmaProb *probs, UInt32 sym, UInt32 matchByte) +{ + UInt32 range = p->range; + UInt32 offs = 0x100; + sym |= 0x100; + do + { + UInt32 ttt, newBound; + CLzmaProb *prob; + UInt32 bit; + matchByte <<= 1; + // RangeEnc_EncodeBit(p, probs + (offs + (matchByte & offs) + (sym >> 8)), (sym >> 7) & 1); + prob = probs + (offs + (matchByte & offs) + (sym >> 8)); + bit = (sym >> 7) & 1; + sym <<= 1; + offs &= ~(matchByte ^ sym); + RC_BIT(p, prob, bit); + } + while (sym < 0x10000); + p->range = range; +} + +static void LzmaEnc_InitPriceTables(CProbPrice *ProbPrices) +{ + UInt32 i; + for (i = 0; i < (kBitModelTotal >> kNumMoveReducingBits); i++) + { + const unsigned kCyclesBits = kNumBitPriceShiftBits; + UInt32 w = (i << kNumMoveReducingBits) + (1 << (kNumMoveReducingBits - 1)); + unsigned bitCount = 0; + unsigned j; + for (j = 0; j < kCyclesBits; j++) + { + w = w * w; + bitCount <<= 1; + while (w >= ((UInt32)1 << 16)) + { + w >>= 1; + bitCount++; + } + } + ProbPrices[i] = (CProbPrice)((kNumBitModelTotalBits << kCyclesBits) - 15 - bitCount); + // printf("\n%3d: %5d", i, ProbPrices[i]); + } +} + +#define GET_PRICE(prob, bit) \ + p->ProbPrices[((prob) ^ (unsigned)(((-(int)(bit))) & (kBitModelTotal - 1))) >> kNumMoveReducingBits]; + +#define GET_PRICEa(prob, bit) \ + ProbPrices[((prob) ^ (unsigned)((-((int)(bit))) & (kBitModelTotal - 1))) >> kNumMoveReducingBits]; + +#define GET_PRICE_0(prob) p->ProbPrices[(prob) >> kNumMoveReducingBits] +#define GET_PRICE_1(prob) p->ProbPrices[((prob) ^ (kBitModelTotal - 1)) >> kNumMoveReducingBits] + +#define GET_PRICEa_0(prob) ProbPrices[(prob) >> kNumMoveReducingBits] +#define GET_PRICEa_1(prob) ProbPrices[((prob) ^ (kBitModelTotal - 1)) >> kNumMoveReducingBits] + +static UInt32 LitEnc_GetPrice(const CLzmaProb *probs, UInt32 sym, const CProbPrice *ProbPrices) +{ + UInt32 price = 0; + sym |= 0x100; + do + { + unsigned bit = sym & 1; + sym >>= 1; + price += GET_PRICEa(probs[sym], bit); + } + while (sym >= 2); + return price; +} + +static UInt32 LitEnc_Matched_GetPrice(const CLzmaProb *probs, UInt32 sym, UInt32 matchByte, const CProbPrice *ProbPrices) +{ + UInt32 price = 0; + UInt32 offs = 0x100; + sym |= 0x100; + do + { + matchByte <<= 1; + price += GET_PRICEa(probs[offs + (matchByte & offs) + (sym >> 8)], (sym >> 7) & 1); + sym <<= 1; + offs &= ~(matchByte ^ sym); + } + while (sym < 0x10000); + return price; +} + +static void RcTree_ReverseEncode(CRangeEnc *rc, CLzmaProb *probs, unsigned numBits, unsigned sym) +{ + UInt32 range = rc->range; + unsigned m = 1; + do + { + UInt32 ttt, newBound; + unsigned bit = sym & 1; + // RangeEnc_EncodeBit(rc, probs + m, bit); + sym >>= 1; + RC_BIT(rc, probs + m, bit); + m = (m << 1) | bit; + } + while (--numBits); + rc->range = range; +} + +static void LenEnc_Init(CLenEnc *p) +{ + unsigned i; + for (i = 0; i < (LZMA_NUM_PB_STATES_MAX << (kLenNumLowBits + 1)); i++) + p->low[i] = kProbInitValue; + for (i = 0; i < kLenNumHighSymbols; i++) + p->high[i] = kProbInitValue; +} + +static void LenEnc_Encode(CLenEnc *p, CRangeEnc *rc, unsigned sym, unsigned posState) +{ + UInt32 range, ttt, newBound; + CLzmaProb *probs = p->low; + range = rc->range; + RC_BIT_PRE(rc, probs); + if (sym >= kLenNumLowSymbols) + { + RC_BIT_1(rc, probs); + probs += kLenNumLowSymbols; + RC_BIT_PRE(rc, probs); + if (sym >= kLenNumLowSymbols * 2) + { + RC_BIT_1(rc, probs); + rc->range = range; + // RcTree_Encode(rc, p->high, kLenNumHighBits, sym - kLenNumLowSymbols * 2); + LitEnc_Encode(rc, p->high, sym - kLenNumLowSymbols * 2); + return; + } + sym -= kLenNumLowSymbols; + } + + // RcTree_Encode(rc, probs + (posState << kLenNumLowBits), kLenNumLowBits, sym); + { + unsigned m; + unsigned bit; + RC_BIT_0(rc, probs); + probs += (posState << (1 + kLenNumLowBits)); + bit = (sym >> 2) ; RC_BIT(rc, probs + 1, bit); m = (1 << 1) + bit; + bit = (sym >> 1) & 1; RC_BIT(rc, probs + m, bit); m = (m << 1) + bit; + bit = sym & 1; RC_BIT(rc, probs + m, bit); + rc->range = range; + } +} + +static void SetPrices_3(const CLzmaProb *probs, UInt32 startPrice, UInt32 *prices, const CProbPrice *ProbPrices) +{ + unsigned i; + for (i = 0; i < 8; i += 2) + { + UInt32 price = startPrice; + UInt32 prob; + price += GET_PRICEa(probs[1 ], (i >> 2)); + price += GET_PRICEa(probs[2 + (i >> 2)], (i >> 1) & 1); + prob = probs[4 + (i >> 1)]; + prices[i ] = price + GET_PRICEa_0(prob); + prices[i + 1] = price + GET_PRICEa_1(prob); + } +} + +MY_NO_INLINE static void MY_FAST_CALL LenPriceEnc_UpdateTables( + CLenPriceEnc *p, + unsigned numPosStates, + const CLenEnc *enc, + const CProbPrice *ProbPrices) +{ + UInt32 b; + + { + unsigned prob = enc->low[0]; + UInt32 a, c; + unsigned posState; + b = GET_PRICEa_1(prob); + a = GET_PRICEa_0(prob); + c = b + GET_PRICEa_0(enc->low[kLenNumLowSymbols]); + for (posState = 0; posState < numPosStates; posState++) + { + UInt32 *prices = p->prices[posState]; + const CLzmaProb *probs = enc->low + (posState << (1 + kLenNumLowBits)); + SetPrices_3(probs, a, prices, ProbPrices); + SetPrices_3(probs + kLenNumLowSymbols, c, prices + kLenNumLowSymbols, ProbPrices); + } + } + + /* + { + unsigned i; + UInt32 b; + a = GET_PRICEa_0(enc->low[0]); + for (i = 0; i < kLenNumLowSymbols; i++) + p->prices2[i] = a; + a = GET_PRICEa_1(enc->low[0]); + b = a + GET_PRICEa_0(enc->low[kLenNumLowSymbols]); + for (i = kLenNumLowSymbols; i < kLenNumLowSymbols * 2; i++) + p->prices2[i] = b; + a += GET_PRICEa_1(enc->low[kLenNumLowSymbols]); + } + */ + + // p->counter = numSymbols; + // p->counter = 64; + + { + unsigned i = p->tableSize; + + if (i > kLenNumLowSymbols * 2) + { + const CLzmaProb *probs = enc->high; + UInt32 *prices = p->prices[0] + kLenNumLowSymbols * 2; + i -= kLenNumLowSymbols * 2 - 1; + i >>= 1; + b += GET_PRICEa_1(enc->low[kLenNumLowSymbols]); + do + { + /* + p->prices2[i] = a + + // RcTree_GetPrice(enc->high, kLenNumHighBits, i - kLenNumLowSymbols * 2, ProbPrices); + LitEnc_GetPrice(probs, i - kLenNumLowSymbols * 2, ProbPrices); + */ + // UInt32 price = a + RcTree_GetPrice(probs, kLenNumHighBits - 1, sym, ProbPrices); + unsigned sym = --i + (1 << (kLenNumHighBits - 1)); + UInt32 price = b; + do + { + unsigned bit = sym & 1; + sym >>= 1; + price += GET_PRICEa(probs[sym], bit); + } + while (sym >= 2); + + { + unsigned prob = probs[(size_t)i + (1 << (kLenNumHighBits - 1))]; + prices[(size_t)i * 2 ] = price + GET_PRICEa_0(prob); + prices[(size_t)i * 2 + 1] = price + GET_PRICEa_1(prob); + } + } + while (i); + + { + unsigned posState; + size_t num = (p->tableSize - kLenNumLowSymbols * 2) * sizeof(p->prices[0][0]); + for (posState = 1; posState < numPosStates; posState++) + memcpy(p->prices[posState] + kLenNumLowSymbols * 2, p->prices[0] + kLenNumLowSymbols * 2, num); + } + } + } +} + +/* + #ifdef SHOW_STAT + g_STAT_OFFSET += num; + printf("\n MovePos %u", num); + #endif +*/ + +#define MOVE_POS(p, num) { \ + p->additionalOffset += (num); \ + p->matchFinder.Skip(p->matchFinderObj, (UInt32)(num)); } + +static unsigned ReadMatchDistances(CLzmaEnc *p, unsigned *numPairsRes) +{ + unsigned numPairs; + + p->additionalOffset++; + p->numAvail = p->matchFinder.GetNumAvailableBytes(p->matchFinderObj); + numPairs = p->matchFinder.GetMatches(p->matchFinderObj, p->matches); + *numPairsRes = numPairs; + + #ifdef SHOW_STAT + printf("\n i = %u numPairs = %u ", g_STAT_OFFSET, numPairs / 2); + g_STAT_OFFSET++; + { + unsigned i; + for (i = 0; i < numPairs; i += 2) + printf("%2u %6u | ", p->matches[i], p->matches[i + 1]); + } + #endif + + if (numPairs == 0) + return 0; + { + unsigned len = p->matches[(size_t)numPairs - 2]; + if (len != p->numFastBytes) + return len; + { + UInt32 numAvail = p->numAvail; + if (numAvail > LZMA_MATCH_LEN_MAX) + numAvail = LZMA_MATCH_LEN_MAX; + { + const Byte *p1 = p->matchFinder.GetPointerToCurrentPos(p->matchFinderObj) - 1; + const Byte *p2 = p1 + len; + ptrdiff_t dif = (ptrdiff_t)-1 - p->matches[(size_t)numPairs - 1]; + const Byte *lim = p1 + numAvail; + for (; p2 != lim && *p2 == p2[dif]; p2++) + {} + return (unsigned)(p2 - p1); + } + } + } +} + +#define MARK_LIT ((UInt32)(Int32)-1) + +#define MakeAs_Lit(p) { (p)->dist = MARK_LIT; (p)->extra = 0; } +#define MakeAs_ShortRep(p) { (p)->dist = 0; (p)->extra = 0; } +#define IsShortRep(p) ((p)->dist == 0) + +#define GetPrice_ShortRep(p, state, posState) \ + ( GET_PRICE_0(p->isRepG0[state]) + GET_PRICE_0(p->isRep0Long[state][posState])) + +#define GetPrice_Rep_0(p, state, posState) ( \ + GET_PRICE_1(p->isMatch[state][posState]) \ + + GET_PRICE_1(p->isRep0Long[state][posState])) \ + + GET_PRICE_1(p->isRep[state]) \ + + GET_PRICE_0(p->isRepG0[state]) + +MY_FORCE_INLINE +static UInt32 GetPrice_PureRep(const CLzmaEnc *p, unsigned repIndex, size_t state, size_t posState) +{ + UInt32 price; + UInt32 prob = p->isRepG0[state]; + if (repIndex == 0) + { + price = GET_PRICE_0(prob); + price += GET_PRICE_1(p->isRep0Long[state][posState]); + } + else + { + price = GET_PRICE_1(prob); + prob = p->isRepG1[state]; + if (repIndex == 1) + price += GET_PRICE_0(prob); + else + { + price += GET_PRICE_1(prob); + price += GET_PRICE(p->isRepG2[state], repIndex - 2); + } + } + return price; +} + +static unsigned Backward(CLzmaEnc *p, unsigned cur) +{ + unsigned wr = cur + 1; + p->optEnd = wr; + + for (;;) + { + UInt32 dist = p->opt[cur].dist; + unsigned len = (unsigned)p->opt[cur].len; + unsigned extra = (unsigned)p->opt[cur].extra; + cur -= len; + + if (extra) + { + wr--; + p->opt[wr].len = (UInt32)len; + cur -= extra; + len = extra; + if (extra == 1) + { + p->opt[wr].dist = dist; + dist = MARK_LIT; + } + else + { + p->opt[wr].dist = 0; + len--; + wr--; + p->opt[wr].dist = MARK_LIT; + p->opt[wr].len = 1; + } + } + + if (cur == 0) + { + p->backRes = dist; + p->optCur = wr; + return len; + } + + wr--; + p->opt[wr].dist = dist; + p->opt[wr].len = (UInt32)len; + } +} + +#define LIT_PROBS(pos, prevByte) \ + (p->litProbs + (UInt32)3 * (((((pos) << 8) + (prevByte)) & p->lpMask) << p->lc)) + +static unsigned GetOptimum(CLzmaEnc *p, UInt32 position) +{ + unsigned last, cur; + UInt32 reps[LZMA_NUM_REPS]; + unsigned repLens[LZMA_NUM_REPS]; + UInt32 *matches; + + { + UInt32 numAvail; + unsigned numPairs, mainLen, repMaxIndex, i, posState; + UInt32 matchPrice, repMatchPrice; + const Byte *data; + Byte curByte, matchByte; + + p->optCur = p->optEnd = 0; + + if (p->additionalOffset == 0) + mainLen = ReadMatchDistances(p, &numPairs); + else + { + mainLen = p->longestMatchLen; + numPairs = p->numPairs; + } + + numAvail = p->numAvail; + if (numAvail < 2) + { + p->backRes = MARK_LIT; + return 1; + } + if (numAvail > LZMA_MATCH_LEN_MAX) + numAvail = LZMA_MATCH_LEN_MAX; + + data = p->matchFinder.GetPointerToCurrentPos(p->matchFinderObj) - 1; + repMaxIndex = 0; + + for (i = 0; i < LZMA_NUM_REPS; i++) + { + unsigned len; + const Byte *data2; + reps[i] = p->reps[i]; + data2 = data - reps[i]; + if (data[0] != data2[0] || data[1] != data2[1]) + { + repLens[i] = 0; + continue; + } + for (len = 2; len < numAvail && data[len] == data2[len]; len++) + {} + repLens[i] = len; + if (len > repLens[repMaxIndex]) + repMaxIndex = i; + } + + if (repLens[repMaxIndex] >= p->numFastBytes) + { + unsigned len; + p->backRes = (UInt32)repMaxIndex; + len = repLens[repMaxIndex]; + MOVE_POS(p, len - 1) + return len; + } + + matches = p->matches; + + if (mainLen >= p->numFastBytes) + { + p->backRes = matches[(size_t)numPairs - 1] + LZMA_NUM_REPS; + MOVE_POS(p, mainLen - 1) + return mainLen; + } + + curByte = *data; + matchByte = *(data - reps[0]); + + last = repLens[repMaxIndex]; + if (last <= mainLen) + last = mainLen; + + if (last < 2 && curByte != matchByte) + { + p->backRes = MARK_LIT; + return 1; + } + + p->opt[0].state = (CState)p->state; + + posState = (position & p->pbMask); + + { + const CLzmaProb *probs = LIT_PROBS(position, *(data - 1)); + p->opt[1].price = GET_PRICE_0(p->isMatch[p->state][posState]) + + (!IsLitState(p->state) ? + LitEnc_Matched_GetPrice(probs, curByte, matchByte, p->ProbPrices) : + LitEnc_GetPrice(probs, curByte, p->ProbPrices)); + } + + MakeAs_Lit(&p->opt[1]); + + matchPrice = GET_PRICE_1(p->isMatch[p->state][posState]); + repMatchPrice = matchPrice + GET_PRICE_1(p->isRep[p->state]); + + // 18.06 + if (matchByte == curByte && repLens[0] == 0) + { + UInt32 shortRepPrice = repMatchPrice + GetPrice_ShortRep(p, p->state, posState); + if (shortRepPrice < p->opt[1].price) + { + p->opt[1].price = shortRepPrice; + MakeAs_ShortRep(&p->opt[1]); + } + if (last < 2) + { + p->backRes = p->opt[1].dist; + return 1; + } + } + + p->opt[1].len = 1; + + p->opt[0].reps[0] = reps[0]; + p->opt[0].reps[1] = reps[1]; + p->opt[0].reps[2] = reps[2]; + p->opt[0].reps[3] = reps[3]; + + // ---------- REP ---------- + + for (i = 0; i < LZMA_NUM_REPS; i++) + { + unsigned repLen = repLens[i]; + UInt32 price; + if (repLen < 2) + continue; + price = repMatchPrice + GetPrice_PureRep(p, i, p->state, posState); + do + { + UInt32 price2 = price + GET_PRICE_LEN(&p->repLenEnc, posState, repLen); + COptimal *opt = &p->opt[repLen]; + if (price2 < opt->price) + { + opt->price = price2; + opt->len = (UInt32)repLen; + opt->dist = (UInt32)i; + opt->extra = 0; + } + } + while (--repLen >= 2); + } + + // ---------- MATCH ---------- + { + unsigned len = repLens[0] + 1; + if (len <= mainLen) + { + unsigned offs = 0; + UInt32 normalMatchPrice = matchPrice + GET_PRICE_0(p->isRep[p->state]); + + if (len < 2) + len = 2; + else + while (len > matches[offs]) + offs += 2; + + for (; ; len++) + { + COptimal *opt; + UInt32 dist = matches[(size_t)offs + 1]; + UInt32 price = normalMatchPrice + GET_PRICE_LEN(&p->lenEnc, posState, len); + unsigned lenToPosState = GetLenToPosState(len); + + if (dist < kNumFullDistances) + price += p->distancesPrices[lenToPosState][dist & (kNumFullDistances - 1)]; + else + { + unsigned slot; + GetPosSlot2(dist, slot); + price += p->alignPrices[dist & kAlignMask]; + price += p->posSlotPrices[lenToPosState][slot]; + } + + opt = &p->opt[len]; + + if (price < opt->price) + { + opt->price = price; + opt->len = (UInt32)len; + opt->dist = dist + LZMA_NUM_REPS; + opt->extra = 0; + } + + if (len == matches[offs]) + { + offs += 2; + if (offs == numPairs) + break; + } + } + } + } + + cur = 0; + + #ifdef SHOW_STAT2 + /* if (position >= 0) */ + { + unsigned i; + printf("\n pos = %4X", position); + for (i = cur; i <= last; i++) + printf("\nprice[%4X] = %u", position - cur + i, p->opt[i].price); + } + #endif + } + + // ---------- Optimal Parsing ---------- + + for (;;) + { + unsigned numAvail; + UInt32 numAvailFull; + unsigned newLen, numPairs, prev, state, posState, startLen; + UInt32 litPrice, matchPrice, repMatchPrice; + BoolInt nextIsLit; + Byte curByte, matchByte; + const Byte *data; + COptimal *curOpt, *nextOpt; + + if (++cur == last) + break; + + // 18.06 + if (cur >= kNumOpts - 64) + { + unsigned j, best; + UInt32 price = p->opt[cur].price; + best = cur; + for (j = cur + 1; j <= last; j++) + { + UInt32 price2 = p->opt[j].price; + if (price >= price2) + { + price = price2; + best = j; + } + } + { + unsigned delta = best - cur; + if (delta != 0) + { + MOVE_POS(p, delta); + } + } + cur = best; + break; + } + + newLen = ReadMatchDistances(p, &numPairs); + + if (newLen >= p->numFastBytes) + { + p->numPairs = numPairs; + p->longestMatchLen = newLen; + break; + } + + curOpt = &p->opt[cur]; + + position++; + + // we need that check here, if skip_items in p->opt are possible + /* + if (curOpt->price >= kInfinityPrice) + continue; + */ + + prev = cur - curOpt->len; + + if (curOpt->len == 1) + { + state = (unsigned)p->opt[prev].state; + if (IsShortRep(curOpt)) + state = kShortRepNextStates[state]; + else + state = kLiteralNextStates[state]; + } + else + { + const COptimal *prevOpt; + UInt32 b0; + UInt32 dist = curOpt->dist; + + if (curOpt->extra) + { + prev -= (unsigned)curOpt->extra; + state = kState_RepAfterLit; + if (curOpt->extra == 1) + state = (dist < LZMA_NUM_REPS ? kState_RepAfterLit : kState_MatchAfterLit); + } + else + { + state = (unsigned)p->opt[prev].state; + if (dist < LZMA_NUM_REPS) + state = kRepNextStates[state]; + else + state = kMatchNextStates[state]; + } + + prevOpt = &p->opt[prev]; + b0 = prevOpt->reps[0]; + + if (dist < LZMA_NUM_REPS) + { + if (dist == 0) + { + reps[0] = b0; + reps[1] = prevOpt->reps[1]; + reps[2] = prevOpt->reps[2]; + reps[3] = prevOpt->reps[3]; + } + else + { + reps[1] = b0; + b0 = prevOpt->reps[1]; + if (dist == 1) + { + reps[0] = b0; + reps[2] = prevOpt->reps[2]; + reps[3] = prevOpt->reps[3]; + } + else + { + reps[2] = b0; + reps[0] = prevOpt->reps[dist]; + reps[3] = prevOpt->reps[dist ^ 1]; + } + } + } + else + { + reps[0] = (dist - LZMA_NUM_REPS + 1); + reps[1] = b0; + reps[2] = prevOpt->reps[1]; + reps[3] = prevOpt->reps[2]; + } + } + + curOpt->state = (CState)state; + curOpt->reps[0] = reps[0]; + curOpt->reps[1] = reps[1]; + curOpt->reps[2] = reps[2]; + curOpt->reps[3] = reps[3]; + + data = p->matchFinder.GetPointerToCurrentPos(p->matchFinderObj) - 1; + curByte = *data; + matchByte = *(data - reps[0]); + + posState = (position & p->pbMask); + + /* + The order of Price checks: + < LIT + <= SHORT_REP + < LIT : REP_0 + < REP [ : LIT : REP_0 ] + < MATCH [ : LIT : REP_0 ] + */ + + { + UInt32 curPrice = curOpt->price; + unsigned prob = p->isMatch[state][posState]; + matchPrice = curPrice + GET_PRICE_1(prob); + litPrice = curPrice + GET_PRICE_0(prob); + } + + nextOpt = &p->opt[(size_t)cur + 1]; + nextIsLit = False; + + // here we can allow skip_items in p->opt, if we don't check (nextOpt->price < kInfinityPrice) + // 18.new.06 + if ((nextOpt->price < kInfinityPrice + // && !IsLitState(state) + && matchByte == curByte) + || litPrice > nextOpt->price + ) + litPrice = 0; + else + { + const CLzmaProb *probs = LIT_PROBS(position, *(data - 1)); + litPrice += (!IsLitState(state) ? + LitEnc_Matched_GetPrice(probs, curByte, matchByte, p->ProbPrices) : + LitEnc_GetPrice(probs, curByte, p->ProbPrices)); + + if (litPrice < nextOpt->price) + { + nextOpt->price = litPrice; + nextOpt->len = 1; + MakeAs_Lit(nextOpt); + nextIsLit = True; + } + } + + repMatchPrice = matchPrice + GET_PRICE_1(p->isRep[state]); + + numAvailFull = p->numAvail; + { + unsigned temp = kNumOpts - 1 - cur; + if (numAvailFull > temp) + numAvailFull = (UInt32)temp; + } + + // 18.06 + // ---------- SHORT_REP ---------- + if (IsLitState(state)) // 18.new + if (matchByte == curByte) + if (repMatchPrice < nextOpt->price) // 18.new + // if (numAvailFull < 2 || data[1] != *(data - reps[0] + 1)) + if ( + // nextOpt->price >= kInfinityPrice || + nextOpt->len < 2 // we can check nextOpt->len, if skip items are not allowed in p->opt + || (nextOpt->dist != 0 + // && nextOpt->extra <= 1 // 17.old + ) + ) + { + UInt32 shortRepPrice = repMatchPrice + GetPrice_ShortRep(p, state, posState); + // if (shortRepPrice <= nextOpt->price) // 17.old + if (shortRepPrice < nextOpt->price) // 18.new + { + nextOpt->price = shortRepPrice; + nextOpt->len = 1; + MakeAs_ShortRep(nextOpt); + nextIsLit = False; + } + } + + if (numAvailFull < 2) + continue; + numAvail = (numAvailFull <= p->numFastBytes ? numAvailFull : p->numFastBytes); + + // numAvail <= p->numFastBytes + + // ---------- LIT : REP_0 ---------- + + if (!nextIsLit + && litPrice != 0 // 18.new + && matchByte != curByte + && numAvailFull > 2) + { + const Byte *data2 = data - reps[0]; + if (data[1] == data2[1] && data[2] == data2[2]) + { + unsigned len; + unsigned limit = p->numFastBytes + 1; + if (limit > numAvailFull) + limit = numAvailFull; + for (len = 3; len < limit && data[len] == data2[len]; len++) + {} + + { + unsigned state2 = kLiteralNextStates[state]; + unsigned posState2 = (position + 1) & p->pbMask; + UInt32 price = litPrice + GetPrice_Rep_0(p, state2, posState2); + { + unsigned offset = cur + len; + + if (last < offset) + last = offset; + + // do + { + UInt32 price2; + COptimal *opt; + len--; + // price2 = price + GetPrice_Len_Rep_0(p, len, state2, posState2); + price2 = price + GET_PRICE_LEN(&p->repLenEnc, posState2, len); + + opt = &p->opt[offset]; + // offset--; + if (price2 < opt->price) + { + opt->price = price2; + opt->len = (UInt32)len; + opt->dist = 0; + opt->extra = 1; + } + } + // while (len >= 3); + } + } + } + } + + startLen = 2; /* speed optimization */ + + { + // ---------- REP ---------- + unsigned repIndex = 0; // 17.old + // unsigned repIndex = IsLitState(state) ? 0 : 1; // 18.notused + for (; repIndex < LZMA_NUM_REPS; repIndex++) + { + unsigned len; + UInt32 price; + const Byte *data2 = data - reps[repIndex]; + if (data[0] != data2[0] || data[1] != data2[1]) + continue; + + for (len = 2; len < numAvail && data[len] == data2[len]; len++) + {} + + // if (len < startLen) continue; // 18.new: speed optimization + + { + unsigned offset = cur + len; + if (last < offset) + last = offset; + } + { + unsigned len2 = len; + price = repMatchPrice + GetPrice_PureRep(p, repIndex, state, posState); + do + { + UInt32 price2 = price + GET_PRICE_LEN(&p->repLenEnc, posState, len2); + COptimal *opt = &p->opt[cur + len2]; + if (price2 < opt->price) + { + opt->price = price2; + opt->len = (UInt32)len2; + opt->dist = (UInt32)repIndex; + opt->extra = 0; + } + } + while (--len2 >= 2); + } + + if (repIndex == 0) startLen = len + 1; // 17.old + // startLen = len + 1; // 18.new + + /* if (_maxMode) */ + { + // ---------- REP : LIT : REP_0 ---------- + // numFastBytes + 1 + numFastBytes + + unsigned len2 = len + 1; + unsigned limit = len2 + p->numFastBytes; + if (limit > numAvailFull) + limit = numAvailFull; + + len2 += 2; + if (len2 <= limit) + if (data[len2 - 2] == data2[len2 - 2]) + if (data[len2 - 1] == data2[len2 - 1]) + { + unsigned state2 = kRepNextStates[state]; + unsigned posState2 = (position + len) & p->pbMask; + price += GET_PRICE_LEN(&p->repLenEnc, posState, len) + + GET_PRICE_0(p->isMatch[state2][posState2]) + + LitEnc_Matched_GetPrice(LIT_PROBS(position + len, data[(size_t)len - 1]), + data[len], data2[len], p->ProbPrices); + + // state2 = kLiteralNextStates[state2]; + state2 = kState_LitAfterRep; + posState2 = (posState2 + 1) & p->pbMask; + + price += GetPrice_Rep_0(p, state2, posState2); + + for (; len2 < limit && data[len2] == data2[len2]; len2++) + {} + + len2 -= len; + // if (len2 >= 3) + { + { + unsigned offset = cur + len + len2; + + if (last < offset) + last = offset; + // do + { + UInt32 price2; + COptimal *opt; + len2--; + // price2 = price + GetPrice_Len_Rep_0(p, len2, state2, posState2); + price2 = price + GET_PRICE_LEN(&p->repLenEnc, posState2, len2); + + opt = &p->opt[offset]; + // offset--; + if (price2 < opt->price) + { + opt->price = price2; + opt->len = (UInt32)len2; + opt->extra = (CExtra)(len + 1); + opt->dist = (UInt32)repIndex; + } + } + // while (len2 >= 3); + } + } + } + } + } + } + + // ---------- MATCH ---------- + /* for (unsigned len = 2; len <= newLen; len++) */ + if (newLen > numAvail) + { + newLen = numAvail; + for (numPairs = 0; newLen > matches[numPairs]; numPairs += 2); + matches[numPairs] = (UInt32)newLen; + numPairs += 2; + } + + // startLen = 2; /* speed optimization */ + + if (newLen >= startLen) + { + UInt32 normalMatchPrice = matchPrice + GET_PRICE_0(p->isRep[state]); + UInt32 dist; + unsigned offs, posSlot, len; + + { + unsigned offset = cur + newLen; + if (last < offset) + last = offset; + } + + offs = 0; + while (startLen > matches[offs]) + offs += 2; + dist = matches[(size_t)offs + 1]; + + // if (dist >= kNumFullDistances) + GetPosSlot2(dist, posSlot); + + for (len = /*2*/ startLen; ; len++) + { + UInt32 price = normalMatchPrice + GET_PRICE_LEN(&p->lenEnc, posState, len); + { + COptimal *opt; + unsigned lenNorm = len - 2; + lenNorm = GetLenToPosState2(lenNorm); + if (dist < kNumFullDistances) + price += p->distancesPrices[lenNorm][dist & (kNumFullDistances - 1)]; + else + price += p->posSlotPrices[lenNorm][posSlot] + p->alignPrices[dist & kAlignMask]; + + opt = &p->opt[cur + len]; + if (price < opt->price) + { + opt->price = price; + opt->len = (UInt32)len; + opt->dist = dist + LZMA_NUM_REPS; + opt->extra = 0; + } + } + + if (len == matches[offs]) + { + // if (p->_maxMode) { + // MATCH : LIT : REP_0 + + const Byte *data2 = data - dist - 1; + unsigned len2 = len + 1; + unsigned limit = len2 + p->numFastBytes; + if (limit > numAvailFull) + limit = numAvailFull; + + len2 += 2; + if (len2 <= limit) + if (data[len2 - 2] == data2[len2 - 2]) + if (data[len2 - 1] == data2[len2 - 1]) + { + for (; len2 < limit && data[len2] == data2[len2]; len2++) + {} + + len2 -= len; + + // if (len2 >= 3) + { + unsigned state2 = kMatchNextStates[state]; + unsigned posState2 = (position + len) & p->pbMask; + unsigned offset; + price += GET_PRICE_0(p->isMatch[state2][posState2]); + price += LitEnc_Matched_GetPrice(LIT_PROBS(position + len, data[(size_t)len - 1]), + data[len], data2[len], p->ProbPrices); + + // state2 = kLiteralNextStates[state2]; + state2 = kState_LitAfterMatch; + + posState2 = (posState2 + 1) & p->pbMask; + price += GetPrice_Rep_0(p, state2, posState2); + + offset = cur + len + len2; + + if (last < offset) + last = offset; + // do + { + UInt32 price2; + COptimal *opt; + len2--; + // price2 = price + GetPrice_Len_Rep_0(p, len2, state2, posState2); + price2 = price + GET_PRICE_LEN(&p->repLenEnc, posState2, len2); + opt = &p->opt[offset]; + // offset--; + if (price2 < opt->price) + { + opt->price = price2; + opt->len = (UInt32)len2; + opt->extra = (CExtra)(len + 1); + opt->dist = dist + LZMA_NUM_REPS; + } + } + // while (len2 >= 3); + } + + } + + offs += 2; + if (offs == numPairs) + break; + dist = matches[(size_t)offs + 1]; + // if (dist >= kNumFullDistances) + GetPosSlot2(dist, posSlot); + } + } + } + } + + do + p->opt[last].price = kInfinityPrice; + while (--last); + + return Backward(p, cur); +} + +#define ChangePair(smallDist, bigDist) (((bigDist) >> 7) > (smallDist)) + +static unsigned GetOptimumFast(CLzmaEnc *p) +{ + UInt32 numAvail, mainDist; + unsigned mainLen, numPairs, repIndex, repLen, i; + const Byte *data; + + if (p->additionalOffset == 0) + mainLen = ReadMatchDistances(p, &numPairs); + else + { + mainLen = p->longestMatchLen; + numPairs = p->numPairs; + } + + numAvail = p->numAvail; + p->backRes = MARK_LIT; + if (numAvail < 2) + return 1; + // if (mainLen < 2 && p->state == 0) return 1; // 18.06.notused + if (numAvail > LZMA_MATCH_LEN_MAX) + numAvail = LZMA_MATCH_LEN_MAX; + data = p->matchFinder.GetPointerToCurrentPos(p->matchFinderObj) - 1; + repLen = repIndex = 0; + + for (i = 0; i < LZMA_NUM_REPS; i++) + { + unsigned len; + const Byte *data2 = data - p->reps[i]; + if (data[0] != data2[0] || data[1] != data2[1]) + continue; + for (len = 2; len < numAvail && data[len] == data2[len]; len++) + {} + if (len >= p->numFastBytes) + { + p->backRes = (UInt32)i; + MOVE_POS(p, len - 1) + return len; + } + if (len > repLen) + { + repIndex = i; + repLen = len; + } + } + + if (mainLen >= p->numFastBytes) + { + p->backRes = p->matches[(size_t)numPairs - 1] + LZMA_NUM_REPS; + MOVE_POS(p, mainLen - 1) + return mainLen; + } + + mainDist = 0; /* for GCC */ + + if (mainLen >= 2) + { + mainDist = p->matches[(size_t)numPairs - 1]; + while (numPairs > 2) + { + UInt32 dist2; + if (mainLen != p->matches[(size_t)numPairs - 4] + 1) + break; + dist2 = p->matches[(size_t)numPairs - 3]; + if (!ChangePair(dist2, mainDist)) + break; + numPairs -= 2; + mainLen--; + mainDist = dist2; + } + if (mainLen == 2 && mainDist >= 0x80) + mainLen = 1; + } + + if (repLen >= 2) + if ( repLen + 1 >= mainLen + || (repLen + 2 >= mainLen && mainDist >= (1 << 9)) + || (repLen + 3 >= mainLen && mainDist >= (1 << 15))) + { + p->backRes = (UInt32)repIndex; + MOVE_POS(p, repLen - 1) + return repLen; + } + + if (mainLen < 2 || numAvail <= 2) + return 1; + + { + unsigned len1 = ReadMatchDistances(p, &p->numPairs); + p->longestMatchLen = len1; + + if (len1 >= 2) + { + UInt32 newDist = p->matches[(size_t)p->numPairs - 1]; + if ( (len1 >= mainLen && newDist < mainDist) + || (len1 == mainLen + 1 && !ChangePair(mainDist, newDist)) + || (len1 > mainLen + 1) + || (len1 + 1 >= mainLen && mainLen >= 3 && ChangePair(newDist, mainDist))) + return 1; + } + } + + data = p->matchFinder.GetPointerToCurrentPos(p->matchFinderObj) - 1; + + for (i = 0; i < LZMA_NUM_REPS; i++) + { + unsigned len, limit; + const Byte *data2 = data - p->reps[i]; + if (data[0] != data2[0] || data[1] != data2[1]) + continue; + limit = mainLen - 1; + for (len = 2;; len++) + { + if (len >= limit) + return 1; + if (data[len] != data2[len]) + break; + } + } + + p->backRes = mainDist + LZMA_NUM_REPS; + if (mainLen != 2) + { + MOVE_POS(p, mainLen - 2) + } + return mainLen; +} + +static void WriteEndMarker(CLzmaEnc *p, unsigned posState) +{ + UInt32 range; + range = p->rc.range; + { + UInt32 ttt, newBound; + CLzmaProb *prob = &p->isMatch[p->state][posState]; + RC_BIT_PRE(&p->rc, prob) + RC_BIT_1(&p->rc, prob) + prob = &p->isRep[p->state]; + RC_BIT_PRE(&p->rc, prob) + RC_BIT_0(&p->rc, prob) + } + p->state = kMatchNextStates[p->state]; + + p->rc.range = range; + LenEnc_Encode(&p->lenProbs, &p->rc, 0, posState); + range = p->rc.range; + + { + // RcTree_Encode_PosSlot(&p->rc, p->posSlotEncoder[0], (1 << kNumPosSlotBits) - 1); + CLzmaProb *probs = p->posSlotEncoder[0]; + unsigned m = 1; + do + { + UInt32 ttt, newBound; + RC_BIT_PRE(p, probs + m) + RC_BIT_1(&p->rc, probs + m); + m = (m << 1) + 1; + } + while (m < (1 << kNumPosSlotBits)); + } + { + // RangeEnc_EncodeDirectBits(&p->rc, ((UInt32)1 << (30 - kNumAlignBits)) - 1, 30 - kNumAlignBits); UInt32 range = p->range; + unsigned numBits = 30 - kNumAlignBits; + do + { + range >>= 1; + p->rc.low += range; + RC_NORM(&p->rc) + } + while (--numBits); + } + + { + // RcTree_ReverseEncode(&p->rc, p->posAlignEncoder, kNumAlignBits, kAlignMask); + CLzmaProb *probs = p->posAlignEncoder; + unsigned m = 1; + do + { + UInt32 ttt, newBound; + RC_BIT_PRE(p, probs + m) + RC_BIT_1(&p->rc, probs + m); + m = (m << 1) + 1; + } + while (m < kAlignTableSize); + } + p->rc.range = range; +} + +static SRes CheckErrors(CLzmaEnc *p) +{ + if (p->result != SZ_OK) + return p->result; + if (p->rc.res != SZ_OK) + p->result = SZ_ERROR_WRITE; + if (p->matchFinderBase.result != SZ_OK) + p->result = SZ_ERROR_READ; + if (p->result != SZ_OK) + p->finished = True; + return p->result; +} + +MY_NO_INLINE static SRes Flush(CLzmaEnc *p, UInt32 nowPos) +{ + /* ReleaseMFStream(); */ + p->finished = True; + if (p->writeEndMark) + WriteEndMarker(p, nowPos & p->pbMask); + RangeEnc_FlushData(&p->rc); + RangeEnc_FlushStream(&p->rc); + return CheckErrors(p); +} + +MY_NO_INLINE static void FillAlignPrices(CLzmaEnc *p) +{ + unsigned i; + const CProbPrice *ProbPrices = p->ProbPrices; + const CLzmaProb *probs = p->posAlignEncoder; + // p->alignPriceCount = 0; + for (i = 0; i < kAlignTableSize / 2; i++) + { + UInt32 price = 0; + unsigned sym = i; + unsigned m = 1; + unsigned bit; + UInt32 prob; + bit = sym & 1; sym >>= 1; price += GET_PRICEa(probs[m], bit); m = (m << 1) + bit; + bit = sym & 1; sym >>= 1; price += GET_PRICEa(probs[m], bit); m = (m << 1) + bit; + bit = sym & 1; sym >>= 1; price += GET_PRICEa(probs[m], bit); m = (m << 1) + bit; + prob = probs[m]; + p->alignPrices[i ] = price + GET_PRICEa_0(prob); + p->alignPrices[i + 8] = price + GET_PRICEa_1(prob); + // p->alignPrices[i] = RcTree_ReverseGetPrice(p->posAlignEncoder, kNumAlignBits, i, p->ProbPrices); + } +} + +MY_NO_INLINE static void FillDistancesPrices(CLzmaEnc *p) +{ + // int y; for (y = 0; y < 100; y++) { + + UInt32 tempPrices[kNumFullDistances]; + unsigned i, lps; + + const CProbPrice *ProbPrices = p->ProbPrices; + p->matchPriceCount = 0; + + for (i = kStartPosModelIndex / 2; i < kNumFullDistances / 2; i++) + { + unsigned posSlot = GetPosSlot1(i); + unsigned footerBits = (posSlot >> 1) - 1; + unsigned base = ((2 | (posSlot & 1)) << footerBits); + const CLzmaProb *probs = p->posEncoders + (size_t)base * 2; + // tempPrices[i] = RcTree_ReverseGetPrice(p->posEncoders + base, footerBits, i - base, p->ProbPrices); + UInt32 price = 0; + unsigned m = 1; + unsigned sym = i; + unsigned offset = (unsigned)1 << footerBits; + base += i; + + if (footerBits) + do + { + unsigned bit = sym & 1; + sym >>= 1; + price += GET_PRICEa(probs[m], bit); + m = (m << 1) + bit; + } + while (--footerBits); + + { + unsigned prob = probs[m]; + tempPrices[base ] = price + GET_PRICEa_0(prob); + tempPrices[base + offset] = price + GET_PRICEa_1(prob); + } + } + + for (lps = 0; lps < kNumLenToPosStates; lps++) + { + unsigned slot; + unsigned distTableSize2 = (p->distTableSize + 1) >> 1; + UInt32 *posSlotPrices = p->posSlotPrices[lps]; + const CLzmaProb *probs = p->posSlotEncoder[lps]; + + for (slot = 0; slot < distTableSize2; slot++) + { + // posSlotPrices[slot] = RcTree_GetPrice(encoder, kNumPosSlotBits, slot, p->ProbPrices); + UInt32 price; + unsigned bit; + unsigned sym = slot + (1 << (kNumPosSlotBits - 1)); + unsigned prob; + bit = sym & 1; sym >>= 1; price = GET_PRICEa(probs[sym], bit); + bit = sym & 1; sym >>= 1; price += GET_PRICEa(probs[sym], bit); + bit = sym & 1; sym >>= 1; price += GET_PRICEa(probs[sym], bit); + bit = sym & 1; sym >>= 1; price += GET_PRICEa(probs[sym], bit); + bit = sym & 1; sym >>= 1; price += GET_PRICEa(probs[sym], bit); + prob = probs[(size_t)slot + (1 << (kNumPosSlotBits - 1))]; + posSlotPrices[(size_t)slot * 2 ] = price + GET_PRICEa_0(prob); + posSlotPrices[(size_t)slot * 2 + 1] = price + GET_PRICEa_1(prob); + } + + { + UInt32 delta = ((UInt32)((kEndPosModelIndex / 2 - 1) - kNumAlignBits) << kNumBitPriceShiftBits); + for (slot = kEndPosModelIndex / 2; slot < distTableSize2; slot++) + { + posSlotPrices[(size_t)slot * 2 ] += delta; + posSlotPrices[(size_t)slot * 2 + 1] += delta; + delta += ((UInt32)1 << kNumBitPriceShiftBits); + } + } + + { + UInt32 *dp = p->distancesPrices[lps]; + + dp[0] = posSlotPrices[0]; + dp[1] = posSlotPrices[1]; + dp[2] = posSlotPrices[2]; + dp[3] = posSlotPrices[3]; + + for (i = 4; i < kNumFullDistances; i += 2) + { + UInt32 slotPrice = posSlotPrices[GetPosSlot1(i)]; + dp[i ] = slotPrice + tempPrices[i]; + dp[i + 1] = slotPrice + tempPrices[i + 1]; + } + } + } + // } +} + +void LzmaEnc_Construct(CLzmaEnc *p) +{ + RangeEnc_Construct(&p->rc); + MatchFinder_Construct(&p->matchFinderBase); + + { + CLzmaEncProps props; + LzmaEncProps_Init(&props); + LzmaEnc_SetProps(p, &props); + } + + #ifndef LZMA_LOG_BSR + LzmaEnc_FastPosInit(p->g_FastPos); + #endif + + LzmaEnc_InitPriceTables(p->ProbPrices); + p->litProbs = NULL; + p->saveState.litProbs = NULL; + +} + +CLzmaEncHandle LzmaEnc_Create(ISzAllocPtr alloc) +{ + void *p; + p = ISzAlloc_Alloc(alloc, sizeof(CLzmaEnc)); + if (p) + LzmaEnc_Construct((CLzmaEnc *)p); + return p; +} + +void LzmaEnc_FreeLits(CLzmaEnc *p, ISzAllocPtr alloc) +{ + ISzAlloc_Free(alloc, p->litProbs); + ISzAlloc_Free(alloc, p->saveState.litProbs); + p->litProbs = NULL; + p->saveState.litProbs = NULL; +} + +void LzmaEnc_Destruct(CLzmaEnc *p, ISzAllocPtr alloc, ISzAllocPtr allocBig) +{ + MatchFinder_Free(&p->matchFinderBase, allocBig); + LzmaEnc_FreeLits(p, alloc); + RangeEnc_Free(&p->rc, alloc); +} + +void LzmaEnc_Destroy(CLzmaEncHandle p, ISzAllocPtr alloc, ISzAllocPtr allocBig) +{ + LzmaEnc_Destruct((CLzmaEnc *)p, alloc, allocBig); + ISzAlloc_Free(alloc, p); +} + +static SRes LzmaEnc_CodeOneBlock(CLzmaEnc *p, UInt32 maxPackSize, UInt32 maxUnpackSize) +{ + UInt32 nowPos32, startPos32; + if (p->needInit) + { + p->matchFinder.Init(p->matchFinderObj); + p->needInit = 0; + } + + if (p->finished) + return p->result; + RINOK(CheckErrors(p)); + + nowPos32 = (UInt32)p->nowPos64; + startPos32 = nowPos32; + + if (p->nowPos64 == 0) + { + unsigned numPairs; + Byte curByte; + if (p->matchFinder.GetNumAvailableBytes(p->matchFinderObj) == 0) + return Flush(p, nowPos32); + ReadMatchDistances(p, &numPairs); + RangeEnc_EncodeBit_0(&p->rc, &p->isMatch[kState_Start][0]); + // p->state = kLiteralNextStates[p->state]; + curByte = *(p->matchFinder.GetPointerToCurrentPos(p->matchFinderObj) - p->additionalOffset); + LitEnc_Encode(&p->rc, p->litProbs, curByte); + p->additionalOffset--; + nowPos32++; + } + + if (p->matchFinder.GetNumAvailableBytes(p->matchFinderObj) != 0) + + for (;;) + { + UInt32 dist; + unsigned len, posState; + UInt32 range, ttt, newBound; + CLzmaProb *probs; + + if (p->fastMode) + len = GetOptimumFast(p); + else + { + unsigned oci = p->optCur; + if (p->optEnd == oci) + len = GetOptimum(p, nowPos32); + else + { + const COptimal *opt = &p->opt[oci]; + len = opt->len; + p->backRes = opt->dist; + p->optCur = oci + 1; + } + } + + posState = (unsigned)nowPos32 & p->pbMask; + range = p->rc.range; + probs = &p->isMatch[p->state][posState]; + + RC_BIT_PRE(&p->rc, probs) + + dist = p->backRes; + + #ifdef SHOW_STAT2 + printf("\n pos = %6X, len = %3u pos = %6u", nowPos32, len, dist); + #endif + + if (dist == MARK_LIT) + { + Byte curByte; + const Byte *data; + unsigned state; + + RC_BIT_0(&p->rc, probs); + p->rc.range = range; + data = p->matchFinder.GetPointerToCurrentPos(p->matchFinderObj) - p->additionalOffset; + probs = LIT_PROBS(nowPos32, *(data - 1)); + curByte = *data; + state = p->state; + p->state = kLiteralNextStates[state]; + if (IsLitState(state)) + LitEnc_Encode(&p->rc, probs, curByte); + else + LitEnc_EncodeMatched(&p->rc, probs, curByte, *(data - p->reps[0])); + } + else + { + RC_BIT_1(&p->rc, probs); + probs = &p->isRep[p->state]; + RC_BIT_PRE(&p->rc, probs) + + if (dist < LZMA_NUM_REPS) + { + RC_BIT_1(&p->rc, probs); + probs = &p->isRepG0[p->state]; + RC_BIT_PRE(&p->rc, probs) + if (dist == 0) + { + RC_BIT_0(&p->rc, probs); + probs = &p->isRep0Long[p->state][posState]; + RC_BIT_PRE(&p->rc, probs) + if (len != 1) + { + RC_BIT_1_BASE(&p->rc, probs); + } + else + { + RC_BIT_0_BASE(&p->rc, probs); + p->state = kShortRepNextStates[p->state]; + } + } + else + { + RC_BIT_1(&p->rc, probs); + probs = &p->isRepG1[p->state]; + RC_BIT_PRE(&p->rc, probs) + if (dist == 1) + { + RC_BIT_0_BASE(&p->rc, probs); + dist = p->reps[1]; + } + else + { + RC_BIT_1(&p->rc, probs); + probs = &p->isRepG2[p->state]; + RC_BIT_PRE(&p->rc, probs) + if (dist == 2) + { + RC_BIT_0_BASE(&p->rc, probs); + dist = p->reps[2]; + } + else + { + RC_BIT_1_BASE(&p->rc, probs); + dist = p->reps[3]; + p->reps[3] = p->reps[2]; + } + p->reps[2] = p->reps[1]; + } + p->reps[1] = p->reps[0]; + p->reps[0] = dist; + } + + RC_NORM(&p->rc) + + p->rc.range = range; + + if (len != 1) + { + LenEnc_Encode(&p->repLenProbs, &p->rc, len - LZMA_MATCH_LEN_MIN, posState); + --p->repLenEncCounter; + p->state = kRepNextStates[p->state]; + } + } + else + { + unsigned posSlot; + RC_BIT_0(&p->rc, probs); + p->rc.range = range; + p->state = kMatchNextStates[p->state]; + + LenEnc_Encode(&p->lenProbs, &p->rc, len - LZMA_MATCH_LEN_MIN, posState); + // --p->lenEnc.counter; + + dist -= LZMA_NUM_REPS; + p->reps[3] = p->reps[2]; + p->reps[2] = p->reps[1]; + p->reps[1] = p->reps[0]; + p->reps[0] = dist + 1; + + p->matchPriceCount++; + GetPosSlot(dist, posSlot); + // RcTree_Encode_PosSlot(&p->rc, p->posSlotEncoder[GetLenToPosState(len)], posSlot); + { + UInt32 sym = (UInt32)posSlot + (1 << kNumPosSlotBits); + range = p->rc.range; + probs = p->posSlotEncoder[GetLenToPosState(len)]; + do + { + CLzmaProb *prob = probs + (sym >> kNumPosSlotBits); + UInt32 bit = (sym >> (kNumPosSlotBits - 1)) & 1; + sym <<= 1; + RC_BIT(&p->rc, prob, bit); + } + while (sym < (1 << kNumPosSlotBits * 2)); + p->rc.range = range; + } + + if (dist >= kStartPosModelIndex) + { + unsigned footerBits = ((posSlot >> 1) - 1); + + if (dist < kNumFullDistances) + { + unsigned base = ((2 | (posSlot & 1)) << footerBits); + RcTree_ReverseEncode(&p->rc, p->posEncoders + base, footerBits, (unsigned)(dist /* - base */)); + } + else + { + UInt32 pos2 = (dist | 0xF) << (32 - footerBits); + range = p->rc.range; + // RangeEnc_EncodeDirectBits(&p->rc, posReduced >> kNumAlignBits, footerBits - kNumAlignBits); + /* + do + { + range >>= 1; + p->rc.low += range & (0 - ((dist >> --footerBits) & 1)); + RC_NORM(&p->rc) + } + while (footerBits > kNumAlignBits); + */ + do + { + range >>= 1; + p->rc.low += range & (0 - (pos2 >> 31)); + pos2 += pos2; + RC_NORM(&p->rc) + } + while (pos2 != 0xF0000000); + + // RcTree_ReverseEncode(&p->rc, p->posAlignEncoder, kNumAlignBits, posReduced & kAlignMask); + + { + unsigned m = 1; + unsigned bit; + bit = dist & 1; dist >>= 1; RC_BIT(&p->rc, p->posAlignEncoder + m, bit); m = (m << 1) + bit; + bit = dist & 1; dist >>= 1; RC_BIT(&p->rc, p->posAlignEncoder + m, bit); m = (m << 1) + bit; + bit = dist & 1; dist >>= 1; RC_BIT(&p->rc, p->posAlignEncoder + m, bit); m = (m << 1) + bit; + bit = dist & 1; RC_BIT(&p->rc, p->posAlignEncoder + m, bit); + p->rc.range = range; + // p->alignPriceCount++; + } + } + } + } + } + + nowPos32 += (UInt32)len; + p->additionalOffset -= len; + + if (p->additionalOffset == 0) + { + UInt32 processed; + + if (!p->fastMode) + { + /* + if (p->alignPriceCount >= 16) // kAlignTableSize + FillAlignPrices(p); + if (p->matchPriceCount >= 128) + FillDistancesPrices(p); + if (p->lenEnc.counter <= 0) + LenPriceEnc_UpdateTables(&p->lenEnc, 1 << p->pb, &p->lenProbs, p->ProbPrices); + */ + if (p->matchPriceCount >= 64) + { + FillAlignPrices(p); + // { int y; for (y = 0; y < 100; y++) { + FillDistancesPrices(p); + // }} + LenPriceEnc_UpdateTables(&p->lenEnc, 1 << p->pb, &p->lenProbs, p->ProbPrices); + } + if (p->repLenEncCounter <= 0) + { + p->repLenEncCounter = REP_LEN_COUNT; + LenPriceEnc_UpdateTables(&p->repLenEnc, 1 << p->pb, &p->repLenProbs, p->ProbPrices); + } + } + + if (p->matchFinder.GetNumAvailableBytes(p->matchFinderObj) == 0) + break; + processed = nowPos32 - startPos32; + + if (maxPackSize) + { + if (processed + kNumOpts + 300 >= maxUnpackSize + || RangeEnc_GetProcessed_sizet(&p->rc) + kPackReserve >= maxPackSize) + break; + } + else if (processed >= (1 << 17)) + { + p->nowPos64 += nowPos32 - startPos32; + return CheckErrors(p); + } + } + } + + p->nowPos64 += nowPos32 - startPos32; + return Flush(p, nowPos32); +} + +#define kBigHashDicLimit ((UInt32)1 << 24) + +static SRes LzmaEnc_Alloc(CLzmaEnc *p, UInt32 keepWindowSize, ISzAllocPtr alloc, ISzAllocPtr allocBig) +{ + UInt32 beforeSize = kNumOpts; + if (!RangeEnc_Alloc(&p->rc, alloc)) + return SZ_ERROR_MEM; + + { + unsigned lclp = p->lc + p->lp; + if (!p->litProbs || !p->saveState.litProbs || p->lclp != lclp) + { + LzmaEnc_FreeLits(p, alloc); + p->litProbs = (CLzmaProb *)ISzAlloc_Alloc(alloc, ((UInt32)0x300 << lclp) * sizeof(CLzmaProb)); + p->saveState.litProbs = (CLzmaProb *)ISzAlloc_Alloc(alloc, ((UInt32)0x300 << lclp) * sizeof(CLzmaProb)); + if (!p->litProbs || !p->saveState.litProbs) + { + LzmaEnc_FreeLits(p, alloc); + return SZ_ERROR_MEM; + } + p->lclp = lclp; + } + } + + p->matchFinderBase.bigHash = (Byte)(p->dictSize > kBigHashDicLimit ? 1 : 0); + + if (beforeSize + p->dictSize < keepWindowSize) + beforeSize = keepWindowSize - p->dictSize; + + { + if (!MatchFinder_Create(&p->matchFinderBase, p->dictSize, beforeSize, p->numFastBytes, LZMA_MATCH_LEN_MAX, allocBig)) + return SZ_ERROR_MEM; + p->matchFinderObj = &p->matchFinderBase; + MatchFinder_CreateVTable(&p->matchFinderBase, &p->matchFinder); + } + + return SZ_OK; +} + +void LzmaEnc_Init(CLzmaEnc *p) +{ + unsigned i; + p->state = 0; + p->reps[0] = + p->reps[1] = + p->reps[2] = + p->reps[3] = 1; + + RangeEnc_Init(&p->rc); + + for (i = 0; i < (1 << kNumAlignBits); i++) + p->posAlignEncoder[i] = kProbInitValue; + + for (i = 0; i < kNumStates; i++) + { + unsigned j; + for (j = 0; j < LZMA_NUM_PB_STATES_MAX; j++) + { + p->isMatch[i][j] = kProbInitValue; + p->isRep0Long[i][j] = kProbInitValue; + } + p->isRep[i] = kProbInitValue; + p->isRepG0[i] = kProbInitValue; + p->isRepG1[i] = kProbInitValue; + p->isRepG2[i] = kProbInitValue; + } + + { + for (i = 0; i < kNumLenToPosStates; i++) + { + CLzmaProb *probs = p->posSlotEncoder[i]; + unsigned j; + for (j = 0; j < (1 << kNumPosSlotBits); j++) + probs[j] = kProbInitValue; + } + } + { + for (i = 0; i < kNumFullDistances; i++) + p->posEncoders[i] = kProbInitValue; + } + + { + UInt32 num = (UInt32)0x300 << (p->lp + p->lc); + UInt32 k; + CLzmaProb *probs = p->litProbs; + for (k = 0; k < num; k++) + probs[k] = kProbInitValue; + } + + LenEnc_Init(&p->lenProbs); + LenEnc_Init(&p->repLenProbs); + + p->optEnd = 0; + p->optCur = 0; + + { + for (i = 0; i < kNumOpts; i++) + p->opt[i].price = kInfinityPrice; + } + + p->additionalOffset = 0; + + p->pbMask = (1 << p->pb) - 1; + p->lpMask = ((UInt32)0x100 << p->lp) - ((unsigned)0x100 >> p->lc); +} + +void LzmaEnc_InitPrices(CLzmaEnc *p) +{ + if (!p->fastMode) + { + FillDistancesPrices(p); + FillAlignPrices(p); + } + + p->lenEnc.tableSize = + p->repLenEnc.tableSize = + p->numFastBytes + 1 - LZMA_MATCH_LEN_MIN; + + p->repLenEncCounter = REP_LEN_COUNT; + + LenPriceEnc_UpdateTables(&p->lenEnc, 1 << p->pb, &p->lenProbs, p->ProbPrices); + LenPriceEnc_UpdateTables(&p->repLenEnc, 1 << p->pb, &p->repLenProbs, p->ProbPrices); +} + +static SRes LzmaEnc_AllocAndInit(CLzmaEnc *p, UInt32 keepWindowSize, ISzAllocPtr alloc, ISzAllocPtr allocBig) +{ + unsigned i; + for (i = kEndPosModelIndex / 2; i < kDicLogSizeMax; i++) + if (p->dictSize <= ((UInt32)1 << i)) + break; + p->distTableSize = i * 2; + + p->finished = False; + p->result = SZ_OK; + RINOK(LzmaEnc_Alloc(p, keepWindowSize, alloc, allocBig)); + LzmaEnc_Init(p); + LzmaEnc_InitPrices(p); + p->nowPos64 = 0; + return SZ_OK; +} + +static SRes LzmaEnc_Prepare(CLzmaEncHandle pp, ISeqOutStream *outStream, ISeqInStream *inStream, + ISzAllocPtr alloc, ISzAllocPtr allocBig) +{ + CLzmaEnc *p = (CLzmaEnc *)pp; + p->matchFinderBase.stream = inStream; + p->needInit = 1; + p->rc.outStream = outStream; + return LzmaEnc_AllocAndInit(p, 0, alloc, allocBig); +} + +SRes LzmaEnc_PrepareForLzma2(CLzmaEncHandle pp, + ISeqInStream *inStream, UInt32 keepWindowSize, + ISzAllocPtr alloc, ISzAllocPtr allocBig) +{ + CLzmaEnc *p = (CLzmaEnc *)pp; + p->matchFinderBase.stream = inStream; + p->needInit = 1; + return LzmaEnc_AllocAndInit(p, keepWindowSize, alloc, allocBig); +} + +static void LzmaEnc_SetInputBuf(CLzmaEnc *p, const Byte *src, SizeT srcLen) +{ + p->matchFinderBase.directInput = 1; + p->matchFinderBase.bufferBase = (Byte *)src; + p->matchFinderBase.directInputRem = srcLen; +} + +SRes LzmaEnc_MemPrepare(CLzmaEncHandle pp, const Byte *src, SizeT srcLen, + UInt32 keepWindowSize, ISzAllocPtr alloc, ISzAllocPtr allocBig) +{ + CLzmaEnc *p = (CLzmaEnc *)pp; + LzmaEnc_SetInputBuf(p, src, srcLen); + p->needInit = 1; + + LzmaEnc_SetDataSize(pp, srcLen); + return LzmaEnc_AllocAndInit(p, keepWindowSize, alloc, allocBig); +} + +void LzmaEnc_Finish(CLzmaEncHandle pp) +{ + UNUSED_VAR(pp) +} + +typedef struct +{ + ISeqOutStream vt; + Byte *data; + SizeT rem; + BoolInt overflow; +} CLzmaEnc_SeqOutStreamBuf; + +static size_t SeqOutStreamBuf_Write(const ISeqOutStream *pp, const void *data, size_t size) +{ + CLzmaEnc_SeqOutStreamBuf *p = CONTAINER_FROM_VTBL(pp, CLzmaEnc_SeqOutStreamBuf, vt); + if (p->rem < size) + { + size = p->rem; + p->overflow = True; + } + memcpy(p->data, data, size); + p->rem -= size; + p->data += size; + return size; +} + +UInt32 LzmaEnc_GetNumAvailableBytes(CLzmaEncHandle pp) +{ + const CLzmaEnc *p = (CLzmaEnc *)pp; + return p->matchFinder.GetNumAvailableBytes(p->matchFinderObj); +} + +const Byte *LzmaEnc_GetCurBuf(CLzmaEncHandle pp) +{ + const CLzmaEnc *p = (CLzmaEnc *)pp; + return p->matchFinder.GetPointerToCurrentPos(p->matchFinderObj) - p->additionalOffset; +} + +SRes LzmaEnc_CodeOneMemBlock(CLzmaEncHandle pp, BoolInt reInit, + Byte *dest, size_t *destLen, UInt32 desiredPackSize, UInt32 *unpackSize) +{ + CLzmaEnc *p = (CLzmaEnc *)pp; + UInt64 nowPos64; + SRes res; + CLzmaEnc_SeqOutStreamBuf outStream; + + outStream.vt.Write = SeqOutStreamBuf_Write; + outStream.data = dest; + outStream.rem = *destLen; + outStream.overflow = False; + + p->writeEndMark = False; + p->finished = False; + p->result = SZ_OK; + + if (reInit) + LzmaEnc_Init(p); + LzmaEnc_InitPrices(p); + + nowPos64 = p->nowPos64; + RangeEnc_Init(&p->rc); + p->rc.outStream = &outStream.vt; + + if (desiredPackSize == 0) + return SZ_ERROR_OUTPUT_EOF; + + res = LzmaEnc_CodeOneBlock(p, desiredPackSize, *unpackSize); + + *unpackSize = (UInt32)(p->nowPos64 - nowPos64); + *destLen -= outStream.rem; + if (outStream.overflow) + return SZ_ERROR_OUTPUT_EOF; + + return res; +} + +static SRes LzmaEnc_Encode2(CLzmaEnc *p, ICompressProgress *progress) +{ + SRes res = SZ_OK; + + for (;;) + { + res = LzmaEnc_CodeOneBlock(p, 0, 0); + if (res != SZ_OK || p->finished) + break; + if (progress) + { + res = ICompressProgress_Progress(progress, p->nowPos64, RangeEnc_GetProcessed(&p->rc)); + if (res != SZ_OK) + { + res = SZ_ERROR_PROGRESS; + break; + } + } + } + + LzmaEnc_Finish(p); + + /* + if (res == SZ_OK && !Inline_MatchFinder_IsFinishedOK(&p->matchFinderBase)) + res = SZ_ERROR_FAIL; + } + */ + + return res; +} + +SRes LzmaEnc_Encode(CLzmaEncHandle pp, ISeqOutStream *outStream, ISeqInStream *inStream, ICompressProgress *progress, + ISzAllocPtr alloc, ISzAllocPtr allocBig) +{ + RINOK(LzmaEnc_Prepare(pp, outStream, inStream, alloc, allocBig)); + return LzmaEnc_Encode2((CLzmaEnc *)pp, progress); +} + +SRes LzmaEnc_WriteProperties(CLzmaEncHandle pp, Byte *props, SizeT *size) +{ + CLzmaEnc *p = (CLzmaEnc *)pp; + unsigned i; + UInt32 dictSize = p->dictSize; + if (*size < LZMA_PROPS_SIZE) + return SZ_ERROR_PARAM; + *size = LZMA_PROPS_SIZE; + props[0] = (Byte)((p->pb * 5 + p->lp) * 9 + p->lc); + + if (dictSize >= ((UInt32)1 << 22)) + { + UInt32 kDictMask = ((UInt32)1 << 20) - 1; + if (dictSize < (UInt32)0xFFFFFFFF - kDictMask) + dictSize = (dictSize + kDictMask) & ~kDictMask; + } + else for (i = 11; i <= 30; i++) + { + if (dictSize <= ((UInt32)2 << i)) { dictSize = (2 << i); break; } + if (dictSize <= ((UInt32)3 << i)) { dictSize = (3 << i); break; } + } + + for (i = 0; i < 4; i++) + props[1 + i] = (Byte)(dictSize >> (8 * i)); + return SZ_OK; +} + +unsigned LzmaEnc_IsWriteEndMark(CLzmaEncHandle pp) +{ + return ((CLzmaEnc *)pp)->writeEndMark; +} + +SRes LzmaEnc_MemEncode(CLzmaEncHandle pp, Byte *dest, SizeT *destLen, const Byte *src, SizeT srcLen, + int writeEndMark, ICompressProgress *progress, ISzAllocPtr alloc, ISzAllocPtr allocBig) +{ + SRes res; + CLzmaEnc *p = (CLzmaEnc *)pp; + + CLzmaEnc_SeqOutStreamBuf outStream; + + outStream.vt.Write = SeqOutStreamBuf_Write; + outStream.data = dest; + outStream.rem = *destLen; + outStream.overflow = False; + + p->writeEndMark = writeEndMark; + p->rc.outStream = &outStream.vt; + + res = LzmaEnc_MemPrepare(pp, src, srcLen, 0, alloc, allocBig); + + if (res == SZ_OK) + { + res = LzmaEnc_Encode2(p, progress); + if (res == SZ_OK && p->nowPos64 != srcLen) + res = SZ_ERROR_FAIL; + } + + *destLen -= outStream.rem; + if (outStream.overflow) + return SZ_ERROR_OUTPUT_EOF; + return res; +} + +SRes LzmaEncode(Byte *dest, SizeT *destLen, const Byte *src, SizeT srcLen, + const CLzmaEncProps *props, Byte *propsEncoded, SizeT *propsSize, int writeEndMark, + ICompressProgress *progress, ISzAllocPtr alloc, ISzAllocPtr allocBig) +{ + CLzmaEnc *p = (CLzmaEnc *)LzmaEnc_Create(alloc); + SRes res; + if (!p) + return SZ_ERROR_MEM; + + res = LzmaEnc_SetProps(p, props); + if (res == SZ_OK) + { + res = LzmaEnc_WriteProperties(p, propsEncoded, propsSize); + if (res == SZ_OK) + res = LzmaEnc_MemEncode(p, dest, destLen, src, srcLen, + writeEndMark, progress, alloc, allocBig); + } + + LzmaEnc_Destroy(p, alloc, allocBig); + return res; +} + +/*** End of inlined file: LzmaEnc.c ***/ + + +/*** Start of inlined file: LzFind.c ***/ +//#include + + +/*** Start of inlined file: LzHash.h ***/ +#ifndef __LZ_HASH_H +#define __LZ_HASH_H + +#define kHash2Size (1 << 10) +#define kHash3Size (1 << 16) +#define kHash4Size (1 << 20) + +#define kFix3HashSize (kHash2Size) +#define kFix4HashSize (kHash2Size + kHash3Size) +#define kFix5HashSize (kHash2Size + kHash3Size + kHash4Size) + +#define HASH2_CALC hv = cur[0] | ((UInt32)cur[1] << 8); + +#define HASH3_CALC { \ + UInt32 temp = p->crc[cur[0]] ^ cur[1]; \ + h2 = temp & (kHash2Size - 1); \ + hv = (temp ^ ((UInt32)cur[2] << 8)) & p->hashMask; } + +#define HASH4_CALC { \ + UInt32 temp = p->crc[cur[0]] ^ cur[1]; \ + h2 = temp & (kHash2Size - 1); \ + temp ^= ((UInt32)cur[2] << 8); \ + h3 = temp & (kHash3Size - 1); \ + hv = (temp ^ (p->crc[cur[3]] << 5)) & p->hashMask; } + +#define HASH5_CALC { \ + UInt32 temp = p->crc[cur[0]] ^ cur[1]; \ + h2 = temp & (kHash2Size - 1); \ + temp ^= ((UInt32)cur[2] << 8); \ + h3 = temp & (kHash3Size - 1); \ + temp ^= (p->crc[cur[3]] << 5); \ + h4 = temp & (kHash4Size - 1); \ + hv = (temp ^ (p->crc[cur[4]] << 3)) & p->hashMask; } + +/* #define HASH_ZIP_CALC hv = ((cur[0] | ((UInt32)cur[1] << 8)) ^ p->crc[cur[2]]) & 0xFFFF; */ +#define HASH_ZIP_CALC hv = ((cur[2] | ((UInt32)cur[0] << 8)) ^ p->crc[cur[1]]) & 0xFFFF; + +#define MT_HASH2_CALC \ + h2 = (p->crc[cur[0]] ^ cur[1]) & (kHash2Size - 1); + +#define MT_HASH3_CALC { \ + UInt32 temp = p->crc[cur[0]] ^ cur[1]; \ + h2 = temp & (kHash2Size - 1); \ + h3 = (temp ^ ((UInt32)cur[2] << 8)) & (kHash3Size - 1); } + +#define MT_HASH4_CALC { \ + UInt32 temp = p->crc[cur[0]] ^ cur[1]; \ + h2 = temp & (kHash2Size - 1); \ + temp ^= ((UInt32)cur[2] << 8); \ + h3 = temp & (kHash3Size - 1); \ + h4 = (temp ^ (p->crc[cur[3]] << 5)) & (kHash4Size - 1); } + +#endif + +/*** End of inlined file: LzHash.h ***/ + +#define kEmptyHashValue 0 +#define kMaxValForNormalize ((UInt32)0xFFFFFFFF) +#define kNormalizeStepMin (1 << 10) /* it must be power of 2 */ +#define kNormalizeMask (~(UInt32)(kNormalizeStepMin - 1)) +#define kMaxHistorySize ((UInt32)7 << 29) + +#define kStartMaxLen 3 + +static void LzInWindow_Free(CMatchFinder *p, ISzAllocPtr alloc) +{ + if (!p->directInput) + { + ISzAlloc_Free(alloc, p->bufferBase); + p->bufferBase = NULL; + } +} + +/* keepSizeBefore + keepSizeAfter + keepSizeReserv must be < 4G) */ + +static int LzInWindow_Create(CMatchFinder *p, UInt32 keepSizeReserv, ISzAllocPtr alloc) +{ + UInt32 blockSize = p->keepSizeBefore + p->keepSizeAfter + keepSizeReserv; + if (p->directInput) + { + p->blockSize = blockSize; + return 1; + } + if (!p->bufferBase || p->blockSize != blockSize) + { + LzInWindow_Free(p, alloc); + p->blockSize = blockSize; + p->bufferBase = (Byte *)ISzAlloc_Alloc(alloc, (size_t)blockSize); + } + return (p->bufferBase != NULL); +} + +Byte *MatchFinder_GetPointerToCurrentPos(CMatchFinder *p) { return p->buffer; } + +UInt32 MatchFinder_GetNumAvailableBytes(CMatchFinder *p) { return p->streamPos - p->pos; } + +void MatchFinder_ReduceOffsets(CMatchFinder *p, UInt32 subValue) +{ + p->posLimit -= subValue; + p->pos -= subValue; + p->streamPos -= subValue; +} + +static void MatchFinder_ReadBlock(CMatchFinder *p) +{ + if (p->streamEndWasReached || p->result != SZ_OK) + return; + + /* We use (p->streamPos - p->pos) value. (p->streamPos < p->pos) is allowed. */ + + if (p->directInput) + { + UInt32 curSize = 0xFFFFFFFF - (p->streamPos - p->pos); + if (curSize > p->directInputRem) + curSize = (UInt32)p->directInputRem; + p->directInputRem -= curSize; + p->streamPos += curSize; + if (p->directInputRem == 0) + p->streamEndWasReached = 1; + return; + } + + for (;;) + { + Byte *dest = p->buffer + (p->streamPos - p->pos); + size_t size = (p->bufferBase + p->blockSize - dest); + if (size == 0) + return; + + p->result = ISeqInStream_Read(p->stream, dest, &size); + if (p->result != SZ_OK) + return; + if (size == 0) + { + p->streamEndWasReached = 1; + return; + } + p->streamPos += (UInt32)size; + if (p->streamPos - p->pos > p->keepSizeAfter) + return; + } +} + +void MatchFinder_MoveBlock(CMatchFinder *p) +{ + memmove(p->bufferBase, + p->buffer - p->keepSizeBefore, + (size_t)(p->streamPos - p->pos) + p->keepSizeBefore); + p->buffer = p->bufferBase + p->keepSizeBefore; +} + +int MatchFinder_NeedMove(CMatchFinder *p) +{ + if (p->directInput) + return 0; + /* if (p->streamEndWasReached) return 0; */ + return ((size_t)(p->bufferBase + p->blockSize - p->buffer) <= p->keepSizeAfter); +} + +void MatchFinder_ReadIfRequired(CMatchFinder *p) +{ + if (p->streamEndWasReached) + return; + if (p->keepSizeAfter >= p->streamPos - p->pos) + MatchFinder_ReadBlock(p); +} + +static void MatchFinder_CheckAndMoveAndRead(CMatchFinder *p) +{ + if (MatchFinder_NeedMove(p)) + MatchFinder_MoveBlock(p); + MatchFinder_ReadBlock(p); +} + +static void MatchFinder_SetDefaultSettings(CMatchFinder *p) +{ + p->cutValue = 32; + p->btMode = 1; + p->numHashBytes = 4; + p->bigHash = 0; +} + +#define kCrcPoly 0xEDB88320 + +void MatchFinder_Construct(CMatchFinder *p) +{ + unsigned i; + p->bufferBase = NULL; + p->directInput = 0; + p->hash = NULL; + p->expectedDataSize = (UInt64)(Int64)-1; + MatchFinder_SetDefaultSettings(p); + + for (i = 0; i < 256; i++) + { + UInt32 r = (UInt32)i; + unsigned j; + for (j = 0; j < 8; j++) + r = (r >> 1) ^ (kCrcPoly & ((UInt32)0 - (r & 1))); + p->crc[i] = r; + } +} + +static void MatchFinder_FreeThisClassMemory(CMatchFinder *p, ISzAllocPtr alloc) +{ + ISzAlloc_Free(alloc, p->hash); + p->hash = NULL; +} + +void MatchFinder_Free(CMatchFinder *p, ISzAllocPtr alloc) +{ + MatchFinder_FreeThisClassMemory(p, alloc); + LzInWindow_Free(p, alloc); +} + +static CLzRef* AllocRefs(size_t num, ISzAllocPtr alloc) +{ + size_t sizeInBytes = (size_t)num * sizeof(CLzRef); + if (sizeInBytes / sizeof(CLzRef) != num) + return NULL; + return (CLzRef *)ISzAlloc_Alloc(alloc, sizeInBytes); +} + +int MatchFinder_Create(CMatchFinder *p, UInt32 historySize, + UInt32 keepAddBufferBefore, UInt32 matchMaxLen, UInt32 keepAddBufferAfter, + ISzAllocPtr alloc) +{ + UInt32 sizeReserv; + + if (historySize > kMaxHistorySize) + { + MatchFinder_Free(p, alloc); + return 0; + } + + sizeReserv = historySize >> 1; + if (historySize >= ((UInt32)3 << 30)) sizeReserv = historySize >> 3; + else if (historySize >= ((UInt32)2 << 30)) sizeReserv = historySize >> 2; + + sizeReserv += (keepAddBufferBefore + matchMaxLen + keepAddBufferAfter) / 2 + (1 << 19); + + p->keepSizeBefore = historySize + keepAddBufferBefore + 1; + p->keepSizeAfter = matchMaxLen + keepAddBufferAfter; + + /* we need one additional byte, since we use MoveBlock after pos++ and before dictionary using */ + + if (LzInWindow_Create(p, sizeReserv, alloc)) + { + UInt32 newCyclicBufferSize = historySize + 1; + UInt32 hs; + p->matchMaxLen = matchMaxLen; + { + p->fixedHashSize = 0; + if (p->numHashBytes == 2) + hs = (1 << 16) - 1; + else + { + hs = historySize; + if (hs > p->expectedDataSize) + hs = (UInt32)p->expectedDataSize; + if (hs != 0) + hs--; + hs |= (hs >> 1); + hs |= (hs >> 2); + hs |= (hs >> 4); + hs |= (hs >> 8); + hs >>= 1; + hs |= 0xFFFF; /* don't change it! It's required for Deflate */ + if (hs > (1 << 24)) + { + if (p->numHashBytes == 3) + hs = (1 << 24) - 1; + else + hs >>= 1; + /* if (bigHash) mode, GetHeads4b() in LzFindMt.c needs (hs >= ((1 << 24) - 1))) */ + } + } + p->hashMask = hs; + hs++; + if (p->numHashBytes > 2) p->fixedHashSize += kHash2Size; + if (p->numHashBytes > 3) p->fixedHashSize += kHash3Size; + if (p->numHashBytes > 4) p->fixedHashSize += kHash4Size; + hs += p->fixedHashSize; + } + + { + size_t newSize; + size_t numSons; + p->historySize = historySize; + p->hashSizeSum = hs; + p->cyclicBufferSize = newCyclicBufferSize; + + numSons = newCyclicBufferSize; + if (p->btMode) + numSons <<= 1; + newSize = hs + numSons; + + if (p->hash && p->numRefs == newSize) + return 1; + + MatchFinder_FreeThisClassMemory(p, alloc); + p->numRefs = newSize; + p->hash = AllocRefs(newSize, alloc); + + if (p->hash) + { + p->son = p->hash + p->hashSizeSum; + return 1; + } + } + } + + MatchFinder_Free(p, alloc); + return 0; +} + +static void MatchFinder_SetLimits(CMatchFinder *p) +{ + UInt32 limit = kMaxValForNormalize - p->pos; + UInt32 limit2 = p->cyclicBufferSize - p->cyclicBufferPos; + + if (limit2 < limit) + limit = limit2; + limit2 = p->streamPos - p->pos; + + if (limit2 <= p->keepSizeAfter) + { + if (limit2 > 0) + limit2 = 1; + } + else + limit2 -= p->keepSizeAfter; + + if (limit2 < limit) + limit = limit2; + + { + UInt32 lenLimit = p->streamPos - p->pos; + if (lenLimit > p->matchMaxLen) + lenLimit = p->matchMaxLen; + p->lenLimit = lenLimit; + } + p->posLimit = p->pos + limit; +} + +void MatchFinder_Init_LowHash(CMatchFinder *p) +{ + size_t i; + CLzRef *items = p->hash; + size_t numItems = p->fixedHashSize; + for (i = 0; i < numItems; i++) + items[i] = kEmptyHashValue; +} + +void MatchFinder_Init_HighHash(CMatchFinder *p) +{ + size_t i; + CLzRef *items = p->hash + p->fixedHashSize; + size_t numItems = (size_t)p->hashMask + 1; + for (i = 0; i < numItems; i++) + items[i] = kEmptyHashValue; +} + +void MatchFinder_Init_3(CMatchFinder *p, int readData) +{ + p->cyclicBufferPos = 0; + p->buffer = p->bufferBase; + p->pos = + p->streamPos = p->cyclicBufferSize; + p->result = SZ_OK; + p->streamEndWasReached = 0; + + if (readData) + MatchFinder_ReadBlock(p); + + MatchFinder_SetLimits(p); +} + +void MatchFinder_Init(CMatchFinder *p) +{ + MatchFinder_Init_HighHash(p); + MatchFinder_Init_LowHash(p); + MatchFinder_Init_3(p, True); +} + +static UInt32 MatchFinder_GetSubValue(CMatchFinder *p) +{ + return (p->pos - p->historySize - 1) & kNormalizeMask; +} + +void MatchFinder_Normalize3(UInt32 subValue, CLzRef *items, size_t numItems) +{ + size_t i; + for (i = 0; i < numItems; i++) + { + UInt32 value = items[i]; + if (value <= subValue) + value = kEmptyHashValue; + else + value -= subValue; + items[i] = value; + } +} + +static void MatchFinder_Normalize(CMatchFinder *p) +{ + UInt32 subValue = MatchFinder_GetSubValue(p); + MatchFinder_Normalize3(subValue, p->hash, p->numRefs); + MatchFinder_ReduceOffsets(p, subValue); +} + +MY_NO_INLINE +static void MatchFinder_CheckLimits(CMatchFinder *p) +{ + if (p->pos == kMaxValForNormalize) + MatchFinder_Normalize(p); + if (!p->streamEndWasReached && p->keepSizeAfter == p->streamPos - p->pos) + MatchFinder_CheckAndMoveAndRead(p); + if (p->cyclicBufferPos == p->cyclicBufferSize) + p->cyclicBufferPos = 0; + MatchFinder_SetLimits(p); +} + +/* + (lenLimit > maxLen) +*/ +MY_FORCE_INLINE +static UInt32 * Hc_GetMatchesSpec(unsigned lenLimit, UInt32 curMatch, UInt32 pos, const Byte *cur, CLzRef *son, + UInt32 _cyclicBufferPos, UInt32 _cyclicBufferSize, UInt32 cutValue, + UInt32 *distances, unsigned maxLen) +{ + /* + son[_cyclicBufferPos] = curMatch; + for (;;) + { + UInt32 delta = pos - curMatch; + if (cutValue-- == 0 || delta >= _cyclicBufferSize) + return distances; + { + const Byte *pb = cur - delta; + curMatch = son[_cyclicBufferPos - delta + ((delta > _cyclicBufferPos) ? _cyclicBufferSize : 0)]; + if (pb[maxLen] == cur[maxLen] && *pb == *cur) + { + UInt32 len = 0; + while (++len != lenLimit) + if (pb[len] != cur[len]) + break; + if (maxLen < len) + { + maxLen = len; + *distances++ = len; + *distances++ = delta - 1; + if (len == lenLimit) + return distances; + } + } + } + } + */ + + const Byte *lim = cur + lenLimit; + son[_cyclicBufferPos] = curMatch; + do + { + UInt32 delta = pos - curMatch; + if (delta >= _cyclicBufferSize) + break; + { + ptrdiff_t diff; + curMatch = son[_cyclicBufferPos - delta + ((delta > _cyclicBufferPos) ? _cyclicBufferSize : 0)]; + diff = (ptrdiff_t)0 - delta; + if (cur[maxLen] == cur[maxLen + diff]) + { + const Byte *c = cur; + while (*c == c[diff]) + { + if (++c == lim) + { + distances[0] = (UInt32)(lim - cur); + distances[1] = delta - 1; + return distances + 2; + } + } + { + unsigned len = (unsigned)(c - cur); + if (maxLen < len) + { + maxLen = len; + distances[0] = (UInt32)len; + distances[1] = delta - 1; + distances += 2; + } + } + } + } + } + while (--cutValue); + + return distances; +} + +MY_FORCE_INLINE +UInt32 * GetMatchesSpec1(UInt32 lenLimit, UInt32 curMatch, UInt32 pos, const Byte *cur, CLzRef *son, + UInt32 _cyclicBufferPos, UInt32 _cyclicBufferSize, UInt32 cutValue, + UInt32 *distances, UInt32 maxLen) +{ + CLzRef *ptr0 = son + ((size_t)_cyclicBufferPos << 1) + 1; + CLzRef *ptr1 = son + ((size_t)_cyclicBufferPos << 1); + unsigned len0 = 0, len1 = 0; + for (;;) + { + UInt32 delta = pos - curMatch; + if (cutValue-- == 0 || delta >= _cyclicBufferSize) + { + *ptr0 = *ptr1 = kEmptyHashValue; + return distances; + } + { + CLzRef *pair = son + ((size_t)(_cyclicBufferPos - delta + ((delta > _cyclicBufferPos) ? _cyclicBufferSize : 0)) << 1); + const Byte *pb = cur - delta; + unsigned len = (len0 < len1 ? len0 : len1); + UInt32 pair0 = pair[0]; + if (pb[len] == cur[len]) + { + if (++len != lenLimit && pb[len] == cur[len]) + while (++len != lenLimit) + if (pb[len] != cur[len]) + break; + if (maxLen < len) + { + maxLen = (UInt32)len; + *distances++ = (UInt32)len; + *distances++ = delta - 1; + if (len == lenLimit) + { + *ptr1 = pair0; + *ptr0 = pair[1]; + return distances; + } + } + } + if (pb[len] < cur[len]) + { + *ptr1 = curMatch; + ptr1 = pair + 1; + curMatch = *ptr1; + len1 = len; + } + else + { + *ptr0 = curMatch; + ptr0 = pair; + curMatch = *ptr0; + len0 = len; + } + } + } +} + +static void SkipMatchesSpec(UInt32 lenLimit, UInt32 curMatch, UInt32 pos, const Byte *cur, CLzRef *son, + UInt32 _cyclicBufferPos, UInt32 _cyclicBufferSize, UInt32 cutValue) +{ + CLzRef *ptr0 = son + ((size_t)_cyclicBufferPos << 1) + 1; + CLzRef *ptr1 = son + ((size_t)_cyclicBufferPos << 1); + unsigned len0 = 0, len1 = 0; + for (;;) + { + UInt32 delta = pos - curMatch; + if (cutValue-- == 0 || delta >= _cyclicBufferSize) + { + *ptr0 = *ptr1 = kEmptyHashValue; + return; + } + { + CLzRef *pair = son + ((size_t)(_cyclicBufferPos - delta + ((delta > _cyclicBufferPos) ? _cyclicBufferSize : 0)) << 1); + const Byte *pb = cur - delta; + unsigned len = (len0 < len1 ? len0 : len1); + if (pb[len] == cur[len]) + { + while (++len != lenLimit) + if (pb[len] != cur[len]) + break; + { + if (len == lenLimit) + { + *ptr1 = pair[0]; + *ptr0 = pair[1]; + return; + } + } + } + if (pb[len] < cur[len]) + { + *ptr1 = curMatch; + ptr1 = pair + 1; + curMatch = *ptr1; + len1 = len; + } + else + { + *ptr0 = curMatch; + ptr0 = pair; + curMatch = *ptr0; + len0 = len; + } + } + } +} + +#undef MOVE_POS +#define MOVE_POS \ + ++p->cyclicBufferPos; \ + p->buffer++; \ + if (++p->pos == p->posLimit) MatchFinder_CheckLimits(p); + +#define MOVE_POS_RET MOVE_POS return (UInt32)offset; + +static void MatchFinder_MovePos(CMatchFinder *p) { MOVE_POS; } + +#define GET_MATCHES_HEADER2(minLen, ret_op) \ + unsigned lenLimit; UInt32 hv; const Byte *cur; UInt32 curMatch; \ + lenLimit = (unsigned)p->lenLimit; { if (lenLimit < minLen) { MatchFinder_MovePos(p); ret_op; }} \ + cur = p->buffer; + +#define GET_MATCHES_HEADER(minLen) GET_MATCHES_HEADER2(minLen, return 0) +#define SKIP_HEADER(minLen) GET_MATCHES_HEADER2(minLen, continue) + +#define MF_PARAMS(p) p->pos, p->buffer, p->son, p->cyclicBufferPos, p->cyclicBufferSize, p->cutValue + +#define GET_MATCHES_FOOTER(offset, maxLen) \ + offset = (unsigned)(GetMatchesSpec1((UInt32)lenLimit, curMatch, MF_PARAMS(p), \ + distances + offset, (UInt32)maxLen) - distances); MOVE_POS_RET; + +#define SKIP_FOOTER \ + SkipMatchesSpec((UInt32)lenLimit, curMatch, MF_PARAMS(p)); MOVE_POS; + +#define UPDATE_maxLen { \ + ptrdiff_t diff = (ptrdiff_t)0 - d2; \ + const Byte *c = cur + maxLen; \ + const Byte *lim = cur + lenLimit; \ + for (; c != lim; c++) if (*(c + diff) != *c) break; \ + maxLen = (unsigned)(c - cur); } + +static UInt32 Bt2_MatchFinder_GetMatches(CMatchFinder *p, UInt32 *distances) +{ + unsigned offset; + GET_MATCHES_HEADER(2) + HASH2_CALC; + curMatch = p->hash[hv]; + p->hash[hv] = p->pos; + offset = 0; + GET_MATCHES_FOOTER(offset, 1) +} + +UInt32 Bt3Zip_MatchFinder_GetMatches(CMatchFinder *p, UInt32 *distances) +{ + unsigned offset; + GET_MATCHES_HEADER(3) + HASH_ZIP_CALC; + curMatch = p->hash[hv]; + p->hash[hv] = p->pos; + offset = 0; + GET_MATCHES_FOOTER(offset, 2) +} + +static UInt32 Bt3_MatchFinder_GetMatches(CMatchFinder *p, UInt32 *distances) +{ + UInt32 h2, d2, pos; + unsigned maxLen, offset; + UInt32 *hash; + GET_MATCHES_HEADER(3) + + HASH3_CALC; + + hash = p->hash; + pos = p->pos; + + d2 = pos - hash[h2]; + + curMatch = (hash + kFix3HashSize)[hv]; + + hash[h2] = pos; + (hash + kFix3HashSize)[hv] = pos; + + maxLen = 2; + offset = 0; + + if (d2 < p->cyclicBufferSize && *(cur - d2) == *cur) + { + UPDATE_maxLen + distances[0] = (UInt32)maxLen; + distances[1] = d2 - 1; + offset = 2; + if (maxLen == lenLimit) + { + SkipMatchesSpec((UInt32)lenLimit, curMatch, MF_PARAMS(p)); + MOVE_POS_RET; + } + } + + GET_MATCHES_FOOTER(offset, maxLen) +} + +static UInt32 Bt4_MatchFinder_GetMatches(CMatchFinder *p, UInt32 *distances) +{ + UInt32 h2, h3, d2, d3, pos; + unsigned maxLen, offset; + UInt32 *hash; + GET_MATCHES_HEADER(4) + + HASH4_CALC; + + hash = p->hash; + pos = p->pos; + + d2 = pos - hash [h2]; + d3 = pos - (hash + kFix3HashSize)[h3]; + + curMatch = (hash + kFix4HashSize)[hv]; + + hash [h2] = pos; + (hash + kFix3HashSize)[h3] = pos; + (hash + kFix4HashSize)[hv] = pos; + + maxLen = 0; + offset = 0; + + if (d2 < p->cyclicBufferSize && *(cur - d2) == *cur) + { + maxLen = 2; + distances[0] = 2; + distances[1] = d2 - 1; + offset = 2; + } + + if (d2 != d3 && d3 < p->cyclicBufferSize && *(cur - d3) == *cur) + { + maxLen = 3; + distances[(size_t)offset + 1] = d3 - 1; + offset += 2; + d2 = d3; + } + + if (offset != 0) + { + UPDATE_maxLen + distances[(size_t)offset - 2] = (UInt32)maxLen; + if (maxLen == lenLimit) + { + SkipMatchesSpec((UInt32)lenLimit, curMatch, MF_PARAMS(p)); + MOVE_POS_RET; + } + } + + if (maxLen < 3) + maxLen = 3; + + GET_MATCHES_FOOTER(offset, maxLen) +} + +/* +static UInt32 Bt5_MatchFinder_GetMatches(CMatchFinder *p, UInt32 *distances) +{ + UInt32 h2, h3, h4, d2, d3, d4, maxLen, offset, pos; + UInt32 *hash; + GET_MATCHES_HEADER(5) + + HASH5_CALC; + + hash = p->hash; + pos = p->pos; + + d2 = pos - hash [h2]; + d3 = pos - (hash + kFix3HashSize)[h3]; + d4 = pos - (hash + kFix4HashSize)[h4]; + + curMatch = (hash + kFix5HashSize)[hv]; + + hash [h2] = pos; + (hash + kFix3HashSize)[h3] = pos; + (hash + kFix4HashSize)[h4] = pos; + (hash + kFix5HashSize)[hv] = pos; + + maxLen = 0; + offset = 0; + + if (d2 < p->cyclicBufferSize && *(cur - d2) == *cur) + { + distances[0] = maxLen = 2; + distances[1] = d2 - 1; + offset = 2; + if (*(cur - d2 + 2) == cur[2]) + distances[0] = maxLen = 3; + else if (d3 < p->cyclicBufferSize && *(cur - d3) == *cur) + { + distances[2] = maxLen = 3; + distances[3] = d3 - 1; + offset = 4; + d2 = d3; + } + } + else if (d3 < p->cyclicBufferSize && *(cur - d3) == *cur) + { + distances[0] = maxLen = 3; + distances[1] = d3 - 1; + offset = 2; + d2 = d3; + } + + if (d2 != d4 && d4 < p->cyclicBufferSize + && *(cur - d4) == *cur + && *(cur - d4 + 3) == *(cur + 3)) + { + maxLen = 4; + distances[(size_t)offset + 1] = d4 - 1; + offset += 2; + d2 = d4; + } + + if (offset != 0) + { + UPDATE_maxLen + distances[(size_t)offset - 2] = maxLen; + if (maxLen == lenLimit) + { + SkipMatchesSpec(lenLimit, curMatch, MF_PARAMS(p)); + MOVE_POS_RET; + } + } + + if (maxLen < 4) + maxLen = 4; + + GET_MATCHES_FOOTER(offset, maxLen) +} +*/ + +static UInt32 Hc4_MatchFinder_GetMatches(CMatchFinder *p, UInt32 *distances) +{ + UInt32 h2, h3, d2, d3, pos; + unsigned maxLen, offset; + UInt32 *hash; + GET_MATCHES_HEADER(4) + + HASH4_CALC; + + hash = p->hash; + pos = p->pos; + + d2 = pos - hash [h2]; + d3 = pos - (hash + kFix3HashSize)[h3]; + curMatch = (hash + kFix4HashSize)[hv]; + + hash [h2] = pos; + (hash + kFix3HashSize)[h3] = pos; + (hash + kFix4HashSize)[hv] = pos; + + maxLen = 0; + offset = 0; + + if (d2 < p->cyclicBufferSize && *(cur - d2) == *cur) + { + maxLen = 2; + distances[0] = 2; + distances[1] = d2 - 1; + offset = 2; + } + + if (d2 != d3 && d3 < p->cyclicBufferSize && *(cur - d3) == *cur) + { + maxLen = 3; + distances[(size_t)offset + 1] = d3 - 1; + offset += 2; + d2 = d3; + } + + if (offset != 0) + { + UPDATE_maxLen + distances[(size_t)offset - 2] = (UInt32)maxLen; + if (maxLen == lenLimit) + { + p->son[p->cyclicBufferPos] = curMatch; + MOVE_POS_RET; + } + } + + if (maxLen < 3) + maxLen = 3; + + offset = (unsigned)(Hc_GetMatchesSpec(lenLimit, curMatch, MF_PARAMS(p), + distances + offset, maxLen) - (distances)); + MOVE_POS_RET +} + +/* +static UInt32 Hc5_MatchFinder_GetMatches(CMatchFinder *p, UInt32 *distances) +{ + UInt32 h2, h3, h4, d2, d3, d4, maxLen, offset, pos + UInt32 *hash; + GET_MATCHES_HEADER(5) + + HASH5_CALC; + + hash = p->hash; + pos = p->pos; + + d2 = pos - hash [h2]; + d3 = pos - (hash + kFix3HashSize)[h3]; + d4 = pos - (hash + kFix4HashSize)[h4]; + + curMatch = (hash + kFix5HashSize)[hv]; + + hash [h2] = pos; + (hash + kFix3HashSize)[h3] = pos; + (hash + kFix4HashSize)[h4] = pos; + (hash + kFix5HashSize)[hv] = pos; + + maxLen = 0; + offset = 0; + + if (d2 < p->cyclicBufferSize && *(cur - d2) == *cur) + { + distances[0] = maxLen = 2; + distances[1] = d2 - 1; + offset = 2; + if (*(cur - d2 + 2) == cur[2]) + distances[0] = maxLen = 3; + else if (d3 < p->cyclicBufferSize && *(cur - d3) == *cur) + { + distances[2] = maxLen = 3; + distances[3] = d3 - 1; + offset = 4; + d2 = d3; + } + } + else if (d3 < p->cyclicBufferSize && *(cur - d3) == *cur) + { + distances[0] = maxLen = 3; + distances[1] = d3 - 1; + offset = 2; + d2 = d3; + } + + if (d2 != d4 && d4 < p->cyclicBufferSize + && *(cur - d4) == *cur + && *(cur - d4 + 3) == *(cur + 3)) + { + maxLen = 4; + distances[(size_t)offset + 1] = d4 - 1; + offset += 2; + d2 = d4; + } + + if (offset != 0) + { + UPDATE_maxLen + distances[(size_t)offset - 2] = maxLen; + if (maxLen == lenLimit) + { + p->son[p->cyclicBufferPos] = curMatch; + MOVE_POS_RET; + } + } + + if (maxLen < 4) + maxLen = 4; + + offset = (UInt32)(Hc_GetMatchesSpec(lenLimit, curMatch, MF_PARAMS(p), + distances + offset, maxLen) - (distances)); + MOVE_POS_RET +} +*/ + +UInt32 Hc3Zip_MatchFinder_GetMatches(CMatchFinder *p, UInt32 *distances) +{ + unsigned offset; + GET_MATCHES_HEADER(3) + HASH_ZIP_CALC; + curMatch = p->hash[hv]; + p->hash[hv] = p->pos; + offset = (unsigned)(Hc_GetMatchesSpec(lenLimit, curMatch, MF_PARAMS(p), + distances, 2) - (distances)); + MOVE_POS_RET +} + +static void Bt2_MatchFinder_Skip(CMatchFinder *p, UInt32 num) +{ + do + { + SKIP_HEADER(2) + HASH2_CALC; + curMatch = p->hash[hv]; + p->hash[hv] = p->pos; + SKIP_FOOTER + } + while (--num != 0); +} + +void Bt3Zip_MatchFinder_Skip(CMatchFinder *p, UInt32 num) +{ + do + { + SKIP_HEADER(3) + HASH_ZIP_CALC; + curMatch = p->hash[hv]; + p->hash[hv] = p->pos; + SKIP_FOOTER + } + while (--num != 0); +} + +static void Bt3_MatchFinder_Skip(CMatchFinder *p, UInt32 num) +{ + do + { + UInt32 h2; + UInt32 *hash; + SKIP_HEADER(3) + HASH3_CALC; + hash = p->hash; + curMatch = (hash + kFix3HashSize)[hv]; + hash[h2] = + (hash + kFix3HashSize)[hv] = p->pos; + SKIP_FOOTER + } + while (--num != 0); +} + +static void Bt4_MatchFinder_Skip(CMatchFinder *p, UInt32 num) +{ + do + { + UInt32 h2, h3; + UInt32 *hash; + SKIP_HEADER(4) + HASH4_CALC; + hash = p->hash; + curMatch = (hash + kFix4HashSize)[hv]; + hash [h2] = + (hash + kFix3HashSize)[h3] = + (hash + kFix4HashSize)[hv] = p->pos; + SKIP_FOOTER + } + while (--num != 0); +} + +/* +static void Bt5_MatchFinder_Skip(CMatchFinder *p, UInt32 num) +{ + do + { + UInt32 h2, h3, h4; + UInt32 *hash; + SKIP_HEADER(5) + HASH5_CALC; + hash = p->hash; + curMatch = (hash + kFix5HashSize)[hv]; + hash [h2] = + (hash + kFix3HashSize)[h3] = + (hash + kFix4HashSize)[h4] = + (hash + kFix5HashSize)[hv] = p->pos; + SKIP_FOOTER + } + while (--num != 0); +} +*/ + +static void Hc4_MatchFinder_Skip(CMatchFinder *p, UInt32 num) +{ + do + { + UInt32 h2, h3; + UInt32 *hash; + SKIP_HEADER(4) + HASH4_CALC; + hash = p->hash; + curMatch = (hash + kFix4HashSize)[hv]; + hash [h2] = + (hash + kFix3HashSize)[h3] = + (hash + kFix4HashSize)[hv] = p->pos; + p->son[p->cyclicBufferPos] = curMatch; + MOVE_POS + } + while (--num != 0); +} + +/* +static void Hc5_MatchFinder_Skip(CMatchFinder *p, UInt32 num) +{ + do + { + UInt32 h2, h3, h4; + UInt32 *hash; + SKIP_HEADER(5) + HASH5_CALC; + hash = p->hash; + curMatch = hash + kFix5HashSize)[hv]; + hash [h2] = + (hash + kFix3HashSize)[h3] = + (hash + kFix4HashSize)[h4] = + (hash + kFix5HashSize)[hv] = p->pos; + p->son[p->cyclicBufferPos] = curMatch; + MOVE_POS + } + while (--num != 0); +} +*/ + +void Hc3Zip_MatchFinder_Skip(CMatchFinder *p, UInt32 num) +{ + do + { + SKIP_HEADER(3) + HASH_ZIP_CALC; + curMatch = p->hash[hv]; + p->hash[hv] = p->pos; + p->son[p->cyclicBufferPos] = curMatch; + MOVE_POS + } + while (--num != 0); +} + +void MatchFinder_CreateVTable(CMatchFinder *p, IMatchFinder *vTable) +{ + vTable->Init = (Mf_Init_Func)MatchFinder_Init; + vTable->GetNumAvailableBytes = (Mf_GetNumAvailableBytes_Func)MatchFinder_GetNumAvailableBytes; + vTable->GetPointerToCurrentPos = (Mf_GetPointerToCurrentPos_Func)MatchFinder_GetPointerToCurrentPos; + if (!p->btMode) + { + /* if (p->numHashBytes <= 4) */ + { + vTable->GetMatches = (Mf_GetMatches_Func)Hc4_MatchFinder_GetMatches; + vTable->Skip = (Mf_Skip_Func)Hc4_MatchFinder_Skip; + } + /* + else + { + vTable->GetMatches = (Mf_GetMatches_Func)Hc5_MatchFinder_GetMatches; + vTable->Skip = (Mf_Skip_Func)Hc5_MatchFinder_Skip; + } + */ + } + else if (p->numHashBytes == 2) + { + vTable->GetMatches = (Mf_GetMatches_Func)Bt2_MatchFinder_GetMatches; + vTable->Skip = (Mf_Skip_Func)Bt2_MatchFinder_Skip; + } + else if (p->numHashBytes == 3) + { + vTable->GetMatches = (Mf_GetMatches_Func)Bt3_MatchFinder_GetMatches; + vTable->Skip = (Mf_Skip_Func)Bt3_MatchFinder_Skip; + } + else /* if (p->numHashBytes == 4) */ + { + vTable->GetMatches = (Mf_GetMatches_Func)Bt4_MatchFinder_GetMatches; + vTable->Skip = (Mf_Skip_Func)Bt4_MatchFinder_Skip; + } + /* + else + { + vTable->GetMatches = (Mf_GetMatches_Func)Bt5_MatchFinder_GetMatches; + vTable->Skip = (Mf_Skip_Func)Bt5_MatchFinder_Skip; + } + */ +} + +/*** End of inlined file: LzFind.c ***/ + + +/*** Start of inlined file: LzmaLib.c ***/ +MY_STDAPI LzmaCompress(unsigned char *dest, size_t *destLen, const unsigned char *src, size_t srcLen, + unsigned char *outProps, size_t *outPropsSize, + int level, /* 0 <= level <= 9, default = 5 */ + unsigned dictSize, /* use (1 << N) or (3 << N). 4 KB < dictSize <= 128 MB */ + int lc, /* 0 <= lc <= 8, default = 3 */ + int lp, /* 0 <= lp <= 4, default = 0 */ + int pb, /* 0 <= pb <= 4, default = 2 */ + int fb, /* 5 <= fb <= 273, default = 32 */ + int numThreads /* 1 or 2, default = 2 */ +) +{ + CLzmaEncProps props; + LzmaEncProps_Init(&props); + props.level = level; + props.dictSize = dictSize; + props.lc = lc; + props.lp = lp; + props.pb = pb; + props.fb = fb; + props.numThreads = numThreads; + + return LzmaEncode(dest, destLen, src, srcLen, &props, outProps, outPropsSize, 0, + NULL, &g_Alloc, &g_Alloc); +} + +MY_STDAPI LzmaUncompress(unsigned char *dest, size_t *destLen, const unsigned char *src, size_t *srcLen, + const unsigned char *props, size_t propsSize) +{ + ELzmaStatus status; + return LzmaDecode(dest, destLen, src, srcLen, props, (unsigned)propsSize, LZMA_FINISH_ANY, &status, &g_Alloc); +} + +/*** End of inlined file: LzmaLib.c ***/ + + #endif //POCKETLZMA_LZMA_C_DEFINE + } +} + +/*** Start of inlined file: pocketlzma_common.hpp ***/ +// +// Created by robin on 29.12.2020. +// + +#ifndef POCKETLZMA_POCKETLZMA_COMMON_HPP +#define POCKETLZMA_POCKETLZMA_COMMON_HPP +#include +#include +#include + +#include +#include +#include + +namespace plz +{ + const uint8_t PLZ_MAX_LEVEL {9}; + const uint32_t PLZ_MIN_DICTIONARY_SIZE {1 << 8}; + const uint32_t PLZ_MAX_DICTIONARY_SIZE {1 << 30}; + const uint8_t PLZ_MAX_LITERAL_CONTEXT_BITS {8}; + const uint8_t PLZ_MAX_LITERAL_POSITION_BITS {4}; + const uint8_t PLZ_MAX_POSITION_BITS {4}; + const uint16_t PLZ_MIN_FAST_BYTES {5}; + const uint16_t PLZ_MAX_FAST_BYTES {273}; + + const uint32_t PLZ_BUFFER_SIZE {1 << 16}; //65536 bytes + const uint8_t PLZ_MINIMUM_LZMA_SIZE {12}; + + enum class StatusCode + { + + Ok = SZ_OK, //0 + ErrorData = SZ_ERROR_DATA, //1 + ErrorMem = SZ_ERROR_MEM, //2 + ErrorCrc = SZ_ERROR_CRC, //3 + ErrorUnsupported = SZ_ERROR_UNSUPPORTED, //4 + ErrorParam = SZ_ERROR_PARAM, //5 + ErrorInputEof = SZ_ERROR_INPUT_EOF, //6 + ErrorOutputEof = SZ_ERROR_OUTPUT_EOF, //7 + ErrorRead = SZ_ERROR_READ, //8 + ErrorWrite = SZ_ERROR_WRITE, //9 + ErrorProgress = SZ_ERROR_PROGRESS, //10 + ErrorFail = SZ_ERROR_FAIL, //11 + ErrorThread = SZ_ERROR_THREAD, //12 + ErrorArchive = SZ_ERROR_ARCHIVE, //16 + ErrorNoArchive = SZ_ERROR_NO_ARCHIVE, //17 + + /*! When you attempt to decompress something that cannot be LZMA data */ + InvalidLzmaData = 100, + + /*! If you get this, you probably have attempted to decompress corrupted/garbage LZMA data */ + UndefinedError = 999 + }; + + enum class Preset : uint8_t + { + Default = 0, + Fastest = 1, + Fast = 2, + GoodCompression = 3, + BestCompression = 4 + }; +} +#endif //POCKETLZMA_POCKETLZMA_COMMON_HPP + +/*** End of inlined file: pocketlzma_common.hpp ***/ + + + +/*** Start of inlined file: Settings.hpp ***/ +// +// Created by robin on 29.12.2020. +// + +#ifndef POCKETLZMA_SETTINGS_HPP +#define POCKETLZMA_SETTINGS_HPP + +namespace plz +{ + class Settings + { + public: + + Settings() = default; + inline explicit Settings(Preset preset); + /*! + * Makes sure no values are out of valid range + */ + inline void validate(); + + inline void usePreset(Preset preset); + + /*! + * level - compression level: 0 <= level <= 9; + + * level dictSize algo fb + * 0: 16 KB 0 32 + * 1: 64 KB 0 32 + * 2: 256 KB 0 32 + * 3: 1 MB 0 32 + * 4: 4 MB 0 32 + * 5: 16 MB 1 32 + * 6: 32 MB 1 32 + * 7+: 64 MB 1 64 + * + * The default value for "level" is 5. + * + * algo = 0 means fast method + * algo = 1 means normal method + */ + uint8_t level {5}; + + /*! + * The dictionary size in bytes. The maximum value is + * 128 MB = (1 << 27) bytes for 32-bit version + * 1 GB = (1 << 30) bytes for 64-bit version + * The default value is 16 MB = (1 << 24) bytes. + * It's recommended to use the dictionary that is larger than 4 KB and + * that can be calculated as (1 << N) or (3 << N) sizes. + * + * pocketlzma has a lower limit of (1 << 8) (256 bytes) + */ + uint32_t dictionarySize {1 << 24}; + + /*! + * lc - The number of literal context bits (high bits of previous literal). + * It can be in the range from 0 to 8. The default value is 3. + * Sometimes lc=4 gives the gain for big files. + */ + uint8_t literalContextBits {3}; + + /*! + * lp - The number of literal pos bits (low bits of current position for literals). + * It can be in the range from 0 to 4. The default value is 0. + * The lp switch is intended for periodical data when the period is equal to 2^lp. + * For example, for 32-bit (4 bytes) periodical data you can use lp=2. Often it's + * better to set lc=0, if you change lp switch. + */ + uint8_t literalPositionBits {0}; + + /*! + * pb - The number of pos bits (low bits of current position). + It can be in the range from 0 to 4. The default value is 2. + The pb switch is intended for periodical data when the period is equal 2^pb. + */ + uint8_t positionBits {2}; + + /*! + * fb - Word size (the number of fast bytes). + * It can be in the range from 5 to 273. The default value is 32. + * Usually, a big number gives a little bit better compression ratio and + * slower compression process. + */ + uint16_t fastBytes {32}; + }; + + Settings::Settings(Preset preset) + { + usePreset(preset); + } + + void Settings::validate() + { + if(level > PLZ_MAX_LEVEL) + level = PLZ_MAX_LEVEL; + + if(dictionarySize < PLZ_MIN_DICTIONARY_SIZE) + dictionarySize = PLZ_MIN_DICTIONARY_SIZE; + else if(dictionarySize > PLZ_MAX_DICTIONARY_SIZE) + dictionarySize = PLZ_MAX_DICTIONARY_SIZE; + + if(literalContextBits > PLZ_MAX_LITERAL_CONTEXT_BITS) + literalContextBits = PLZ_MAX_LITERAL_CONTEXT_BITS; + + if(literalPositionBits > PLZ_MAX_LITERAL_POSITION_BITS) + literalPositionBits = PLZ_MAX_LITERAL_POSITION_BITS; + + if(positionBits > PLZ_MAX_POSITION_BITS) + positionBits = PLZ_MAX_POSITION_BITS; + + if(fastBytes < PLZ_MIN_FAST_BYTES) + fastBytes = PLZ_MIN_FAST_BYTES; + else if(fastBytes > PLZ_MAX_FAST_BYTES) + fastBytes = PLZ_MAX_FAST_BYTES; + + } + + void Settings::usePreset(Preset preset) + { + switch(preset) + { + case Preset::Default: + level = 5; + dictionarySize = 1 << 24; + literalContextBits = 3; + literalPositionBits = 0; + positionBits = 2; + fastBytes = 32; + break; + + case Preset::Fastest: + level = 1; + dictionarySize = 1 << 16; + literalContextBits = 4; + literalPositionBits = 0; + positionBits = 2; + fastBytes = 8; + break; + + case Preset::Fast: + level = 4; + dictionarySize = 1 << 22; + literalContextBits = 4; + literalPositionBits = 0; + positionBits = 2; + fastBytes = 16; + break; + + case Preset::GoodCompression: + level = 7; + dictionarySize = 1 << 26; + literalContextBits = 3; + literalPositionBits = 0; + positionBits = 2; + fastBytes = 64; + break; + + case Preset::BestCompression: + level = 9; + dictionarySize = 1 << 27; + literalContextBits = 3; + literalPositionBits = 0; + positionBits = 2; + fastBytes = 128; + break; + + } + } + +} + +#endif //POCKETLZMA_SETTINGS_HPP + +/*** End of inlined file: Settings.hpp ***/ + + +/*** Start of inlined file: FileStatus.hpp ***/ +// +// Created by robin on 29.12.2020. +// + +#ifndef POCKETLZMA_FILESTATUS_HPP +#define POCKETLZMA_FILESTATUS_HPP + +namespace plz +{ + class FileStatus + { + public: + + enum class Code + { + Ok = 0, + FileWriteError = 100, + FileWriteErrorBadBit = 101, + FileWriteErrorFailBit = 102, + + FileReadError = 200, + FileReadErrorBadBit = 201, + FileReadErrorFailBit = 202 + }; + + inline FileStatus() = default; + inline FileStatus(FileStatus::Code status, int code, const std::string &exception, const std::string &category, const std::string &message); + + inline void set(FileStatus::Code status, int code, const std::string &exception, const std::string &category, const std::string &message); + + inline Code status() const; + inline int code() const; + inline const std::string &exception() const; + inline const std::string &category() const; + inline const std::string &message() const; + + private: + Code m_status { Code::Ok }; + int m_code {0}; + std::string m_exception; + std::string m_category; + std::string m_message; + + }; + + FileStatus::FileStatus(FileStatus::Code status, int code, const std::string &exception, const std::string &category, const std::string &message) + { + set(status, code, exception, category, message); + } + + void FileStatus::set(FileStatus::Code status, int code, const std::string &exception, const std::string &category, const std::string &message) + { + m_status = status; + m_code = code; + m_exception = exception; + m_category = category; + m_message = message; + } + + FileStatus::Code FileStatus::status() const + { + return m_status; + } + + int FileStatus::code() const + { + return m_code; + } + + const std::string &FileStatus::exception() const + { + return m_exception; + } + + const std::string &FileStatus::category() const + { + return m_category; + } + + const std::string &FileStatus::message() const + { + return m_message; + } +} + +#endif //POCKETLZMA_FILESTATUS_HPP + +/*** End of inlined file: FileStatus.hpp ***/ + + +/*** Start of inlined file: File.hpp ***/ +// +// Created by robin on 28.12.2020. +// + +#ifndef POCKETLZMA_FILE_HPP +#define POCKETLZMA_FILE_HPP + + +/*** Start of inlined file: MemoryStream.hpp ***/ +// +// Created by robin on 28.12.2020. +// + +#ifndef POCKETLZMA_MEMORYSTREAM_HPP +#define POCKETLZMA_MEMORYSTREAM_HPP + + +/*** Start of inlined file: MemoryBuffer.hpp ***/ +// +// Created by robin on 28.12.2020. +// + +#ifndef POCKETLZMA_MEMORYBUFFER_HPP +#define POCKETLZMA_MEMORYBUFFER_HPP + +#include + +namespace plz +{ + class MemoryBuffer : public std::basic_streambuf { + public: + MemoryBuffer(const uint8_t *p, size_t l) { + setg((char*)p, (char*)p, (char*)p + l); + } + }; +} + +#endif //POCKETLZMA_MEMORYBUFFER_HPP + +/*** End of inlined file: MemoryBuffer.hpp ***/ + +namespace plz +{ + class MemoryStream : public std::istream { + public: + MemoryStream(const uint8_t *p, size_t l) : + std::istream(&m_buffer), + m_buffer(p, l) + { + m_size = l; + rdbuf(&m_buffer); + } + + size_t size() const { return m_size; } + + private: + MemoryBuffer m_buffer; + size_t m_size; + }; +} + +#endif //POCKETLZMA_MEMORYSTREAM_HPP + +/*** End of inlined file: MemoryStream.hpp ***/ + +#include +namespace plz +{ + class File + { + public: + File() = delete; + static inline std::vector FromMemory(const void *data, size_t size); + static inline void FromMemory(const void *data, size_t size, std::vector &output); + + static inline std::vector FromFile(const std::string &path); + static inline FileStatus FromFile(const std::string &path, std::vector &output); + + static inline FileStatus ToFile(const std::string &path, const std::vector &data); + }; + + std::vector File::FromMemory(const void *data, size_t size) + { + std::vector bytes(size); + FromMemory(data, size, bytes); + return bytes; + } + + void File::FromMemory(const void *data, size_t size, std::vector &output) + { + output.resize(size); + plz::MemoryStream mem {(uint8_t *)data, size}; + mem.read((char *)&output[0], size); + } + + std::vector File::FromFile(const std::string &path) + { + std::fstream file; + file = std::fstream(path, std::ios::in | std::ios::binary); + + //Find size + file.seekg(0, std::ios::end); + std::streamoff fileSize = file.tellg(); + file.seekg(0, std::ios::beg); + + std::vector bytes((uint32_t)fileSize); + + file.read((char *)&bytes[0], fileSize); + file.close(); + + return bytes; + } + + FileStatus File::FromFile(const std::string &path, std::vector &output) + { + std::fstream file; + try + { + file = std::fstream(path, std::ios::in | std::ios::binary); + file.exceptions(std::fstream::failbit | std::fstream::badbit); + bool isBad = file.bad(); + bool isFail = file.fail(); + bool isOpen = file.is_open(); + + if(isBad || isFail || !isOpen) + file.close(); + + if(isBad) + return FileStatus(FileStatus::Code::FileWriteErrorBadBit, 0, "", "", ""); + else if(isFail) + return FileStatus(FileStatus::Code::FileWriteErrorFailBit, 0, "", "", ""); + else if(!isOpen) + return FileStatus(FileStatus::Code::FileWriteError, 0, "", "", ""); + + //Find size + file.seekg(0, std::ios::end); + std::streamoff fileSize = file.tellg(); + file.seekg(0, std::ios::beg); + + output.resize((size_t)fileSize); + + file.read((char *) &output[0], fileSize); + file.close(); + + return FileStatus(); + } + catch (const std::fstream::failure &e) + { + if(file.is_open()) + file.close(); + + return FileStatus(FileStatus::Code::FileReadError, e.code().value(), e.what(), e.code().category().name(), e.code().message()); + } + } + + FileStatus File::ToFile(const std::string &path, const std::vector &data) + { + std::fstream file; + file.exceptions(std::fstream::failbit | std::fstream::badbit); + + try + { + file = std::fstream(path, std::ios::out | std::ios::binary); + bool isBad = file.bad(); + bool isFail = file.fail(); + bool isOpen = file.is_open(); + + if(isBad || isFail || !isOpen) + file.close(); + + if(isBad) + return FileStatus(FileStatus::Code::FileWriteErrorBadBit, 0, "", "", ""); + else if(isFail) + return FileStatus(FileStatus::Code::FileWriteErrorFailBit, 0, "", "", ""); + else if(!isOpen) + return FileStatus(FileStatus::Code::FileWriteError, 0, "", "", ""); + + for (const auto &b : data) //b = byte + file << b; + + file.close(); + return FileStatus(); + } + catch (const std::fstream::failure &e) + { + if(file.is_open()) + file.close(); + + return FileStatus(FileStatus::Code::FileWriteError, e.code().value(), e.what(), e.code().category().name(), e.code().message()); + } + } +} + +#endif //POCKETLZMA_FILE_HPP + +/*** End of inlined file: File.hpp ***/ + + +/*** Start of inlined file: pocketlzma_class.hpp ***/ +// +// Created by robin on 29.12.2020. +// + +#ifndef POCKETLZMA_POCKETLZMA_CLASS_HPP +#define POCKETLZMA_POCKETLZMA_CLASS_HPP + +namespace plz +{ + class PocketLzma + { + public: + PocketLzma() = default; + inline explicit PocketLzma(Preset preset); + inline explicit PocketLzma(const Settings &settings) : m_settings {settings} {}; + inline void setSettings(const Settings &settings); + inline void usePreset (Preset preset); + + inline StatusCode compress(const std::vector &input, std::vector &output); + inline StatusCode compress(const uint8_t *input, const size_t inputSize, std::vector &output); + + inline StatusCode decompress(const std::vector &input, std::vector &output); + inline StatusCode decompress(const uint8_t *input, const size_t inputSize, std::vector &output); + + inline StatusCode decompressBuffered(const std::vector &input, std::vector &output, uint32_t bufferSize = PLZ_BUFFER_SIZE); + inline StatusCode decompressBuffered(const uint8_t *input, const size_t inputSize, std::vector &output, uint32_t bufferSize = PLZ_BUFFER_SIZE); + private: + Settings m_settings {}; + }; + + PocketLzma::PocketLzma(Preset preset) + { + usePreset(preset); + } + + /*! + * This is optional. + * PocketLzma uses default values if not set by the user. + * + * If you are a casual user: usePreset() is recommended. + * + * @param settings new settings + */ + void PocketLzma::setSettings(const Settings &settings) + { + m_settings = settings; + } + + /*! + * Set a preset to control compression speed vs ratio. + * + * Recommended: + * For fast compression: Preset::Fast + * For balanced compression: Preset::Default + * For good compression: Preset::GoodCompression + * + * Note: Not used when decompressing. + */ + void PocketLzma::usePreset(Preset preset) + { + m_settings.usePreset(preset); + } + + StatusCode PocketLzma::compress(const std::vector &input, std::vector &output) + { + return compress(&input[0], input.size(), output); + } + + StatusCode PocketLzma::compress(const uint8_t *input, const size_t inputSize, std::vector &output) + { + m_settings.validate(); + size_t propsSize = LZMA_PROPS_SIZE; + uint8_t propsEncoded[LZMA_PROPS_SIZE]; + + size_t outSize = inputSize + (inputSize / 3) + 128; + //uint8_t out[outSize]; + std::unique_ptr out(new uint8_t[outSize]); + + int rc = plz::c::LzmaCompress(&out[0], &outSize, input, inputSize, propsEncoded, &propsSize, m_settings.level, m_settings.dictionarySize, + m_settings.literalContextBits,m_settings.literalPositionBits,m_settings.positionBits,m_settings.fastBytes,1); + + StatusCode status = static_cast(rc); + if(status == StatusCode::Ok) + { + std::vector sizeBits; + for (int i = 0; i < 8; i++) + sizeBits.push_back((inputSize >> (i * 8)) & 0xFF); + + output.insert(output.end(), propsEncoded, propsEncoded + propsSize); // Property header + output.insert(output.end(), sizeBits.begin(), sizeBits.end()); // Add decompress size information + output.insert(output.end(), out.get(), out.get() + outSize); // Data + } + + return status; + } + + /*! + * Decompresses LZMA data. + * + * Will choose the best solution based on whether lzma file size in header is known or not. + * In cases where the file size is unknown, decompressBuffered() will be called + * + * @param input The input compressed data + * @param output The decompressed data + * @return Status for the decompression process + */ + StatusCode PocketLzma::decompress(const std::vector &input, std::vector &output) + { + return decompress(&input[0], input.size(), output); + } + + /*! + * Decompresses LZMA data. + * + * Will choose the best solution based on whether lzma file size in header is known or not. + * In cases where the file size is unknown, decompressBuffered() will be called + * + * @param input The input compressed data + * @param output The decompressed data + * @return Status for the decompression process + */ + StatusCode PocketLzma::decompress(const uint8_t *input, const size_t inputSize, std::vector &output) + { + if(inputSize <= PLZ_MINIMUM_LZMA_SIZE) + return StatusCode::InvalidLzmaData; + + size_t propsSize = LZMA_PROPS_SIZE + 8; //header + decompress_size + size_t size = 0; + bool sizeInfoMissing = true; //True until proven otherwise + + for (int i = 0; i < 8; i++) + { + uint8_t value = input[LZMA_PROPS_SIZE + i]; + if(value != 0xFF) + sizeInfoMissing = false; + + size |= (value << (i * 8)); + } + + if(sizeInfoMissing) + return decompressBuffered(input, inputSize, output, PLZ_BUFFER_SIZE); //StatusCode::MissingSizeInfoInHeader; + + size_t outSize = size; + //uint8_t out[size]; + std::unique_ptr out(new uint8_t[size]); + + size_t inSize = inputSize - propsSize; + int rc = plz::c::LzmaUncompress(&out[0], &outSize, &input[propsSize], &inSize, &input[0], LZMA_PROPS_SIZE); + + StatusCode status = static_cast(rc); + + output.insert(output.end(), out.get(), out.get() + outSize); + + return status; + } + + /*! + * Prefer using decompress(). + * + * Only use this if you for some reason required the decompression to be buffered or somehow think you can benefit from reading the data buffered. + * decompressBuffered() will probably always be slower than the regular decompress() when the LZMA header contains a known file size. + * decompress() will automatically call decompressBuffered() when file size in LZMA header is unknown. + * + * When file size is unknown in the LZMA header, data is always read using the buffered system. + * This will be slightly slower than normal decompression, but does not require the file result to be known + * + * @param input The input compressed data + * @param output The decompressed data + * @param bufferSize The buffer size. Default buffer size for pocketlzma is 65536 bytes. + * @return Status for the decompression process + */ + StatusCode PocketLzma::decompressBuffered(const std::vector &input, std::vector &output, uint32_t bufferSize) + { + return decompressBuffered(&input[0], input.size(), output, bufferSize); + } + + /*! + * Prefer using decompress(). + * + * Only use this if you for some reason required the decompression to be buffered or somehow think you can benefit from reading the data buffered. + * decompressBuffered() will probably always be slower than the regular decompress() when the LZMA header contains a known file size. + * decompress() will automatically call decompressBuffered() when file size in LZMA header is unknown. + * + * When file size is unknown in the LZMA header, data is always read using the buffered system. + * This will be slightly slower than normal decompression, but does not require the file result to be known + * + * @param input The input data + * @param output The decompressed data + * @param bufferSize The buffer size. Default buffer size for pocketlzma is 65536 bytes. + * @return Status for the decompression process + */ + StatusCode PocketLzma::decompressBuffered(const uint8_t *input, const size_t inputSize, std::vector &output, uint32_t bufferSize) + { + if(inputSize <= PLZ_MINIMUM_LZMA_SIZE) + return StatusCode::InvalidLzmaData; + + //size_t unpackSize = 0; + + plz::c::CLzmaDec state; + size_t propsSize = LZMA_PROPS_SIZE + 8; //header + decompress_size + + /* header: 5 bytes of LZMA properties and 8 bytes of uncompressed size */ + unsigned char header[LZMA_PROPS_SIZE + 8]; //MSVC requires this fully constant... + + //Read header data + for(size_t i = 0; i < propsSize; ++i) + header[i] = input[i]; + + LzmaDec_Construct(&state); + int res = 0; + res = LzmaDec_Allocate(&state, header, LZMA_PROPS_SIZE, &plz::c::g_Alloc); + + //uint8_t outBuf[bufferSize]; + std::unique_ptr outBuf(new uint8_t[bufferSize]); + size_t inPos = 0, inSize = 0, outPos = 0; + inSize = inputSize - propsSize; + plz::c::LzmaDec_Init(&state); + for (;;) + { + { + plz::c::SizeT inProcessed = inSize - inPos; + plz::c::SizeT outProcessed = bufferSize - outPos; + plz::c::ELzmaFinishMode finishMode = plz::c::LZMA_FINISH_ANY; + plz::c::ELzmaStatus status; + + res = plz::c::LzmaDec_DecodeToBuf(&state, outBuf.get() + outPos, &outProcessed, + &input[propsSize] + inPos, &inProcessed, finishMode, &status); + + inPos += inProcessed; + outPos += outProcessed; + //unpackSize -= outProcessed; + + output.insert(output.end(), outBuf.get(), outBuf.get() + outPos); + + outPos = 0; + + if (res != SZ_OK) + break; + + if (inProcessed == 0 && outProcessed == 0) + { + if (status != plz::c::LZMA_STATUS_FINISHED_WITH_MARK) + { + LzmaDec_Free(&state, &plz::c::g_Alloc); + return static_cast(SZ_ERROR_DATA); + } + break; + } + } + } + LzmaDec_Free(&state, &plz::c::g_Alloc); + + return static_cast(res); + } + +} + +#endif //POCKETLZMA_POCKETLZMA_CLASS_HPP + +/*** End of inlined file: pocketlzma_class.hpp ***/ + +#endif //POCKETLZMA_POCKETLZMA_H + diff --git a/thirdparty/sgd2freeres/SGD2FreeRes - COPYING b/thirdparty/sgd2freeres/SGD2FreeRes - COPYING new file mode 100644 index 0000000..dbbe355 --- /dev/null +++ b/thirdparty/sgd2freeres/SGD2FreeRes - COPYING @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. diff --git a/thirdparty/sgd2freeres/SGD2FreeRes - Changelog.txt b/thirdparty/sgd2freeres/SGD2FreeRes - Changelog.txt new file mode 100644 index 0000000..893a117 --- /dev/null +++ b/thirdparty/sgd2freeres/SGD2FreeRes - Changelog.txt @@ -0,0 +1,101 @@ +[SGD2FreeRes 3.0.2.0 (May 1, 2021)] +- Restore support for 1.13D. +- Add support for 1.14C and 1.14D. +- Add support for D2DX Glide wrapper. Special thanks to bolrog for + providing the extended API in D2DX. +- Add 856x480 resolution to all existing gateways. +- Add 1068x600 resolution to D2LOD.NET. +- Fix potential incorrect display of DirectDraw video mode. +- Fix config wiping when JSON is not valid. + - Display a warning and exit if the config is not valid JSON. + +[SGD2FreeRes 3.0.1.1B (March 27, 2021)] +- Add D2LOD.NET support. The custom resolution is 1024x768. + +[SGD2FreeRes 3.0.1.1 (March 23, 2021)] +- Add ProjectDiablo 2 support. +- Fix version detection bug that prevented D2SE from launching even + with valid values. +- Fix incorrect inventory positions caused by some resolution + configurations. +- Fix transfer of inventory arrangement positions between SP and MP. + +[SGD2FreeRes 3.0.1.0 (March 13, 2021)] +- Restore support for 1.13C. +- Support D2SE. Must be loaded using the PlugY.ini config. +- Fix video mode detection always incorrectly detecting DirectDraw + when game video mode is not controlled by command line options. +- Fix default assets mismatch for the right screen's border. +- Fix potential bug that may result in the New Skill button not + appearing correctly. + +1.13C Only: +- Fix the aspect ratio window scaling when the maximize button is + pressed. Previously, the maximize button only scaled for 4:3 aspect + ratio even when the resolution's aspect ratio was different. +- Fix the restore down functionality when the maximize button is + pressed. Previously, the button would prevent re-maximizing the + game window when pressed. + +Known issues: +- In 1.09D, Glide mode will sometimes crash from failed ingame + assertion "nIndex != INVALID_HARDWARE". It is currently unknown how + to consistently replicate the error. +- Glide mode displays incorrectly when the maximize button is pressed. + +[SGD2FreeRes 3.0.0.1 (Feb 26, 2021)] +- Support usage of CnC-DDraw as the DDraw wrapper. +- Reduce file size through optimization and the removal of code bloat. +- Fix incompatibility with PlugY. +- Fix improper resolution selection when largest resolution is set, + and 640x480 is excluded in resolution definitions. + +Known issues (since last patch): +- In 1.09D, Glide mode will sometimes crash from failed ingame + assertion "nIndex != INVALID_HARDWARE". It is currently unknown how + to consistently replicate the error. + +[SGD2FreeRes 3.0.0.0 (Dec 12, 2020)] +- Rename the project to SGD2FreeRes, as the project can provide more + than just HD. Also sorts out confusion from folks who conflate HD + with higher quality graphics. +- Rewrite from the ground up, replacing D2Template with SGD2MAPI as + its core. +- Supports 1.09D, support for the other version need to be + ported back in at a later time. +- Use JSON format for config file. +- Uncap single player resolution. Resolutions can be added in the + config file. +- Cap multiplayer resolution is depending on the gateway. +- Support usage of nGlide as the Glide wrapper. +- Fix video option selection crash that would occur when Lighting + Quality is selected under certain circumstances. +- Fix Windows 10 error on game exit, when Sven's Glide Wrapper is + present, but the video mode was not Glide. + +[D2HD 2.0.1.1 (Approx. Mar 2018 - Nov 2018)] +- Add support for 1.12. +- Fix window resize issue upon game exit. +- Fix crash when using a non-standard resolution in DirectDraw or + Direct3D video mode. + +[D2HD 2.0.1.0 (Approx. Mar 2018 - Nov 2018)] +- Set resolution to 1068x600, as Slash wants to maintain consistency + with Resurgence. + +[D2HD 2.0.1.0 Internal Release Candidate (Approx. Mar 2018 - Nov 2018)] +- Set resolution to 1344x700, in case Slash wants to go with the + higher resolution. + +[D2HD 2.0.0.1 (Approx. Dec 2017 - Mar 2018)] +- Fix resolution reload on first game created. Would lag the game, + especially in Chaos Sanctuary. + +[D2HD 2.0 (Approx. June - July 2017] +- Restore 640x480 resolution. New resolutions are added on top of + existing resolutions. +- (Possibly) added support for 1.13D. + +[D2HD 1.0 (Approx. May 2017)] +- Replace 640x480 with 1068x600. +- Only supports 1.13C. diff --git a/thirdparty/sgd2freeres/SGD2FreeRes - LICENSE.md b/thirdparty/sgd2freeres/SGD2FreeRes - LICENSE.md new file mode 100644 index 0000000..eacf6be --- /dev/null +++ b/thirdparty/sgd2freeres/SGD2FreeRes - LICENSE.md @@ -0,0 +1,201 @@ +# SlashGaming Diablo II Free Resolution +Copyright (C) 2019-2021 Mir Drualga + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published +by the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . + +Additional permissions under GNU Affero General Public License version 3 +section 7 + +If you modify this Program, or any covered work, by linking or combining +it with Diablo II (or a modified version of that game and its +libraries), containing parts covered by the terms of Blizzard End User +License Agreement, the licensors of this Program grant you additional +permission to convey the resulting work. This additional permission is +also extended to any combination of expansions, mods, and remasters of +the game. + +If you modify this Program, or any covered work, by linking or combining +it with any Graphics Device Interface (GDI), DirectDraw, Direct3D, +Glide, OpenGL, or Rave wrapper (or modified versions of those +libraries), containing parts not covered by a compatible license, the +licensors of this Program grant you additional permission to convey the +resulting work. + +If you modify this Program, or any covered work, by linking or combining +it with any library (or a modified version of that library) that links +to Diablo II (or a modified version of that game and its libraries), +containing parts not covered by a compatible license, the licensors of +this Program grant you additional permission to convey the resulting +work. + +# SlashGaming Diablo II Modding API +Copyright (C) 2018-2021 Mir Drualga + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published +by the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . + +Additional permissions under GNU Affero General Public License version 3 +section 7 + +If you modify this Program, or any covered work, by linking or combining +it with Diablo II (or a modified version of that game and its +libraries), containing parts covered by the terms of Blizzard End User +License Agreement, the licensors of this Program grant you additional +permission to convey the resulting work. This additional permission is +also extended to any combination of expansions, mods, and remasters of +the game. + +If you modify this Program, or any covered work, by linking or combining +it with any Graphics Device Interface (GDI), DirectDraw, Direct3D, +Glide, OpenGL, or Rave wrapper (or modified versions of those +libraries), containing parts not covered by a compatible license, the +licensors of this Program grant you additional permission to convey the +resulting work. + +If you modify this Program, or any covered work, by linking or combining +it with any library (or a modified version of that library) that links +to Diablo II (or a modified version of that game and its libraries), +containing parts not covered by a compatible license, the licensors of +this Program grant you additional permission to convey the resulting +work. + +# Mir Drualga Common For C +Copyright (C) 2020-2021 Mir Drualga + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published +by the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . + +Additional permissions under GNU Affero General Public License version 3 +section 7 + +If you modify this Program, or any covered work, by linking or combining +it with any program (or a modified version of that program and its +libraries), containing parts covered by the terms of an incompatible +license, the licensors of this Program grant you additional permission +to convey the resulting work. + +# Mir Drualga Common For C++98 +Copyright (C) 2021 Mir Drualga + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published +by the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . + +Additional permissions under GNU Affero General Public License version 3 +section 7 + +If you modify this Program, or any covered work, by linking or combining +it with any program (or a modified version of that program and its +libraries), containing parts covered by the terms of an incompatible +license, the licensors of this Program grant you additional permission +to convey the resulting work. + +# Multi JSON Interface +Copyright 2019 Mir Drualga + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +# RapidJSON +Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +This software also contains the msinttypes r29 from Alexander Chemeris +which is licensed under the BSD License. + +## The msinttypes r29 +Copyright (c) 2006-2013 Alexander Chemeris +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +* Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +* Neither the name of copyright holder nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS +BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR +BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/thirdparty/sgd2freeres/SGD2FreeRes - README.md b/thirdparty/sgd2freeres/SGD2FreeRes - README.md new file mode 100644 index 0000000..f6824b8 --- /dev/null +++ b/thirdparty/sgd2freeres/SGD2FreeRes - README.md @@ -0,0 +1,50 @@ +# SlashGaming Diablo II Free Resolution (SGD2FreeRes) +This is a project aimed at modding Diablo II to support any resolution. + +## Features +- Enables selecting user-specified resolutions from the Video Options menu. +- Automatically adjusts the positions of inventory and UI elements, using 800x600 as a base for positions. +- Allows limited customization of the UI. + +## Compatibility +- Supports 1.09D, 1.13C, 1.13D, 1.14C, and 1.14D. +- Supports multiple video modes. + - Supports standard GDI mode. Resolutions are unrestricted. + - Supports standard DirectDraw and Direct3D modes, restricted to "standard" resolutions. + - Supports CnC-DDraw wrapper. Resolutions are unrestricted. + - Supports [Sven's Glide wrapper](http://www.svenswrapper.de/english/), [nGlide](https://www.zeus-software.com/), and [D2DX](https://github.com/bolrog/d2dx) Glide wrapper. Resolutions are unrestricted. +- Compatibility with select modsystems. + - [PlugY](http://plugy.free.fr/en/index.html) + - [D2SE](https://snej.org/forum/index2.php?topic=18954.msg459574#msg459574) + +## Usage +The DLL does nothing on its own and must be loaded into the game via external tools. Once loaded, the game will need to be run in order to generate the config file for the first time. Singleplayer resolutions, along with other configuration options can be configured to the preferences of the user. + +## Multiplayer Use +Multiplayer use is restricted to certain gateways. This cannot be changed easily and prevents unintentional abuse where it is not allowed (i.e. Battle.net). If used in unpermitted environments, the resolution is hardcoded to run with only 640x480 or 800x600. It is also still possible to be detected by anti-cheat systems and be banned for unauthorized modification of the game client, even if the functionality is ineffective. Where it is permitted, the resolutions are preset for the server. + +Any server owner that wishes to authorize the usage of SGD2FreeRes on their server should contact Mir Drualga on the SlashDiablo Discord. + +Servers that permit SGD2FreeRes: +- [Diablo 09](https://www.diablo09.com/): 640x480, 800x600, 856x480, 1068x600 +- [SlashDiablo](https://slashdiablo.net/): 640x480, 800x600, 856x480, 1068x600 +- [Resurgence](https://resurgence.slashgaming.net/): 640x480, 800x600, 856x480, 1068x600 +- [Project Diablo 2](https://www.projectdiablo2.com/): 640x480, 800x600, 856x480, 1068x600 +- [D2LOD.NET](http://d2lod.net/): 640x480, 800x600, 856x480, 1024x768, 1068x600 + +## Contribution +SGD2FreeRes is coded in C++20. It currently is intended to compile with Visual Studio 2019. + +The linker needs to be configured to link the DLL to version.lib, MDCc.lib, MDCcpp98.lib and SGD2MAPI.lib. + +When you submit a pull request, you certify that the code in the pull request is AGPLv3+ compatible. You also certify that you have authorization to submit the pull request with the code changes. You certify that the merging of the pull request with those changes is authorized under the license terms of the AGPLv3+. Finally, you certify that the contribution is licensed under the AGPLv3+. + +## Thanks +- [/r/SlashDiablo](https://www.reddit.com/r/slashdiablo/): The community that got me started. The whole reason the project took off. +- [PhrozenKeep](https://d2mods.info/): Community with plenty of information on Diablo II, modding, and resources. +- [balrog](https://github.com/bolrog): For providing an extended API to enable interfacing with the D2DX Glide wrapper. + +## Legal +The project is licensed under the terms of the Affero General Public License, version 3 or higher, with exceptions. Components may be licensed under other terms, so check LICENSE for more details. If you wish to apply for a proprietary license exception, please contact Mir Drualga on the SlashDiablo Discord channel. + +Diablo II and Diablo II: Lord of Destruction are registered trademarks of Blizzard Entertainment. This project is not affiliated with Blizzard Entertainment in any way. diff --git a/thirdparty/sgd2freeres/SGD2FreeRes.dll b/thirdparty/sgd2freeres/SGD2FreeRes.dll new file mode 100644 index 0000000..2fdd607 Binary files /dev/null and b/thirdparty/sgd2freeres/SGD2FreeRes.dll differ diff --git a/thirdparty/sgd2freeres/SGD2FreeRes.dll.lzma b/thirdparty/sgd2freeres/SGD2FreeRes.dll.lzma new file mode 100644 index 0000000..e9fd604 Binary files /dev/null and b/thirdparty/sgd2freeres/SGD2FreeRes.dll.lzma differ diff --git a/thirdparty/sgd2freeres/SGD2FreeRes.mpq b/thirdparty/sgd2freeres/SGD2FreeRes.mpq new file mode 100644 index 0000000..5a09faa Binary files /dev/null and b/thirdparty/sgd2freeres/SGD2FreeRes.mpq differ diff --git a/thirdparty/sgd2freeres/SGD2FreeRes.mpq.lzma b/thirdparty/sgd2freeres/SGD2FreeRes.mpq.lzma new file mode 100644 index 0000000..9ac89ba Binary files /dev/null and b/thirdparty/sgd2freeres/SGD2FreeRes.mpq.lzma differ diff --git a/thirdparty/sgd2freeres/SGD2FreeRes.pdb b/thirdparty/sgd2freeres/SGD2FreeRes.pdb new file mode 100644 index 0000000..32d40d1 Binary files /dev/null and b/thirdparty/sgd2freeres/SGD2FreeRes.pdb differ diff --git a/thirdparty/stb_image/stb_image_write.h b/thirdparty/stb_image/stb_image_write.h new file mode 100644 index 0000000..8576385 --- /dev/null +++ b/thirdparty/stb_image/stb_image_write.h @@ -0,0 +1,1708 @@ +/* stb_image_write - v1.15 - public domain - http://nothings.org/stb + writes out PNG/BMP/TGA/JPEG/HDR images to C stdio - Sean Barrett 2010-2015 + no warranty implied; use at your own risk + + Before #including, + + #define STB_IMAGE_WRITE_IMPLEMENTATION + + in the file that you want to have the implementation. + + Will probably not work correctly with strict-aliasing optimizations. + +ABOUT: + + This header file is a library for writing images to C stdio or a callback. + + The PNG output is not optimal; it is 20-50% larger than the file + written by a decent optimizing implementation; though providing a custom + zlib compress function (see STBIW_ZLIB_COMPRESS) can mitigate that. + This library is designed for source code compactness and simplicity, + not optimal image file size or run-time performance. + +BUILDING: + + You can #define STBIW_ASSERT(x) before the #include to avoid using assert.h. + You can #define STBIW_MALLOC(), STBIW_REALLOC(), and STBIW_FREE() to replace + malloc,realloc,free. + You can #define STBIW_MEMMOVE() to replace memmove() + You can #define STBIW_ZLIB_COMPRESS to use a custom zlib-style compress function + for PNG compression (instead of the builtin one), it must have the following signature: + unsigned char * my_compress(unsigned char *data, int data_len, int *out_len, int quality); + The returned data will be freed with STBIW_FREE() (free() by default), + so it must be heap allocated with STBIW_MALLOC() (malloc() by default), + +UNICODE: + + If compiling for Windows and you wish to use Unicode filenames, compile + with + #define STBIW_WINDOWS_UTF8 + and pass utf8-encoded filenames. Call stbiw_convert_wchar_to_utf8 to convert + Windows wchar_t filenames to utf8. + +USAGE: + + There are five functions, one for each image file format: + + int stbi_write_png(char const *filename, int w, int h, int comp, const void *data, int stride_in_bytes); + int stbi_write_bmp(char const *filename, int w, int h, int comp, const void *data); + int stbi_write_tga(char const *filename, int w, int h, int comp, const void *data); + int stbi_write_jpg(char const *filename, int w, int h, int comp, const void *data, int quality); + int stbi_write_hdr(char const *filename, int w, int h, int comp, const float *data); + + void stbi_flip_vertically_on_write(int flag); // flag is non-zero to flip data vertically + + There are also five equivalent functions that use an arbitrary write function. You are + expected to open/close your file-equivalent before and after calling these: + + int stbi_write_png_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data, int stride_in_bytes); + int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); + int stbi_write_tga_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); + int stbi_write_hdr_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const float *data); + int stbi_write_jpg_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int quality); + + where the callback is: + void stbi_write_func(void *context, void *data, int size); + + You can configure it with these global variables: + int stbi_write_tga_with_rle; // defaults to true; set to 0 to disable RLE + int stbi_write_png_compression_level; // defaults to 8; set to higher for more compression + int stbi_write_force_png_filter; // defaults to -1; set to 0..5 to force a filter mode + + + You can define STBI_WRITE_NO_STDIO to disable the file variant of these + functions, so the library will not use stdio.h at all. However, this will + also disable HDR writing, because it requires stdio for formatted output. + + Each function returns 0 on failure and non-0 on success. + + The functions create an image file defined by the parameters. The image + is a rectangle of pixels stored from left-to-right, top-to-bottom. + Each pixel contains 'comp' channels of data stored interleaved with 8-bits + per channel, in the following order: 1=Y, 2=YA, 3=RGB, 4=RGBA. (Y is + monochrome color.) The rectangle is 'w' pixels wide and 'h' pixels tall. + The *data pointer points to the first byte of the top-left-most pixel. + For PNG, "stride_in_bytes" is the distance in bytes from the first byte of + a row of pixels to the first byte of the next row of pixels. + + PNG creates output files with the same number of components as the input. + The BMP format expands Y to RGB in the file format and does not + output alpha. + + PNG supports writing rectangles of data even when the bytes storing rows of + data are not consecutive in memory (e.g. sub-rectangles of a larger image), + by supplying the stride between the beginning of adjacent rows. The other + formats do not. (Thus you cannot write a native-format BMP through the BMP + writer, both because it is in BGR order and because it may have padding + at the end of the line.) + + PNG allows you to set the deflate compression level by setting the global + variable 'stbi_write_png_compression_level' (it defaults to 8). + + HDR expects linear float data. Since the format is always 32-bit rgb(e) + data, alpha (if provided) is discarded, and for monochrome data it is + replicated across all three channels. + + TGA supports RLE or non-RLE compressed data. To use non-RLE-compressed + data, set the global variable 'stbi_write_tga_with_rle' to 0. + + JPEG does ignore alpha channels in input data; quality is between 1 and 100. + Higher quality looks better but results in a bigger image. + JPEG baseline (no JPEG progressive). + +CREDITS: + + + Sean Barrett - PNG/BMP/TGA + Baldur Karlsson - HDR + Jean-Sebastien Guay - TGA monochrome + Tim Kelsey - misc enhancements + Alan Hickman - TGA RLE + Emmanuel Julien - initial file IO callback implementation + Jon Olick - original jo_jpeg.cpp code + Daniel Gibson - integrate JPEG, allow external zlib + Aarni Koskela - allow choosing PNG filter + + bugfixes: + github:Chribba + Guillaume Chereau + github:jry2 + github:romigrou + Sergio Gonzalez + Jonas Karlsson + Filip Wasil + Thatcher Ulrich + github:poppolopoppo + Patrick Boettcher + github:xeekworx + Cap Petschulat + Simon Rodriguez + Ivan Tikhonov + github:ignotion + Adam Schackart + +LICENSE + + See end of file for license information. + +*/ + +#ifndef INCLUDE_STB_IMAGE_WRITE_H +#define INCLUDE_STB_IMAGE_WRITE_H + +#include + +// if STB_IMAGE_WRITE_STATIC causes problems, try defining STBIWDEF to 'inline' or 'static inline' +#ifndef STBIWDEF +#ifdef STB_IMAGE_WRITE_STATIC +#define STBIWDEF static +#else +#ifdef __cplusplus +#define STBIWDEF extern "C" +#else +#define STBIWDEF extern +#endif +#endif +#endif + +#ifndef STB_IMAGE_WRITE_STATIC // C++ forbids static forward declarations +extern int stbi_write_tga_with_rle; +extern int stbi_write_png_compression_level; +extern int stbi_write_force_png_filter; +#endif + +#ifndef STBI_WRITE_NO_STDIO +STBIWDEF int stbi_write_png(char const* filename, int w, int h, int comp, const void* data, int stride_in_bytes); +STBIWDEF int stbi_write_bmp(char const* filename, int w, int h, int comp, const void* data); +STBIWDEF int stbi_write_tga(char const* filename, int w, int h, int comp, const void* data); +STBIWDEF int stbi_write_hdr(char const* filename, int w, int h, int comp, const float* data); +STBIWDEF int stbi_write_jpg(char const* filename, int x, int y, int comp, const void* data, int quality); + +#ifdef STBI_WINDOWS_UTF8 +STBIWDEF int stbiw_convert_wchar_to_utf8(char* buffer, size_t bufferlen, const wchar_t* input); +#endif +#endif + +typedef void stbi_write_func(void* context, void* data, int size); + +STBIWDEF int stbi_write_png_to_func(stbi_write_func* func, void* context, int w, int h, int comp, const void* data, int stride_in_bytes); +STBIWDEF int stbi_write_bmp_to_func(stbi_write_func* func, void* context, int w, int h, int comp, const void* data); +STBIWDEF int stbi_write_tga_to_func(stbi_write_func* func, void* context, int w, int h, int comp, const void* data); +STBIWDEF int stbi_write_hdr_to_func(stbi_write_func* func, void* context, int w, int h, int comp, const float* data); +STBIWDEF int stbi_write_jpg_to_func(stbi_write_func* func, void* context, int x, int y, int comp, const void* data, int quality); + +STBIWDEF void stbi_flip_vertically_on_write(int flip_boolean); + +#endif//INCLUDE_STB_IMAGE_WRITE_H + +#ifdef STB_IMAGE_WRITE_IMPLEMENTATION + +#ifdef _WIN32 +#ifndef _CRT_SECURE_NO_WARNINGS +#define _CRT_SECURE_NO_WARNINGS +#endif +#ifndef _CRT_NONSTDC_NO_DEPRECATE +#define _CRT_NONSTDC_NO_DEPRECATE +#endif +#endif + +#ifndef STBI_WRITE_NO_STDIO +#include +#endif // STBI_WRITE_NO_STDIO + +#include +#include +#include +#include + +#if defined(STBIW_MALLOC) && defined(STBIW_FREE) && (defined(STBIW_REALLOC) || defined(STBIW_REALLOC_SIZED)) +// ok +#elif !defined(STBIW_MALLOC) && !defined(STBIW_FREE) && !defined(STBIW_REALLOC) && !defined(STBIW_REALLOC_SIZED) +// ok +#else +#error "Must define all or none of STBIW_MALLOC, STBIW_FREE, and STBIW_REALLOC (or STBIW_REALLOC_SIZED)." +#endif + +#ifndef STBIW_MALLOC +#define STBIW_MALLOC(sz) malloc(sz) +#define STBIW_REALLOC(p,newsz) realloc(p,newsz) +#define STBIW_FREE(p) free(p) +#endif + +#ifndef STBIW_REALLOC_SIZED +#define STBIW_REALLOC_SIZED(p,oldsz,newsz) STBIW_REALLOC(p,newsz) +#endif + + +#ifndef STBIW_MEMMOVE +#define STBIW_MEMMOVE(a,b,sz) memmove(a,b,sz) +#endif + + +#ifndef STBIW_ASSERT +#include +#define STBIW_ASSERT(x) assert(x) +#endif + +#define STBIW_UCHAR(x) (unsigned char) ((x) & 0xff) + +#ifdef STB_IMAGE_WRITE_STATIC +static int stbi_write_png_compression_level = 8; +static int stbi_write_tga_with_rle = 1; +static int stbi_write_force_png_filter = -1; +#else +int stbi_write_png_compression_level = 8; +int stbi_write_tga_with_rle = 1; +int stbi_write_force_png_filter = -1; +#endif + +static int stbi__flip_vertically_on_write = 0; + +STBIWDEF void stbi_flip_vertically_on_write(int flag) +{ + stbi__flip_vertically_on_write = flag; +} + +typedef struct +{ + stbi_write_func* func; + void* context; + unsigned char buffer[64]; + int buf_used; +} stbi__write_context; + +// initialize a callback-based context +static void stbi__start_write_callbacks(stbi__write_context* s, stbi_write_func* c, void* context) +{ + s->func = c; + s->context = context; +} + +#ifndef STBI_WRITE_NO_STDIO + +static void stbi__stdio_write(void* context, void* data, int size) +{ + fwrite(data, 1, size, (FILE*)context); +} + +#if defined(_MSC_VER) && defined(STBI_WINDOWS_UTF8) +#ifdef __cplusplus +#define STBIW_EXTERN extern "C" +#else +#define STBIW_EXTERN extern +#endif +STBIW_EXTERN __declspec(dllimport) int __stdcall MultiByteToWideChar(unsigned int cp, unsigned long flags, const char* str, int cbmb, wchar_t* widestr, int cchwide); +STBIW_EXTERN __declspec(dllimport) int __stdcall WideCharToMultiByte(unsigned int cp, unsigned long flags, const wchar_t* widestr, int cchwide, char* str, int cbmb, const char* defchar, int* used_default); + +STBIWDEF int stbiw_convert_wchar_to_utf8(char* buffer, size_t bufferlen, const wchar_t* input) +{ + return WideCharToMultiByte(65001 /* UTF8 */, 0, input, -1, buffer, (int)bufferlen, NULL, NULL); +} +#endif + +static FILE* stbiw__fopen(char const* filename, char const* mode) +{ + FILE* f; +#if defined(_MSC_VER) && defined(STBI_WINDOWS_UTF8) + wchar_t wMode[64]; + wchar_t wFilename[1024]; + if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, filename, -1, wFilename, sizeof(wFilename))) + return 0; + + if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, mode, -1, wMode, sizeof(wMode))) + return 0; + +#if _MSC_VER >= 1400 + if (0 != _wfopen_s(&f, wFilename, wMode)) + f = 0; +#else + f = _wfopen(wFilename, wMode); +#endif + +#elif defined(_MSC_VER) && _MSC_VER >= 1400 + if (0 != fopen_s(&f, filename, mode)) + f = 0; +#else + f = fopen(filename, mode); +#endif + return f; +} + +static int stbi__start_write_file(stbi__write_context* s, const char* filename) +{ + FILE* f = stbiw__fopen(filename, "wb"); + stbi__start_write_callbacks(s, stbi__stdio_write, (void*)f); + return f != NULL; +} + +static void stbi__end_write_file(stbi__write_context* s) +{ + fclose((FILE*)s->context); +} + +#endif // !STBI_WRITE_NO_STDIO + +typedef unsigned int stbiw_uint32; +typedef int stb_image_write_test[sizeof(stbiw_uint32) == 4 ? 1 : -1]; + +static void stbiw__writefv(stbi__write_context* s, const char* fmt, va_list v) +{ + while (*fmt) { + switch (*fmt++) { + case ' ': break; + case '1': { unsigned char x = STBIW_UCHAR(va_arg(v, int)); + s->func(s->context, &x, 1); + break; } + case '2': { int x = va_arg(v, int); + unsigned char b[2]; + b[0] = STBIW_UCHAR(x); + b[1] = STBIW_UCHAR(x >> 8); + s->func(s->context, b, 2); + break; } + case '4': { stbiw_uint32 x = va_arg(v, int); + unsigned char b[4]; + b[0] = STBIW_UCHAR(x); + b[1] = STBIW_UCHAR(x >> 8); + b[2] = STBIW_UCHAR(x >> 16); + b[3] = STBIW_UCHAR(x >> 24); + s->func(s->context, b, 4); + break; } + default: + STBIW_ASSERT(0); + return; + } + } +} + +static void stbiw__writef(stbi__write_context* s, const char* fmt, ...) +{ + va_list v; + va_start(v, fmt); + stbiw__writefv(s, fmt, v); + va_end(v); +} + +static void stbiw__write_flush(stbi__write_context* s) +{ + if (s->buf_used) { + s->func(s->context, &s->buffer, s->buf_used); + s->buf_used = 0; + } +} + +static void stbiw__putc(stbi__write_context* s, unsigned char c) +{ + s->func(s->context, &c, 1); +} + +static void stbiw__write1(stbi__write_context* s, unsigned char a) +{ + if (s->buf_used + 1 > sizeof(s->buffer)) + stbiw__write_flush(s); + s->buffer[s->buf_used++] = a; +} + +static void stbiw__write3(stbi__write_context* s, unsigned char a, unsigned char b, unsigned char c) +{ + int n; + if (s->buf_used + 3 > sizeof(s->buffer)) + stbiw__write_flush(s); + n = s->buf_used; + s->buf_used = n + 3; + s->buffer[n + 0] = a; + s->buffer[n + 1] = b; + s->buffer[n + 2] = c; +} + +static void stbiw__write_pixel(stbi__write_context* s, int rgb_dir, int comp, int write_alpha, int expand_mono, unsigned char* d) +{ + unsigned char bg[3] = { 255, 0, 255 }, px[3]; + int k; + + if (write_alpha < 0) + stbiw__write1(s, d[comp - 1]); + + switch (comp) { + case 2: // 2 pixels = mono + alpha, alpha is written separately, so same as 1-channel case + case 1: + if (expand_mono) + stbiw__write3(s, d[0], d[0], d[0]); // monochrome bmp + else + stbiw__write1(s, d[0]); // monochrome TGA + break; + case 4: + if (!write_alpha) { + // composite against pink background + for (k = 0; k < 3; ++k) + px[k] = bg[k] + ((d[k] - bg[k]) * d[3]) / 255; + stbiw__write3(s, px[1 - rgb_dir], px[1], px[1 + rgb_dir]); + break; + } + [[fallthrough]]; + case 3: + stbiw__write3(s, d[1 - rgb_dir], d[1], d[1 + rgb_dir]); + break; + } + if (write_alpha > 0) + stbiw__write1(s, d[comp - 1]); +} + +static void stbiw__write_pixels(stbi__write_context* s, int rgb_dir, int vdir, int x, int y, int comp, void* data, int write_alpha, int scanline_pad, int expand_mono) +{ + stbiw_uint32 zero = 0; + int i, j, j_end; + + if (y <= 0) + return; + + if (stbi__flip_vertically_on_write) + vdir *= -1; + + if (vdir < 0) { + j_end = -1; j = y - 1; + } + else { + j_end = y; j = 0; + } + + for (; j != j_end; j += vdir) { + for (i = 0; i < x; ++i) { + unsigned char* d = (unsigned char*)data + (j * x + i) * comp; + stbiw__write_pixel(s, rgb_dir, comp, write_alpha, expand_mono, d); + } + stbiw__write_flush(s); + s->func(s->context, &zero, scanline_pad); + } +} + +static int stbiw__outfile(stbi__write_context* s, int rgb_dir, int vdir, int x, int y, int comp, int expand_mono, void* data, int alpha, int pad, const char* fmt, ...) +{ + if (y < 0 || x < 0) { + return 0; + } + else { + va_list v; + va_start(v, fmt); + stbiw__writefv(s, fmt, v); + va_end(v); + stbiw__write_pixels(s, rgb_dir, vdir, x, y, comp, data, alpha, pad, expand_mono); + return 1; + } +} + +static int stbi_write_bmp_core(stbi__write_context* s, int x, int y, int comp, const void* data) +{ + int pad = (-x * 3) & 3; + return stbiw__outfile(s, -1, -1, x, y, comp, 1, (void*)data, 0, pad, + "11 4 22 4" "4 44 22 444444", + 'B', 'M', 14 + 40 + (x * 3 + pad) * y, 0, 0, 14 + 40, // file header + 40, x, y, 1, 24, 0, 0, 0, 0, 0, 0); // bitmap header +} + +STBIWDEF int stbi_write_bmp_to_func(stbi_write_func* func, void* context, int x, int y, int comp, const void* data) +{ + stbi__write_context s = { 0 }; + stbi__start_write_callbacks(&s, func, context); + return stbi_write_bmp_core(&s, x, y, comp, data); +} + +#ifndef STBI_WRITE_NO_STDIO +STBIWDEF int stbi_write_bmp(char const* filename, int x, int y, int comp, const void* data) +{ + stbi__write_context s = { 0 }; + if (stbi__start_write_file(&s, filename)) { + int r = stbi_write_bmp_core(&s, x, y, comp, data); + stbi__end_write_file(&s); + return r; + } + else + return 0; +} +#endif //!STBI_WRITE_NO_STDIO + +static int stbi_write_tga_core(stbi__write_context* s, int x, int y, int comp, void* data) +{ + int has_alpha = (comp == 2 || comp == 4); + int colorbytes = has_alpha ? comp - 1 : comp; + int format = colorbytes < 2 ? 3 : 2; // 3 color channels (RGB/RGBA) = 2, 1 color channel (Y/YA) = 3 + + if (y < 0 || x < 0) + return 0; + + if (!stbi_write_tga_with_rle) { + return stbiw__outfile(s, -1, -1, x, y, comp, 0, (void*)data, has_alpha, 0, + "111 221 2222 11", 0, 0, format, 0, 0, 0, 0, 0, x, y, (colorbytes + has_alpha) * 8, has_alpha * 8); + } + else { + int i, j, k; + int jend, jdir; + + stbiw__writef(s, "111 221 2222 11", 0, 0, format + 8, 0, 0, 0, 0, 0, x, y, (colorbytes + has_alpha) * 8, has_alpha * 8); + + if (stbi__flip_vertically_on_write) { + j = 0; + jend = y; + jdir = 1; + } + else { + j = y - 1; + jend = -1; + jdir = -1; + } + for (; j != jend; j += jdir) { + unsigned char* row = (unsigned char*)data + j * x * comp; + int len; + + for (i = 0; i < x; i += len) { + unsigned char* begin = row + i * comp; + int diff = 1; + len = 1; + + if (i < x - 1) { + ++len; + diff = memcmp(begin, row + (i + 1) * comp, comp); + if (diff) { + const unsigned char* prev = begin; + for (k = i + 2; k < x && len < 128; ++k) { + if (memcmp(prev, row + k * comp, comp)) { + prev += comp; + ++len; + } + else { + --len; + break; + } + } + } + else { + for (k = i + 2; k < x && len < 128; ++k) { + if (!memcmp(begin, row + k * comp, comp)) { + ++len; + } + else { + break; + } + } + } + } + + if (diff) { + unsigned char header = STBIW_UCHAR(len - 1); + stbiw__write1(s, header); + for (k = 0; k < len; ++k) { + stbiw__write_pixel(s, -1, comp, has_alpha, 0, begin + k * comp); + } + } + else { + unsigned char header = STBIW_UCHAR(len - 129); + stbiw__write1(s, header); + stbiw__write_pixel(s, -1, comp, has_alpha, 0, begin); + } + } + } + stbiw__write_flush(s); + } + return 1; +} + +STBIWDEF int stbi_write_tga_to_func(stbi_write_func* func, void* context, int x, int y, int comp, const void* data) +{ + stbi__write_context s = { 0 }; + stbi__start_write_callbacks(&s, func, context); + return stbi_write_tga_core(&s, x, y, comp, (void*)data); +} + +#ifndef STBI_WRITE_NO_STDIO +STBIWDEF int stbi_write_tga(char const* filename, int x, int y, int comp, const void* data) +{ + stbi__write_context s = { 0 }; + if (stbi__start_write_file(&s, filename)) { + int r = stbi_write_tga_core(&s, x, y, comp, (void*)data); + stbi__end_write_file(&s); + return r; + } + else + return 0; +} +#endif + +// ************************************************************************************************* +// Radiance RGBE HDR writer +// by Baldur Karlsson + +#define stbiw__max(a, b) ((a) > (b) ? (a) : (b)) + +static void stbiw__linear_to_rgbe(unsigned char* rgbe, float* linear) +{ + int exponent; + float maxcomp = stbiw__max(linear[0], stbiw__max(linear[1], linear[2])); + + if (maxcomp < 1e-32f) { + rgbe[0] = rgbe[1] = rgbe[2] = rgbe[3] = 0; + } + else { + float normalize = (float)frexp(maxcomp, &exponent) * 256.0f / maxcomp; + + rgbe[0] = (unsigned char)(linear[0] * normalize); + rgbe[1] = (unsigned char)(linear[1] * normalize); + rgbe[2] = (unsigned char)(linear[2] * normalize); + rgbe[3] = (unsigned char)(exponent + 128); + } +} + +static void stbiw__write_run_data(stbi__write_context* s, int length, unsigned char databyte) +{ + unsigned char lengthbyte = STBIW_UCHAR(length + 128); + STBIW_ASSERT(length + 128 <= 255); + s->func(s->context, &lengthbyte, 1); + s->func(s->context, &databyte, 1); +} + +static void stbiw__write_dump_data(stbi__write_context* s, int length, unsigned char* data) +{ + unsigned char lengthbyte = STBIW_UCHAR(length); + STBIW_ASSERT(length <= 128); // inconsistent with spec but consistent with official code + s->func(s->context, &lengthbyte, 1); + s->func(s->context, data, length); +} + +static void stbiw__write_hdr_scanline(stbi__write_context* s, int width, int ncomp, unsigned char* scratch, float* scanline) +{ + unsigned char scanlineheader[4] = { 2, 2, 0, 0 }; + unsigned char rgbe[4]; + float linear[3]; + int x; + + scanlineheader[2] = (width & 0xff00) >> 8; + scanlineheader[3] = (width & 0x00ff); + + /* skip RLE for images too small or large */ + if (width < 8 || width >= 32768) { + for (x = 0; x < width; x++) { + switch (ncomp) { + case 4: /* fallthrough */ + case 3: linear[2] = scanline[x * ncomp + 2]; + linear[1] = scanline[x * ncomp + 1]; + linear[0] = scanline[x * ncomp + 0]; + break; + default: + linear[0] = linear[1] = linear[2] = scanline[x * ncomp + 0]; + break; + } + stbiw__linear_to_rgbe(rgbe, linear); + s->func(s->context, rgbe, 4); + } + } + else { + int c, r; + /* encode into scratch buffer */ + for (x = 0; x < width; x++) { + switch (ncomp) { + case 4: /* fallthrough */ + case 3: linear[2] = scanline[x * ncomp + 2]; + linear[1] = scanline[x * ncomp + 1]; + linear[0] = scanline[x * ncomp + 0]; + break; + default: + linear[0] = linear[1] = linear[2] = scanline[x * ncomp + 0]; + break; + } + stbiw__linear_to_rgbe(rgbe, linear); + scratch[x + width * 0] = rgbe[0]; + scratch[x + width * 1] = rgbe[1]; + scratch[x + width * 2] = rgbe[2]; + scratch[x + width * 3] = rgbe[3]; + } + + s->func(s->context, scanlineheader, 4); + + /* RLE each component separately */ + for (c = 0; c < 4; c++) { + unsigned char* comp = &scratch[width * c]; + + x = 0; + while (x < width) { + // find first run + r = x; + while (r + 2 < width) { + if (comp[r] == comp[r + 1] && comp[r] == comp[r + 2]) + break; + ++r; + } + if (r + 2 >= width) + r = width; + // dump up to first run + while (x < r) { + int len = r - x; + if (len > 128) len = 128; + stbiw__write_dump_data(s, len, &comp[x]); + x += len; + } + // if there's a run, output it + if (r + 2 < width) { // same test as what we break out of in search loop, so only true if we break'd + // find next byte after run + while (r < width && comp[r] == comp[x]) + ++r; + // output run up to r + while (x < r) { + int len = r - x; + if (len > 127) len = 127; + stbiw__write_run_data(s, len, comp[x]); + x += len; + } + } + } + } + } +} + +static int stbi_write_hdr_core(stbi__write_context* s, int x, int y, int comp, float* data) +{ + if (y <= 0 || x <= 0 || data == NULL) + return 0; + else { + // Each component is stored separately. Allocate scratch space for full output scanline. + unsigned char* scratch = (unsigned char*)STBIW_MALLOC(x * 4); + int i, len; + char buffer[128]; + char header[] = "#?RADIANCE\n# Written by stb_image_write.h\nFORMAT=32-bit_rle_rgbe\n"; + s->func(s->context, header, sizeof(header) - 1); + +#ifdef __STDC_WANT_SECURE_LIB__ + len = sprintf_s(buffer, sizeof(buffer), "EXPOSURE= 1.0000000000000\n\n-Y %d +X %d\n", y, x); +#else + len = sprintf(buffer, "EXPOSURE= 1.0000000000000\n\n-Y %d +X %d\n", y, x); +#endif + s->func(s->context, buffer, len); + + for (i = 0; i < y; i++) + stbiw__write_hdr_scanline(s, x, comp, scratch, data + comp * x * (stbi__flip_vertically_on_write ? y - 1 - i : i)); + STBIW_FREE(scratch); + return 1; + } +} + +STBIWDEF int stbi_write_hdr_to_func(stbi_write_func* func, void* context, int x, int y, int comp, const float* data) +{ + stbi__write_context s = { 0 }; + stbi__start_write_callbacks(&s, func, context); + return stbi_write_hdr_core(&s, x, y, comp, (float*)data); +} + +#ifndef STBI_WRITE_NO_STDIO +STBIWDEF int stbi_write_hdr(char const* filename, int x, int y, int comp, const float* data) +{ + stbi__write_context s = { 0 }; + if (stbi__start_write_file(&s, filename)) { + int r = stbi_write_hdr_core(&s, x, y, comp, (float*)data); + stbi__end_write_file(&s); + return r; + } + else + return 0; +} +#endif // STBI_WRITE_NO_STDIO + + +////////////////////////////////////////////////////////////////////////////// +// +// PNG writer +// + +#ifndef STBIW_ZLIB_COMPRESS +// stretchy buffer; stbiw__sbpush() == vector<>::push_back() -- stbiw__sbcount() == vector<>::size() +#define stbiw__sbraw(a) ((int *) (void *) (a) - 2) +#define stbiw__sbm(a) stbiw__sbraw(a)[0] +#define stbiw__sbn(a) stbiw__sbraw(a)[1] + +#define stbiw__sbneedgrow(a,n) ((a)==0 || stbiw__sbn(a)+n >= stbiw__sbm(a)) +#define stbiw__sbmaybegrow(a,n) (stbiw__sbneedgrow(a,(n)) ? stbiw__sbgrow(a,n) : 0) +#define stbiw__sbgrow(a,n) stbiw__sbgrowf((void **) &(a), (n), sizeof(*(a))) + +#define stbiw__sbpush(a, v) (stbiw__sbmaybegrow(a,1), (a)[stbiw__sbn(a)++] = (v)) +#define stbiw__sbcount(a) ((a) ? stbiw__sbn(a) : 0) +#define stbiw__sbfree(a) ((a) ? STBIW_FREE(stbiw__sbraw(a)),0 : 0) + +static void* stbiw__sbgrowf(void** arr, int increment, int itemsize) +{ + int m = *arr ? 2 * stbiw__sbm(*arr) + increment : increment + 1; + void* p = STBIW_REALLOC_SIZED(*arr ? stbiw__sbraw(*arr) : 0, *arr ? (stbiw__sbm(*arr) * itemsize + sizeof(int) * 2) : 0, itemsize * m + sizeof(int) * 2); + STBIW_ASSERT(p); + if (p) { + if (!*arr) ((int*)p)[1] = 0; + *arr = (void*)((int*)p + 2); + stbiw__sbm(*arr) = m; + } + return *arr; +} + +static unsigned char* stbiw__zlib_flushf(unsigned char* data, unsigned int* bitbuffer, int* bitcount) +{ + while (*bitcount >= 8) { + stbiw__sbpush(data, STBIW_UCHAR(*bitbuffer)); + *bitbuffer >>= 8; + *bitcount -= 8; + } + return data; +} + +static int stbiw__zlib_bitrev(int code, int codebits) +{ + int res = 0; + while (codebits--) { + res = (res << 1) | (code & 1); + code >>= 1; + } + return res; +} + +static unsigned int stbiw__zlib_countm(unsigned char* a, unsigned char* b, int limit) +{ + int i; + for (i = 0; i < limit && i < 258; ++i) + if (a[i] != b[i]) break; + return i; +} + +static unsigned int stbiw__zhash(unsigned char* data) +{ + stbiw_uint32 hash = data[0] + (data[1] << 8) + (data[2] << 16); + hash ^= hash << 3; + hash += hash >> 5; + hash ^= hash << 4; + hash += hash >> 17; + hash ^= hash << 25; + hash += hash >> 6; + return hash; +} + +#define stbiw__zlib_flush() (out = stbiw__zlib_flushf(out, &bitbuf, &bitcount)) +#define stbiw__zlib_add(code,codebits) \ + (bitbuf |= (code) << bitcount, bitcount += (codebits), stbiw__zlib_flush()) +#define stbiw__zlib_huffa(b,c) stbiw__zlib_add(stbiw__zlib_bitrev(b,c),c) +// default huffman tables +#define stbiw__zlib_huff1(n) stbiw__zlib_huffa(0x30 + (n), 8) +#define stbiw__zlib_huff2(n) stbiw__zlib_huffa(0x190 + (n)-144, 9) +#define stbiw__zlib_huff3(n) stbiw__zlib_huffa(0 + (n)-256,7) +#define stbiw__zlib_huff4(n) stbiw__zlib_huffa(0xc0 + (n)-280,8) +#define stbiw__zlib_huff(n) ((n) <= 143 ? stbiw__zlib_huff1(n) : (n) <= 255 ? stbiw__zlib_huff2(n) : (n) <= 279 ? stbiw__zlib_huff3(n) : stbiw__zlib_huff4(n)) +#define stbiw__zlib_huffb(n) ((n) <= 143 ? stbiw__zlib_huff1(n) : stbiw__zlib_huff2(n)) + +#define stbiw__ZHASH 16384 + +#endif // STBIW_ZLIB_COMPRESS + +STBIWDEF unsigned char* stbi_zlib_compress(unsigned char* data, int data_len, int* out_len, int quality) +{ +#ifdef STBIW_ZLIB_COMPRESS + // user provided a zlib compress implementation, use that + return STBIW_ZLIB_COMPRESS(data, data_len, out_len, quality); +#else // use builtin + static unsigned short lengthc[] = { 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, 259 }; + static unsigned char lengtheb[] = { 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, 0 }; + static unsigned short distc[] = { 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, 32768 }; + static unsigned char disteb[] = { 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 }; + unsigned int bitbuf = 0; + int i, j, bitcount = 0; + unsigned char* out = NULL; + unsigned char*** hash_table = (unsigned char***)STBIW_MALLOC(stbiw__ZHASH * sizeof(unsigned char**)); + if (hash_table == NULL) + return NULL; + if (quality < 5) quality = 5; + + stbiw__sbpush(out, 0x78); // DEFLATE 32K window + stbiw__sbpush(out, 0x5e); // FLEVEL = 1 + stbiw__zlib_add(1, 1); // BFINAL = 1 + stbiw__zlib_add(1, 2); // BTYPE = 1 -- fixed huffman + + for (i = 0; i < stbiw__ZHASH; ++i) + hash_table[i] = NULL; + + i = 0; + while (i < data_len - 3) { + // hash next 3 bytes of data to be compressed + int h = stbiw__zhash(data + i) & (stbiw__ZHASH - 1), best = 3; + unsigned char* bestloc = 0; + unsigned char** hlist = hash_table[h]; + int n = stbiw__sbcount(hlist); + for (j = 0; j < n; ++j) { + if (hlist[j] - data > i - 32768) { // if entry lies within window + int d = stbiw__zlib_countm(hlist[j], data + i, data_len - i); + if (d >= best) { best = d; bestloc = hlist[j]; } + } + } + // when hash table entry is too long, delete half the entries + if (hash_table[h] && stbiw__sbn(hash_table[h]) == 2 * quality) { + STBIW_MEMMOVE(hash_table[h], hash_table[h] + quality, sizeof(hash_table[h][0]) * quality); + stbiw__sbn(hash_table[h]) = quality; + } + stbiw__sbpush(hash_table[h], data + i); + + if (bestloc) { + // "lazy matching" - check match at *next* byte, and if it's better, do cur byte as literal + h = stbiw__zhash(data + i + 1) & (stbiw__ZHASH - 1); + hlist = hash_table[h]; + n = stbiw__sbcount(hlist); + for (j = 0; j < n; ++j) { + if (hlist[j] - data > i - 32767) { + int e = stbiw__zlib_countm(hlist[j], data + i + 1, data_len - i - 1); + if (e > best) { // if next match is better, bail on current match + bestloc = NULL; + break; + } + } + } + } + + if (bestloc) { + int d = (int)(data + i - bestloc); // distance back + STBIW_ASSERT(d <= 32767 && best <= 258); + for (j = 0; best > lengthc[j + 1] - 1; ++j); + stbiw__zlib_huff(j + 257); + if (lengtheb[j]) stbiw__zlib_add(best - lengthc[j], lengtheb[j]); + for (j = 0; d > distc[j + 1] - 1; ++j); + stbiw__zlib_add(stbiw__zlib_bitrev(j, 5), 5); + if (disteb[j]) stbiw__zlib_add(d - distc[j], disteb[j]); + i += best; + } + else { + stbiw__zlib_huffb(data[i]); + ++i; + } + } + // write out final bytes + for (; i < data_len; ++i) + stbiw__zlib_huffb(data[i]); + stbiw__zlib_huff(256); // end of block + // pad with 0 bits to byte boundary + while (bitcount) + stbiw__zlib_add(0, 1); + + for (i = 0; i < stbiw__ZHASH; ++i) + (void)stbiw__sbfree(hash_table[i]); + STBIW_FREE(hash_table); + + { + // compute adler32 on input + unsigned int s1 = 1, s2 = 0; + int blocklen = (int)(data_len % 5552); + j = 0; + while (j < data_len) { + for (i = 0; i < blocklen; ++i) { s1 += data[j + i]; s2 += s1; } + s1 %= 65521; s2 %= 65521; + j += blocklen; + blocklen = 5552; + } + stbiw__sbpush(out, STBIW_UCHAR(s2 >> 8)); + stbiw__sbpush(out, STBIW_UCHAR(s2)); + stbiw__sbpush(out, STBIW_UCHAR(s1 >> 8)); + stbiw__sbpush(out, STBIW_UCHAR(s1)); + } + *out_len = stbiw__sbn(out); + // make returned pointer freeable + STBIW_MEMMOVE(stbiw__sbraw(out), out, *out_len); + return (unsigned char*)stbiw__sbraw(out); +#endif // STBIW_ZLIB_COMPRESS +} + +static unsigned int stbiw__crc32(unsigned char* buffer, int len) +{ +#ifdef STBIW_CRC32 + return STBIW_CRC32(buffer, len); +#else + static unsigned int crc_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 + }; + + unsigned int crc = ~0u; + int i; + for (i = 0; i < len; ++i) + crc = (crc >> 8) ^ crc_table[buffer[i] ^ (crc & 0xff)]; + return ~crc; +#endif +} + +#define stbiw__wpng4(o,a,b,c,d) ((o)[0]=STBIW_UCHAR(a),(o)[1]=STBIW_UCHAR(b),(o)[2]=STBIW_UCHAR(c),(o)[3]=STBIW_UCHAR(d),(o)+=4) +#define stbiw__wp32(data,v) stbiw__wpng4(data, (v)>>24,(v)>>16,(v)>>8,(v)); +#define stbiw__wptag(data,s) stbiw__wpng4(data, s[0],s[1],s[2],s[3]) + +static void stbiw__wpcrc(unsigned char** data, int len) +{ + unsigned int crc = stbiw__crc32(*data - len - 4, len + 4); + stbiw__wp32(*data, crc); +} + +static unsigned char stbiw__paeth(int a, int b, int c) +{ + int p = a + b - c, pa = abs(p - a), pb = abs(p - b), pc = abs(p - c); + if (pa <= pb && pa <= pc) return STBIW_UCHAR(a); + if (pb <= pc) return STBIW_UCHAR(b); + return STBIW_UCHAR(c); +} + +// @OPTIMIZE: provide an option that always forces left-predict or paeth predict +static void stbiw__encode_png_line(unsigned char* pixels, int stride_bytes, int width, int height, int y, int n, int filter_type, signed char* line_buffer) +{ + static int mapping[] = { 0,1,2,3,4 }; + static int firstmap[] = { 0,1,0,5,6 }; + int* mymap = (y != 0) ? mapping : firstmap; + int i; + int type = mymap[filter_type]; + unsigned char* z = pixels + stride_bytes * (stbi__flip_vertically_on_write ? height - 1 - y : y); + int signed_stride = stbi__flip_vertically_on_write ? -stride_bytes : stride_bytes; + + if (type == 0) { + memcpy(line_buffer, z, width * n); + return; + } + + // first loop isn't optimized since it's just one pixel + for (i = 0; i < n; ++i) { + switch (type) { + case 1: line_buffer[i] = z[i]; break; + case 2: line_buffer[i] = z[i] - z[i - signed_stride]; break; + case 3: line_buffer[i] = z[i] - (z[i - signed_stride] >> 1); break; + case 4: line_buffer[i] = (signed char)(z[i] - stbiw__paeth(0, z[i - signed_stride], 0)); break; + case 5: line_buffer[i] = z[i]; break; + case 6: line_buffer[i] = z[i]; break; + } + } + switch (type) { + case 1: for (i = n; i < width * n; ++i) line_buffer[i] = z[i] - z[i - n]; break; + case 2: for (i = n; i < width * n; ++i) line_buffer[i] = z[i] - z[i - signed_stride]; break; + case 3: for (i = n; i < width * n; ++i) line_buffer[i] = z[i] - ((z[i - n] + z[i - signed_stride]) >> 1); break; + case 4: for (i = n; i < width * n; ++i) line_buffer[i] = z[i] - stbiw__paeth(z[i - n], z[i - signed_stride], z[i - signed_stride - n]); break; + case 5: for (i = n; i < width * n; ++i) line_buffer[i] = z[i] - (z[i - n] >> 1); break; + case 6: for (i = n; i < width * n; ++i) line_buffer[i] = z[i] - stbiw__paeth(z[i - n], 0, 0); break; + } +} + +STBIWDEF unsigned char* stbi_write_png_to_mem(const unsigned char* pixels, int stride_bytes, int x, int y, int n, int* out_len) +{ + int force_filter = stbi_write_force_png_filter; + int ctype[5] = { -1, 0, 4, 2, 6 }; + unsigned char sig[8] = { 137,80,78,71,13,10,26,10 }; + unsigned char* out, * o, * filt, * zlib; + signed char* line_buffer; + int j, zlen; + + if (stride_bytes == 0) + stride_bytes = x * n; + + if (force_filter >= 5) { + force_filter = -1; + } + + filt = (unsigned char*)STBIW_MALLOC((x * n + 1) * y); if (!filt) return 0; + line_buffer = (signed char*)STBIW_MALLOC(x * n); if (!line_buffer) { STBIW_FREE(filt); return 0; } + for (j = 0; j < y; ++j) { + int filter_type; + if (force_filter > -1) { + filter_type = force_filter; + stbiw__encode_png_line((unsigned char*)(pixels), stride_bytes, x, y, j, n, force_filter, line_buffer); + } + else { // Estimate the best filter by running through all of them: + int best_filter = 0, best_filter_val = 0x7fffffff, est, i; + for (filter_type = 0; filter_type < 5; filter_type++) { + stbiw__encode_png_line((unsigned char*)(pixels), stride_bytes, x, y, j, n, filter_type, line_buffer); + + // Estimate the entropy of the line using this filter; the less, the better. + est = 0; + for (i = 0; i < x * n; ++i) { + est += abs((signed char)line_buffer[i]); + } + if (est < best_filter_val) { + best_filter_val = est; + best_filter = filter_type; + } + } + if (filter_type != best_filter) { // If the last iteration already got us the best filter, don't redo it + stbiw__encode_png_line((unsigned char*)(pixels), stride_bytes, x, y, j, n, best_filter, line_buffer); + filter_type = best_filter; + } + } + // when we get here, filter_type contains the filter type, and line_buffer contains the data + filt[j * (x * n + 1)] = (unsigned char)filter_type; + STBIW_MEMMOVE(filt + j * (x * n + 1) + 1, line_buffer, x * n); + } + STBIW_FREE(line_buffer); + zlib = stbi_zlib_compress(filt, y * (x * n + 1), &zlen, stbi_write_png_compression_level); + STBIW_FREE(filt); + if (!zlib) return 0; + + // each tag requires 12 bytes of overhead + out = (unsigned char*)STBIW_MALLOC(8 + 12 + 13 + 12 + zlen + 12); + if (!out) return 0; + *out_len = 8 + 12 + 13 + 12 + zlen + 12; + + o = out; + STBIW_MEMMOVE(o, sig, 8); o += 8; + stbiw__wp32(o, 13); // header length + stbiw__wptag(o, "IHDR"); + stbiw__wp32(o, x); + stbiw__wp32(o, y); + *o++ = 8; + *o++ = STBIW_UCHAR(ctype[n]); + *o++ = 0; + *o++ = 0; + *o++ = 0; + stbiw__wpcrc(&o, 13); + + stbiw__wp32(o, zlen); + stbiw__wptag(o, "IDAT"); + STBIW_MEMMOVE(o, zlib, zlen); + o += zlen; + STBIW_FREE(zlib); + stbiw__wpcrc(&o, zlen); + + stbiw__wp32(o, 0); + stbiw__wptag(o, "IEND"); + stbiw__wpcrc(&o, 0); + + STBIW_ASSERT(o == out + *out_len); + + return out; +} + +#ifndef STBI_WRITE_NO_STDIO +STBIWDEF int stbi_write_png(char const* filename, int x, int y, int comp, const void* data, int stride_bytes) +{ + FILE* f; + int len; + unsigned char* png = stbi_write_png_to_mem((const unsigned char*)data, stride_bytes, x, y, comp, &len); + if (png == NULL) return 0; + + f = stbiw__fopen(filename, "wb"); + if (!f) { STBIW_FREE(png); return 0; } + fwrite(png, 1, len, f); + fclose(f); + STBIW_FREE(png); + return 1; +} +#endif + +STBIWDEF int stbi_write_png_to_func(stbi_write_func* func, void* context, int x, int y, int comp, const void* data, int stride_bytes) +{ + int len; + unsigned char* png = stbi_write_png_to_mem((const unsigned char*)data, stride_bytes, x, y, comp, &len); + if (png == NULL) return 0; + func(context, png, len); + STBIW_FREE(png); + return 1; +} + + +/* *************************************************************************** + * + * JPEG writer + * + * This is based on Jon Olick's jo_jpeg.cpp: + * public domain Simple, Minimalistic JPEG writer - http://www.jonolick.com/code.html + */ + +static const unsigned char stbiw__jpg_ZigZag[] = { 0,1,5,6,14,15,27,28,2,4,7,13,16,26,29,42,3,8,12,17,25,30,41,43,9,11,18, + 24,31,40,44,53,10,19,23,32,39,45,52,54,20,22,33,38,46,51,55,60,21,34,37,47,50,56,59,61,35,36,48,49,57,58,62,63 }; + +static void stbiw__jpg_writeBits(stbi__write_context* s, int* bitBufP, int* bitCntP, const unsigned short* bs) { + int bitBuf = *bitBufP, bitCnt = *bitCntP; + bitCnt += bs[1]; + bitBuf |= bs[0] << (24 - bitCnt); + while (bitCnt >= 8) { + unsigned char c = (bitBuf >> 16) & 255; + stbiw__putc(s, c); + if (c == 255) { + stbiw__putc(s, 0); + } + bitBuf <<= 8; + bitCnt -= 8; + } + *bitBufP = bitBuf; + *bitCntP = bitCnt; +} + +static void stbiw__jpg_DCT(float* d0p, float* d1p, float* d2p, float* d3p, float* d4p, float* d5p, float* d6p, float* d7p) { + float d0 = *d0p, d1 = *d1p, d2 = *d2p, d3 = *d3p, d4 = *d4p, d5 = *d5p, d6 = *d6p, d7 = *d7p; + float z1, z2, z3, z4, z5, z11, z13; + + float tmp0 = d0 + d7; + float tmp7 = d0 - d7; + float tmp1 = d1 + d6; + float tmp6 = d1 - d6; + float tmp2 = d2 + d5; + float tmp5 = d2 - d5; + float tmp3 = d3 + d4; + float tmp4 = d3 - d4; + + // Even part + float tmp10 = tmp0 + tmp3; // phase 2 + float tmp13 = tmp0 - tmp3; + float tmp11 = tmp1 + tmp2; + float tmp12 = tmp1 - tmp2; + + d0 = tmp10 + tmp11; // phase 3 + d4 = tmp10 - tmp11; + + z1 = (tmp12 + tmp13) * 0.707106781f; // c4 + d2 = tmp13 + z1; // phase 5 + d6 = tmp13 - z1; + + // Odd part + tmp10 = tmp4 + tmp5; // phase 2 + tmp11 = tmp5 + tmp6; + tmp12 = tmp6 + tmp7; + + // The rotator is modified from fig 4-8 to avoid extra negations. + z5 = (tmp10 - tmp12) * 0.382683433f; // c6 + z2 = tmp10 * 0.541196100f + z5; // c2-c6 + z4 = tmp12 * 1.306562965f + z5; // c2+c6 + z3 = tmp11 * 0.707106781f; // c4 + + z11 = tmp7 + z3; // phase 5 + z13 = tmp7 - z3; + + *d5p = z13 + z2; // phase 6 + *d3p = z13 - z2; + *d1p = z11 + z4; + *d7p = z11 - z4; + + *d0p = d0; *d2p = d2; *d4p = d4; *d6p = d6; +} + +static void stbiw__jpg_calcBits(int val, unsigned short bits[2]) { + int tmp1 = val < 0 ? -val : val; + val = val < 0 ? val - 1 : val; + bits[1] = 1; + while (tmp1 >>= 1) { + ++bits[1]; + } + bits[0] = val & ((1 << bits[1]) - 1); +} + +static int stbiw__jpg_processDU(stbi__write_context* s, int* bitBuf, int* bitCnt, float* CDU, int du_stride, float* fdtbl, int DC, const unsigned short HTDC[256][2], const unsigned short HTAC[256][2]) { + const unsigned short EOB[2] = { HTAC[0x00][0], HTAC[0x00][1] }; + const unsigned short M16zeroes[2] = { HTAC[0xF0][0], HTAC[0xF0][1] }; + int dataOff, i, j, n, diff, end0pos, x, y; + int DU[64]; + + // DCT rows + for (dataOff = 0, n = du_stride * 8; dataOff < n; dataOff += du_stride) { + stbiw__jpg_DCT(&CDU[dataOff], &CDU[dataOff + 1], &CDU[dataOff + 2], &CDU[dataOff + 3], &CDU[dataOff + 4], &CDU[dataOff + 5], &CDU[dataOff + 6], &CDU[dataOff + 7]); + } + // DCT columns + for (dataOff = 0; dataOff < 8; ++dataOff) { + stbiw__jpg_DCT(&CDU[dataOff], &CDU[dataOff + du_stride], &CDU[dataOff + du_stride * 2], &CDU[dataOff + du_stride * 3], &CDU[dataOff + du_stride * 4], + &CDU[dataOff + du_stride * 5], &CDU[dataOff + du_stride * 6], &CDU[dataOff + du_stride * 7]); + } + // Quantize/descale/zigzag the coefficients + for (y = 0, j = 0; y < 8; ++y) { + for (x = 0; x < 8; ++x, ++j) { + float v; + i = y * du_stride + x; + v = CDU[i] * fdtbl[j]; + // DU[stbiw__jpg_ZigZag[j]] = (int)(v < 0 ? ceilf(v - 0.5f) : floorf(v + 0.5f)); + // ceilf() and floorf() are C99, not C89, but I /think/ they're not needed here anyway? + DU[stbiw__jpg_ZigZag[j]] = (int)(v < 0 ? v - 0.5f : v + 0.5f); + } + } + + // Encode DC + diff = DU[0] - DC; + if (diff == 0) { + stbiw__jpg_writeBits(s, bitBuf, bitCnt, HTDC[0]); + } + else { + unsigned short bits[2]; + stbiw__jpg_calcBits(diff, bits); + stbiw__jpg_writeBits(s, bitBuf, bitCnt, HTDC[bits[1]]); + stbiw__jpg_writeBits(s, bitBuf, bitCnt, bits); + } + // Encode ACs + end0pos = 63; + for (; (end0pos > 0) && (DU[end0pos] == 0); --end0pos) { + } + // end0pos = first element in reverse order !=0 + if (end0pos == 0) { + stbiw__jpg_writeBits(s, bitBuf, bitCnt, EOB); + return DU[0]; + } + for (i = 1; i <= end0pos; ++i) { + int startpos = i; + int nrzeroes; + unsigned short bits[2]; + for (; DU[i] == 0 && i <= end0pos; ++i) { + } + nrzeroes = i - startpos; + if (nrzeroes >= 16) { + int lng = nrzeroes >> 4; + int nrmarker; + for (nrmarker = 1; nrmarker <= lng; ++nrmarker) + stbiw__jpg_writeBits(s, bitBuf, bitCnt, M16zeroes); + nrzeroes &= 15; + } + stbiw__jpg_calcBits(DU[i], bits); + stbiw__jpg_writeBits(s, bitBuf, bitCnt, HTAC[(nrzeroes << 4) + bits[1]]); + stbiw__jpg_writeBits(s, bitBuf, bitCnt, bits); + } + if (end0pos != 63) { + stbiw__jpg_writeBits(s, bitBuf, bitCnt, EOB); + } + return DU[0]; +} + +static int stbi_write_jpg_core(stbi__write_context* s, int width, int height, int comp, const void* data, int quality) { + // Constants that don't pollute global namespace + static const unsigned char std_dc_luminance_nrcodes[] = { 0,0,1,5,1,1,1,1,1,1,0,0,0,0,0,0,0 }; + static const unsigned char std_dc_luminance_values[] = { 0,1,2,3,4,5,6,7,8,9,10,11 }; + static const unsigned char std_ac_luminance_nrcodes[] = { 0,0,2,1,3,3,2,4,3,5,5,4,4,0,0,1,0x7d }; + static const unsigned char std_ac_luminance_values[] = { + 0x01,0x02,0x03,0x00,0x04,0x11,0x05,0x12,0x21,0x31,0x41,0x06,0x13,0x51,0x61,0x07,0x22,0x71,0x14,0x32,0x81,0x91,0xa1,0x08, + 0x23,0x42,0xb1,0xc1,0x15,0x52,0xd1,0xf0,0x24,0x33,0x62,0x72,0x82,0x09,0x0a,0x16,0x17,0x18,0x19,0x1a,0x25,0x26,0x27,0x28, + 0x29,0x2a,0x34,0x35,0x36,0x37,0x38,0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4a,0x53,0x54,0x55,0x56,0x57,0x58,0x59, + 0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x83,0x84,0x85,0x86,0x87,0x88,0x89, + 0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,0xb5,0xb6, + 0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda,0xe1,0xe2, + 0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,0xf9,0xfa + }; + static const unsigned char std_dc_chrominance_nrcodes[] = { 0,0,3,1,1,1,1,1,1,1,1,1,0,0,0,0,0 }; + static const unsigned char std_dc_chrominance_values[] = { 0,1,2,3,4,5,6,7,8,9,10,11 }; + static const unsigned char std_ac_chrominance_nrcodes[] = { 0,0,2,1,2,4,4,3,4,7,5,4,4,0,1,2,0x77 }; + static const unsigned char std_ac_chrominance_values[] = { + 0x00,0x01,0x02,0x03,0x11,0x04,0x05,0x21,0x31,0x06,0x12,0x41,0x51,0x07,0x61,0x71,0x13,0x22,0x32,0x81,0x08,0x14,0x42,0x91, + 0xa1,0xb1,0xc1,0x09,0x23,0x33,0x52,0xf0,0x15,0x62,0x72,0xd1,0x0a,0x16,0x24,0x34,0xe1,0x25,0xf1,0x17,0x18,0x19,0x1a,0x26, + 0x27,0x28,0x29,0x2a,0x35,0x36,0x37,0x38,0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4a,0x53,0x54,0x55,0x56,0x57,0x58, + 0x59,0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0x82,0x83,0x84,0x85,0x86,0x87, + 0x88,0x89,0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4, + 0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda, + 0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,0xf9,0xfa + }; + // Huffman tables + static const unsigned short YDC_HT[256][2] = { {0,2},{2,3},{3,3},{4,3},{5,3},{6,3},{14,4},{30,5},{62,6},{126,7},{254,8},{510,9} }; + static const unsigned short UVDC_HT[256][2] = { {0,2},{1,2},{2,2},{6,3},{14,4},{30,5},{62,6},{126,7},{254,8},{510,9},{1022,10},{2046,11} }; + static const unsigned short YAC_HT[256][2] = { + {10,4},{0,2},{1,2},{4,3},{11,4},{26,5},{120,7},{248,8},{1014,10},{65410,16},{65411,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {12,4},{27,5},{121,7},{502,9},{2038,11},{65412,16},{65413,16},{65414,16},{65415,16},{65416,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {28,5},{249,8},{1015,10},{4084,12},{65417,16},{65418,16},{65419,16},{65420,16},{65421,16},{65422,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {58,6},{503,9},{4085,12},{65423,16},{65424,16},{65425,16},{65426,16},{65427,16},{65428,16},{65429,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {59,6},{1016,10},{65430,16},{65431,16},{65432,16},{65433,16},{65434,16},{65435,16},{65436,16},{65437,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {122,7},{2039,11},{65438,16},{65439,16},{65440,16},{65441,16},{65442,16},{65443,16},{65444,16},{65445,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {123,7},{4086,12},{65446,16},{65447,16},{65448,16},{65449,16},{65450,16},{65451,16},{65452,16},{65453,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {250,8},{4087,12},{65454,16},{65455,16},{65456,16},{65457,16},{65458,16},{65459,16},{65460,16},{65461,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {504,9},{32704,15},{65462,16},{65463,16},{65464,16},{65465,16},{65466,16},{65467,16},{65468,16},{65469,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {505,9},{65470,16},{65471,16},{65472,16},{65473,16},{65474,16},{65475,16},{65476,16},{65477,16},{65478,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {506,9},{65479,16},{65480,16},{65481,16},{65482,16},{65483,16},{65484,16},{65485,16},{65486,16},{65487,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {1017,10},{65488,16},{65489,16},{65490,16},{65491,16},{65492,16},{65493,16},{65494,16},{65495,16},{65496,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {1018,10},{65497,16},{65498,16},{65499,16},{65500,16},{65501,16},{65502,16},{65503,16},{65504,16},{65505,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {2040,11},{65506,16},{65507,16},{65508,16},{65509,16},{65510,16},{65511,16},{65512,16},{65513,16},{65514,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {65515,16},{65516,16},{65517,16},{65518,16},{65519,16},{65520,16},{65521,16},{65522,16},{65523,16},{65524,16},{0,0},{0,0},{0,0},{0,0},{0,0}, + {2041,11},{65525,16},{65526,16},{65527,16},{65528,16},{65529,16},{65530,16},{65531,16},{65532,16},{65533,16},{65534,16},{0,0},{0,0},{0,0},{0,0},{0,0} + }; + static const unsigned short UVAC_HT[256][2] = { + {0,2},{1,2},{4,3},{10,4},{24,5},{25,5},{56,6},{120,7},{500,9},{1014,10},{4084,12},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {11,4},{57,6},{246,8},{501,9},{2038,11},{4085,12},{65416,16},{65417,16},{65418,16},{65419,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {26,5},{247,8},{1015,10},{4086,12},{32706,15},{65420,16},{65421,16},{65422,16},{65423,16},{65424,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {27,5},{248,8},{1016,10},{4087,12},{65425,16},{65426,16},{65427,16},{65428,16},{65429,16},{65430,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {58,6},{502,9},{65431,16},{65432,16},{65433,16},{65434,16},{65435,16},{65436,16},{65437,16},{65438,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {59,6},{1017,10},{65439,16},{65440,16},{65441,16},{65442,16},{65443,16},{65444,16},{65445,16},{65446,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {121,7},{2039,11},{65447,16},{65448,16},{65449,16},{65450,16},{65451,16},{65452,16},{65453,16},{65454,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {122,7},{2040,11},{65455,16},{65456,16},{65457,16},{65458,16},{65459,16},{65460,16},{65461,16},{65462,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {249,8},{65463,16},{65464,16},{65465,16},{65466,16},{65467,16},{65468,16},{65469,16},{65470,16},{65471,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {503,9},{65472,16},{65473,16},{65474,16},{65475,16},{65476,16},{65477,16},{65478,16},{65479,16},{65480,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {504,9},{65481,16},{65482,16},{65483,16},{65484,16},{65485,16},{65486,16},{65487,16},{65488,16},{65489,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {505,9},{65490,16},{65491,16},{65492,16},{65493,16},{65494,16},{65495,16},{65496,16},{65497,16},{65498,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {506,9},{65499,16},{65500,16},{65501,16},{65502,16},{65503,16},{65504,16},{65505,16},{65506,16},{65507,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {2041,11},{65508,16},{65509,16},{65510,16},{65511,16},{65512,16},{65513,16},{65514,16},{65515,16},{65516,16},{0,0},{0,0},{0,0},{0,0},{0,0},{0,0}, + {16352,14},{65517,16},{65518,16},{65519,16},{65520,16},{65521,16},{65522,16},{65523,16},{65524,16},{65525,16},{0,0},{0,0},{0,0},{0,0},{0,0}, + {1018,10},{32707,15},{65526,16},{65527,16},{65528,16},{65529,16},{65530,16},{65531,16},{65532,16},{65533,16},{65534,16},{0,0},{0,0},{0,0},{0,0},{0,0} + }; + static const int YQT[] = { 16,11,10,16,24,40,51,61,12,12,14,19,26,58,60,55,14,13,16,24,40,57,69,56,14,17,22,29,51,87,80,62,18,22, + 37,56,68,109,103,77,24,35,55,64,81,104,113,92,49,64,78,87,103,121,120,101,72,92,95,98,112,100,103,99 }; + static const int UVQT[] = { 17,18,24,47,99,99,99,99,18,21,26,66,99,99,99,99,24,26,56,99,99,99,99,99,47,66,99,99,99,99,99,99, + 99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99 }; + static const float aasf[] = { 1.0f * 2.828427125f, 1.387039845f * 2.828427125f, 1.306562965f * 2.828427125f, 1.175875602f * 2.828427125f, + 1.0f * 2.828427125f, 0.785694958f * 2.828427125f, 0.541196100f * 2.828427125f, 0.275899379f * 2.828427125f }; + + int row, col, i, k, subsample; + float fdtbl_Y[64], fdtbl_UV[64]; + unsigned char YTable[64], UVTable[64]; + + if (!data || !width || !height || comp > 4 || comp < 1) { + return 0; + } + + quality = quality ? quality : 90; + subsample = quality <= 90 ? 1 : 0; + quality = quality < 1 ? 1 : quality > 100 ? 100 : quality; + quality = quality < 50 ? 5000 / quality : 200 - quality * 2; + + for (i = 0; i < 64; ++i) { + int uvti, yti = (YQT[i] * quality + 50) / 100; + YTable[stbiw__jpg_ZigZag[i]] = (unsigned char)(yti < 1 ? 1 : yti > 255 ? 255 : yti); + uvti = (UVQT[i] * quality + 50) / 100; + UVTable[stbiw__jpg_ZigZag[i]] = (unsigned char)(uvti < 1 ? 1 : uvti > 255 ? 255 : uvti); + } + + for (row = 0, k = 0; row < 8; ++row) { + for (col = 0; col < 8; ++col, ++k) { + fdtbl_Y[k] = 1 / (YTable[stbiw__jpg_ZigZag[k]] * aasf[row] * aasf[col]); + fdtbl_UV[k] = 1 / (UVTable[stbiw__jpg_ZigZag[k]] * aasf[row] * aasf[col]); + } + } + + // Write Headers + { + static const unsigned char head0[] = { 0xFF,0xD8,0xFF,0xE0,0,0x10,'J','F','I','F',0,1,1,0,0,1,0,1,0,0,0xFF,0xDB,0,0x84,0 }; + static const unsigned char head2[] = { 0xFF,0xDA,0,0xC,3,1,0,2,0x11,3,0x11,0,0x3F,0 }; + const unsigned char head1[] = { 0xFF,0xC0,0,0x11,8,(unsigned char)(height >> 8),STBIW_UCHAR(height),(unsigned char)(width >> 8),STBIW_UCHAR(width), + 3,1,(unsigned char)(subsample ? 0x22 : 0x11),0,2,0x11,1,3,0x11,1,0xFF,0xC4,0x01,0xA2,0 }; + s->func(s->context, (void*)head0, sizeof(head0)); + s->func(s->context, (void*)YTable, sizeof(YTable)); + stbiw__putc(s, 1); + s->func(s->context, UVTable, sizeof(UVTable)); + s->func(s->context, (void*)head1, sizeof(head1)); + s->func(s->context, (void*)(std_dc_luminance_nrcodes + 1), sizeof(std_dc_luminance_nrcodes) - 1); + s->func(s->context, (void*)std_dc_luminance_values, sizeof(std_dc_luminance_values)); + stbiw__putc(s, 0x10); // HTYACinfo + s->func(s->context, (void*)(std_ac_luminance_nrcodes + 1), sizeof(std_ac_luminance_nrcodes) - 1); + s->func(s->context, (void*)std_ac_luminance_values, sizeof(std_ac_luminance_values)); + stbiw__putc(s, 1); // HTUDCinfo + s->func(s->context, (void*)(std_dc_chrominance_nrcodes + 1), sizeof(std_dc_chrominance_nrcodes) - 1); + s->func(s->context, (void*)std_dc_chrominance_values, sizeof(std_dc_chrominance_values)); + stbiw__putc(s, 0x11); // HTUACinfo + s->func(s->context, (void*)(std_ac_chrominance_nrcodes + 1), sizeof(std_ac_chrominance_nrcodes) - 1); + s->func(s->context, (void*)std_ac_chrominance_values, sizeof(std_ac_chrominance_values)); + s->func(s->context, (void*)head2, sizeof(head2)); + } + + // Encode 8x8 macroblocks + { + static const unsigned short fillBits[] = { 0x7F, 7 }; + int DCY = 0, DCU = 0, DCV = 0; + int bitBuf = 0, bitCnt = 0; + // comp == 2 is grey+alpha (alpha is ignored) + int ofsG = comp > 2 ? 1 : 0, ofsB = comp > 2 ? 2 : 0; + const unsigned char* dataR = (const unsigned char*)data; + const unsigned char* dataG = dataR + ofsG; + const unsigned char* dataB = dataR + ofsB; + int x, y, pos; + if (subsample) { + for (y = 0; y < height; y += 16) { + for (x = 0; x < width; x += 16) { + float Y[256], U[256], V[256]; + for (row = y, pos = 0; row < y + 16; ++row) { + // row >= height => use last input row + int clamped_row = (row < height) ? row : height - 1; + int base_p = (stbi__flip_vertically_on_write ? (height - 1 - clamped_row) : clamped_row) * width * comp; + for (col = x; col < x + 16; ++col, ++pos) { + // if col >= width => use pixel from last input column + int p = base_p + ((col < width) ? col : (width - 1)) * comp; + float r = dataR[p], g = dataG[p], b = dataB[p]; + Y[pos] = +0.29900f * r + 0.58700f * g + 0.11400f * b - 128; + U[pos] = -0.16874f * r - 0.33126f * g + 0.50000f * b; + V[pos] = +0.50000f * r - 0.41869f * g - 0.08131f * b; + } + } + DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y + 0, 16, fdtbl_Y, DCY, YDC_HT, YAC_HT); + DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y + 8, 16, fdtbl_Y, DCY, YDC_HT, YAC_HT); + DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y + 128, 16, fdtbl_Y, DCY, YDC_HT, YAC_HT); + DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y + 136, 16, fdtbl_Y, DCY, YDC_HT, YAC_HT); + + // subsample U,V + { + float subU[64], subV[64]; + int yy, xx; + for (yy = 0, pos = 0; yy < 8; ++yy) { + for (xx = 0; xx < 8; ++xx, ++pos) { + int j = yy * 32 + xx * 2; + subU[pos] = (U[j + 0] + U[j + 1] + U[j + 16] + U[j + 17]) * 0.25f; + subV[pos] = (V[j + 0] + V[j + 1] + V[j + 16] + V[j + 17]) * 0.25f; + } + } + DCU = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, subU, 8, fdtbl_UV, DCU, UVDC_HT, UVAC_HT); + DCV = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, subV, 8, fdtbl_UV, DCV, UVDC_HT, UVAC_HT); + } + } + } + } + else { + for (y = 0; y < height; y += 8) { + for (x = 0; x < width; x += 8) { + float Y[64], U[64], V[64]; + for (row = y, pos = 0; row < y + 8; ++row) { + // row >= height => use last input row + int clamped_row = (row < height) ? row : height - 1; + int base_p = (stbi__flip_vertically_on_write ? (height - 1 - clamped_row) : clamped_row) * width * comp; + for (col = x; col < x + 8; ++col, ++pos) { + // if col >= width => use pixel from last input column + int p = base_p + ((col < width) ? col : (width - 1)) * comp; + float r = dataR[p], g = dataG[p], b = dataB[p]; + Y[pos] = +0.29900f * r + 0.58700f * g + 0.11400f * b - 128; + U[pos] = -0.16874f * r - 0.33126f * g + 0.50000f * b; + V[pos] = +0.50000f * r - 0.41869f * g - 0.08131f * b; + } + } + + DCY = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, Y, 8, fdtbl_Y, DCY, YDC_HT, YAC_HT); + DCU = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, U, 8, fdtbl_UV, DCU, UVDC_HT, UVAC_HT); + DCV = stbiw__jpg_processDU(s, &bitBuf, &bitCnt, V, 8, fdtbl_UV, DCV, UVDC_HT, UVAC_HT); + } + } + } + + // Do the bit alignment of the EOI marker + stbiw__jpg_writeBits(s, &bitBuf, &bitCnt, fillBits); + } + + // EOI + stbiw__putc(s, 0xFF); + stbiw__putc(s, 0xD9); + + return 1; +} + +STBIWDEF int stbi_write_jpg_to_func(stbi_write_func* func, void* context, int x, int y, int comp, const void* data, int quality) +{ + stbi__write_context s = { 0 }; + stbi__start_write_callbacks(&s, func, context); + return stbi_write_jpg_core(&s, x, y, comp, (void*)data, quality); +} + + +#ifndef STBI_WRITE_NO_STDIO +STBIWDEF int stbi_write_jpg(char const* filename, int x, int y, int comp, const void* data, int quality) +{ + stbi__write_context s = { 0 }; + if (stbi__start_write_file(&s, filename)) { + int r = stbi_write_jpg_core(&s, x, y, comp, data, quality); + stbi__end_write_file(&s); + return r; + } + else + return 0; +} +#endif + +#endif // STB_IMAGE_WRITE_IMPLEMENTATION + +/* Revision history + 1.14 (2020-02-02) updated JPEG writer to downsample chroma channels + 1.13 + 1.12 + 1.11 (2019-08-11) + + 1.10 (2019-02-07) + support utf8 filenames in Windows; fix warnings and platform ifdefs + 1.09 (2018-02-11) + fix typo in zlib quality API, improve STB_I_W_STATIC in C++ + 1.08 (2018-01-29) + add stbi__flip_vertically_on_write, external zlib, zlib quality, choose PNG filter + 1.07 (2017-07-24) + doc fix + 1.06 (2017-07-23) + writing JPEG (using Jon Olick's code) + 1.05 ??? + 1.04 (2017-03-03) + monochrome BMP expansion + 1.03 ??? + 1.02 (2016-04-02) + avoid allocating large structures on the stack + 1.01 (2016-01-16) + STBIW_REALLOC_SIZED: support allocators with no realloc support + avoid race-condition in crc initialization + minor compile issues + 1.00 (2015-09-14) + installable file IO function + 0.99 (2015-09-13) + warning fixes; TGA rle support + 0.98 (2015-04-08) + added STBIW_MALLOC, STBIW_ASSERT etc + 0.97 (2015-01-18) + fixed HDR asserts, rewrote HDR rle logic + 0.96 (2015-01-17) + add HDR output + fix monochrome BMP + 0.95 (2014-08-17) + add monochrome TGA output + 0.94 (2014-05-31) + rename private functions to avoid conflicts with stb_image.h + 0.93 (2014-05-27) + warning fixes + 0.92 (2010-08-01) + casts to unsigned char to fix warnings + 0.91 (2010-07-17) + first public release + 0.90 first internal release +*/ + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ \ No newline at end of file diff --git a/thirdparty/toml/toml.c b/thirdparty/toml/toml.c new file mode 100644 index 0000000..99c2acd --- /dev/null +++ b/thirdparty/toml/toml.c @@ -0,0 +1,2314 @@ +/* + + MIT License + + Copyright (c) 2017 - 2021 CK Tan + https://github.com/cktan/tomlc99 + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + +*/ +#define _POSIX_C_SOURCE 200809L +#include +#include +#include +#include +#include +#include +#include +#include +#include "toml.h" + + +static void* (*ppmalloc)(size_t) = malloc; +static void (*ppfree)(void*) = free; + +void toml_set_memutil(void* (*xxmalloc)(size_t), + void (*xxfree)(void*)) +{ + if (xxmalloc) ppmalloc = xxmalloc; + if (xxfree) ppfree = xxfree; +} + + +#define MALLOC(a) ppmalloc(a) +#define FREE(a) ppfree(a) + +#define malloc(x) error:do-not-use---use-MALLOC-instead +#define free(x) error:do-not-use---use-FREE-instead + +#define calloc(x,y) error:do-not-use---use-CALLOC-instead +static void* CALLOC(size_t nmemb, size_t sz) +{ + int nb = sz * nmemb; + void* p = MALLOC(nb); + if (p) { + memset(p, 0, nb); + } + return p; +} + + +#define strdup(x) error:do-not-use---use-STRDUP-instead +static char* STRDUP(const char* s) +{ + int len = strlen(s); + char* p = MALLOC(len + 1); + if (p) { + memcpy(p, s, len); + p[len] = 0; + } + return p; +} + +#define strndup(x) error:do-not-use---use-STRNDUP-instead +static char* STRNDUP(const char* s, size_t n) +{ + size_t len = strnlen(s, n); + char* p = MALLOC(len + 1); + if (p) { + memcpy(p, s, len); + p[len] = 0; + } + return p; +} + + + + +/** + * Convert a char in utf8 into UCS, and store it in *ret. + * Return #bytes consumed or -1 on failure. + */ +int toml_utf8_to_ucs(const char* orig, int len, int64_t* ret) +{ + const unsigned char* buf = (const unsigned char*)orig; + unsigned i = *buf++; + int64_t v; + + /* 0x00000000 - 0x0000007F: + 0xxxxxxx + */ + if (0 == (i >> 7)) { + if (len < 1) return -1; + v = i; + return *ret = v, 1; + } + /* 0x00000080 - 0x000007FF: + 110xxxxx 10xxxxxx + */ + if (0x6 == (i >> 5)) { + if (len < 2) return -1; + v = i & 0x1f; + for (int j = 0; j < 1; j++) { + i = *buf++; + if (0x2 != (i >> 6)) return -1; + v = (v << 6) | (i & 0x3f); + } + return *ret = v, (const char*)buf - orig; + } + + /* 0x00000800 - 0x0000FFFF: + 1110xxxx 10xxxxxx 10xxxxxx + */ + if (0xE == (i >> 4)) { + if (len < 3) return -1; + v = i & 0x0F; + for (int j = 0; j < 2; j++) { + i = *buf++; + if (0x2 != (i >> 6)) return -1; + v = (v << 6) | (i & 0x3f); + } + return *ret = v, (const char*)buf - orig; + } + + /* 0x00010000 - 0x001FFFFF: + 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + */ + if (0x1E == (i >> 3)) { + if (len < 4) return -1; + v = i & 0x07; + for (int j = 0; j < 3; j++) { + i = *buf++; + if (0x2 != (i >> 6)) return -1; + v = (v << 6) | (i & 0x3f); + } + return *ret = v, (const char*)buf - orig; + } + + /* 0x00200000 - 0x03FFFFFF: + 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx + */ + if (0x3E == (i >> 2)) { + if (len < 5) return -1; + v = i & 0x03; + for (int j = 0; j < 4; j++) { + i = *buf++; + if (0x2 != (i >> 6)) return -1; + v = (v << 6) | (i & 0x3f); + } + return *ret = v, (const char*)buf - orig; + } + + /* 0x04000000 - 0x7FFFFFFF: + 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx + */ + if (0x7e == (i >> 1)) { + if (len < 6) return -1; + v = i & 0x01; + for (int j = 0; j < 5; j++) { + i = *buf++; + if (0x2 != (i >> 6)) return -1; + v = (v << 6) | (i & 0x3f); + } + return *ret = v, (const char*)buf - orig; + } + return -1; +} + + +/** + * Convert a UCS char to utf8 code, and return it in buf. + * Return #bytes used in buf to encode the char, or + * -1 on error. + */ +int toml_ucs_to_utf8(int64_t code, char buf[6]) +{ + /* http://stackoverflow.com/questions/6240055/manually-converting-unicode-codepoints-into-utf-8-and-utf-16 */ + /* The UCS code values 0xd800–0xdfff (UTF-16 surrogates) as well + * as 0xfffe and 0xffff (UCS noncharacters) should not appear in + * conforming UTF-8 streams. + */ + if (0xd800 <= code && code <= 0xdfff) return -1; + if (0xfffe <= code && code <= 0xffff) return -1; + + /* 0x00000000 - 0x0000007F: + 0xxxxxxx + */ + if (code < 0) return -1; + if (code <= 0x7F) { + buf[0] = (unsigned char)code; + return 1; + } + + /* 0x00000080 - 0x000007FF: + 110xxxxx 10xxxxxx + */ + if (code <= 0x000007FF) { + buf[0] = (char)(0xc0 | (code >> 6)); + buf[1] = (char)(0x80 | (code & 0x3f)); + return 2; + } + + /* 0x00000800 - 0x0000FFFF: + 1110xxxx 10xxxxxx 10xxxxxx + */ + if (code <= 0x0000FFFF) { + buf[0] = (char)(0xe0 | (code >> 12)); + buf[1] = (char)(0x80 | ((code >> 6) & 0x3f)); + buf[2] = (char)(0x80 | (code & 0x3f)); + return 3; + } + + /* 0x00010000 - 0x001FFFFF: + 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + */ + if (code <= 0x001FFFFF) { + buf[0] = (char)(0xf0 | (code >> 18)); + buf[1] = (char)(0x80 | ((code >> 12) & 0x3f)); + buf[2] = (char)(0x80 | ((code >> 6) & 0x3f)); + buf[3] = (char)(0x80 | (code & 0x3f)); + return 4; + } + + /* 0x00200000 - 0x03FFFFFF: + 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx + */ + if (code <= 0x03FFFFFF) { + buf[0] = (char)(0xf8 | (code >> 24)); + buf[1] = (char)(0x80 | ((code >> 18) & 0x3f)); + buf[2] = (char)(0x80 | ((code >> 12) & 0x3f)); + buf[3] = (char)(0x80 | ((code >> 6) & 0x3f)); + buf[4] = (char)(0x80 | (code & 0x3f)); + return 5; + } + + /* 0x04000000 - 0x7FFFFFFF: + 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx + */ + if (code <= 0x7FFFFFFF) { + buf[0] = (char)(0xfc | (code >> 30)); + buf[1] = (char)(0x80 | ((code >> 24) & 0x3f)); + buf[2] = (char)(0x80 | ((code >> 18) & 0x3f)); + buf[3] = (char)(0x80 | ((code >> 12) & 0x3f)); + buf[4] = (char)(0x80 | ((code >> 6) & 0x3f)); + buf[5] = (char)(0x80 | (code & 0x3f)); + return 6; + } + + return -1; +} + +/* + * TOML has 3 data structures: value, array, table. + * Each of them can have identification key. + */ +typedef struct toml_keyval_t toml_keyval_t; +struct toml_keyval_t { + const char* key; /* key to this value */ + const char* val; /* the raw value */ +}; + +typedef struct toml_arritem_t toml_arritem_t; +struct toml_arritem_t { + int valtype; /* for value kind: 'i'nt, 'd'ouble, 'b'ool, 's'tring, 't'ime, 'D'ate, 'T'imestamp */ + char* val; + toml_array_t* arr; + toml_table_t* tab; +}; + + +struct toml_array_t { + const char* key; /* key to this array */ + int kind; /* element kind: 'v'alue, 'a'rray, or 't'able, 'm'ixed */ + int type; /* for value kind: 'i'nt, 'd'ouble, 'b'ool, 's'tring, 't'ime, 'D'ate, 'T'imestamp, 'm'ixed */ + + int nitem; /* number of elements */ + toml_arritem_t* item; +}; + + +struct toml_table_t { + const char* key; /* key to this table */ + bool implicit; /* table was created implicitly */ + bool readonly; /* no more modification allowed */ + + /* key-values in the table */ + int nkval; + toml_keyval_t** kval; + + /* arrays in the table */ + int narr; + toml_array_t** arr; + + /* tables in the table */ + int ntab; + toml_table_t** tab; +}; + + +static inline void xfree(const void* x) { if (x) FREE((void*)(intptr_t)x); } + + +enum tokentype_t { + INVALID, + DOT, + COMMA, + EQUAL, + LBRACE, + RBRACE, + NEWLINE, + LBRACKET, + RBRACKET, + STRING, +}; +typedef enum tokentype_t tokentype_t; + +typedef struct token_t token_t; +struct token_t { + tokentype_t tok; + int lineno; + char* ptr; /* points into context->start */ + int len; + int eof; +}; + + +typedef struct context_t context_t; +struct context_t { + char* start; + char* stop; + char* errbuf; + int errbufsz; + + token_t tok; + toml_table_t* root; + toml_table_t* curtab; + + struct { + int top; + char* key[10]; + token_t tok[10]; + } tpath; + +}; + +#define STRINGIFY(x) #x +#define TOSTRING(x) STRINGIFY(x) +#define FLINE __FILE__ ":" TOSTRING(__LINE__) + +static int next_token(context_t* ctx, int dotisspecial); + +/* + Error reporting. Call when an error is detected. Always return -1. +*/ +static int e_outofmemory(context_t* ctx, const char* fline) +{ + snprintf(ctx->errbuf, ctx->errbufsz, "ERROR: out of memory (%s)", fline); + return -1; +} + + +static int e_internal(context_t* ctx, const char* fline) +{ + snprintf(ctx->errbuf, ctx->errbufsz, "internal error (%s)", fline); + return -1; +} + +static int e_syntax(context_t* ctx, int lineno, const char* msg) +{ + snprintf(ctx->errbuf, ctx->errbufsz, "line %d: %s", lineno, msg); + return -1; +} + +static int e_badkey(context_t* ctx, int lineno) +{ + snprintf(ctx->errbuf, ctx->errbufsz, "line %d: bad key", lineno); + return -1; +} + +static int e_keyexists(context_t* ctx, int lineno) +{ + snprintf(ctx->errbuf, ctx->errbufsz, "line %d: key exists", lineno); + return -1; +} + +static int e_forbid(context_t* ctx, int lineno, const char* msg) +{ + snprintf(ctx->errbuf, ctx->errbufsz, "line %d: %s", lineno, msg); + return -1; +} + +static void* expand(void* p, int sz, int newsz) +{ + void* s = MALLOC(newsz); + if (!s) return 0; + + memcpy(s, p, sz); + FREE(p); + return s; +} + +static void** expand_ptrarr(void** p, int n) +{ + void** s = MALLOC((n + 1) * sizeof(void*)); + if (!s) return 0; + + s[n] = 0; + memcpy(s, p, n * sizeof(void*)); + FREE(p); + return s; +} + +static toml_arritem_t* expand_arritem(toml_arritem_t* p, int n) +{ + toml_arritem_t* pp = expand(p, n * sizeof(*p), (n + 1) * sizeof(*p)); + if (!pp) return 0; + + memset(&pp[n], 0, sizeof(pp[n])); + return pp; +} + + +static char* norm_lit_str(const char* src, int srclen, + int multiline, + char* errbuf, int errbufsz) +{ + char* dst = 0; /* will write to dst[] and return it */ + int max = 0; /* max size of dst[] */ + int off = 0; /* cur offset in dst[] */ + const char* sp = src; + const char* sq = src + srclen; + int ch; + + /* scan forward on src */ + for (;;) { + if (off >= max - 10) { /* have some slack for misc stuff */ + int newmax = max + 50; + char* x = expand(dst, max, newmax); + if (!x) { + xfree(dst); + snprintf(errbuf, errbufsz, "out of memory"); + return 0; + } + dst = x; + max = newmax; + } + + /* finished? */ + if (sp >= sq) break; + + ch = *sp++; + /* control characters other than tab is not allowed */ + if ((0 <= ch && ch <= 0x08) + || (0x0a <= ch && ch <= 0x1f) + || (ch == 0x7f)) { + if (!(multiline && (ch == '\r' || ch == '\n'))) { + xfree(dst); + snprintf(errbuf, errbufsz, "invalid char U+%04x", ch); + return 0; + } + } + + // a plain copy suffice + dst[off++] = ch; + } + + dst[off++] = 0; + return dst; +} + + + + +/* + * Convert src to raw unescaped utf-8 string. + * Returns NULL if error with errmsg in errbuf. + */ +static char* norm_basic_str(const char* src, int srclen, + int multiline, + char* errbuf, int errbufsz) +{ + char* dst = 0; /* will write to dst[] and return it */ + int max = 0; /* max size of dst[] */ + int off = 0; /* cur offset in dst[] */ + const char* sp = src; + const char* sq = src + srclen; + int ch; + + /* scan forward on src */ + for (;;) { + if (off >= max - 10) { /* have some slack for misc stuff */ + int newmax = max + 50; + char* x = expand(dst, max, newmax); + if (!x) { + xfree(dst); + snprintf(errbuf, errbufsz, "out of memory"); + return 0; + } + dst = x; + max = newmax; + } + + /* finished? */ + if (sp >= sq) break; + + ch = *sp++; + if (ch != '\\') { + /* these chars must be escaped: U+0000 to U+0008, U+000A to U+001F, U+007F */ + if ((0 <= ch && ch <= 0x08) + || (0x0a <= ch && ch <= 0x1f) + || (ch == 0x7f)) { + if (!(multiline && (ch == '\r' || ch == '\n'))) { + xfree(dst); + snprintf(errbuf, errbufsz, "invalid char U+%04x", ch); + return 0; + } + } + + // a plain copy suffice + dst[off++] = ch; + continue; + } + + /* ch was backslash. we expect the escape char. */ + if (sp >= sq) { + snprintf(errbuf, errbufsz, "last backslash is invalid"); + xfree(dst); + return 0; + } + + /* for multi-line, we want to kill line-ending-backslash ... */ + if (multiline) { + + // if there is only whitespace after the backslash ... + if (sp[strspn(sp, " \t\r")] == '\n') { + /* skip all the following whitespaces */ + sp += strspn(sp, " \t\r\n"); + continue; + } + } + + /* get the escaped char */ + ch = *sp++; + switch (ch) { + case 'u': case 'U': + { + int64_t ucs = 0; + int nhex = (ch == 'u' ? 4 : 8); + for (int i = 0; i < nhex; i++) { + if (sp >= sq) { + snprintf(errbuf, errbufsz, "\\%c expects %d hex chars", ch, nhex); + xfree(dst); + return 0; + } + ch = *sp++; + int v = ('0' <= ch && ch <= '9') + ? ch - '0' + : (('A' <= ch && ch <= 'F') ? ch - 'A' + 10 : -1); + if (-1 == v) { + snprintf(errbuf, errbufsz, "invalid hex chars for \\u or \\U"); + xfree(dst); + return 0; + } + ucs = ucs * 16 + v; + } + int n = toml_ucs_to_utf8(ucs, &dst[off]); + if (-1 == n) { + snprintf(errbuf, errbufsz, "illegal ucs code in \\u or \\U"); + xfree(dst); + return 0; + } + off += n; + } + continue; + + case 'b': ch = '\b'; break; + case 't': ch = '\t'; break; + case 'n': ch = '\n'; break; + case 'f': ch = '\f'; break; + case 'r': ch = '\r'; break; + case '"': ch = '"'; break; + case '\\': ch = '\\'; break; + default: + snprintf(errbuf, errbufsz, "illegal escape char \\%c", ch); + xfree(dst); + return 0; + } + + dst[off++] = ch; + } + + // Cap with NUL and return it. + dst[off++] = 0; + return dst; +} + + +/* Normalize a key. Convert all special chars to raw unescaped utf-8 chars. */ +static char* normalize_key(context_t* ctx, token_t strtok) +{ + const char* sp = strtok.ptr; + const char* sq = strtok.ptr + strtok.len; + int lineno = strtok.lineno; + char* ret; + int ch = *sp; + char ebuf[80]; + + /* handle quoted string */ + if (ch == '\'' || ch == '\"') { + /* if ''' or """, take 3 chars off front and back. Else, take 1 char off. */ + int multiline = 0; + if (sp[1] == ch && sp[2] == ch) { + sp += 3, sq -= 3; + multiline = 1; + } + else + sp++, sq--; + + if (ch == '\'') { + /* for single quote, take it verbatim. */ + if (!(ret = STRNDUP(sp, sq - sp))) { + e_outofmemory(ctx, FLINE); + return 0; + } + } + else { + /* for double quote, we need to normalize */ + ret = norm_basic_str(sp, sq - sp, multiline, ebuf, sizeof(ebuf)); + if (!ret) { + e_syntax(ctx, lineno, ebuf); + return 0; + } + } + + /* newlines are not allowed in keys */ + if (strchr(ret, '\n')) { + xfree(ret); + e_badkey(ctx, lineno); + return 0; + } + return ret; + } + + /* for bare-key allow only this regex: [A-Za-z0-9_-]+ */ + const char* xp; + for (xp = sp; xp != sq; xp++) { + int k = *xp; + if (isalnum(k)) continue; + if (k == '_' || k == '-') continue; + e_badkey(ctx, lineno); + return 0; + } + + /* dup and return it */ + if (!(ret = STRNDUP(sp, sq - sp))) { + e_outofmemory(ctx, FLINE); + return 0; + } + return ret; +} + + +/* + * Look up key in tab. Return 0 if not found, or + * 'v'alue, 'a'rray or 't'able depending on the element. + */ +static int check_key(toml_table_t* tab, const char* key, + toml_keyval_t** ret_val, + toml_array_t** ret_arr, + toml_table_t** ret_tab) +{ + int i; + void* dummy; + + if (!ret_tab) ret_tab = (toml_table_t**)&dummy; + if (!ret_arr) ret_arr = (toml_array_t**)&dummy; + if (!ret_val) ret_val = (toml_keyval_t**)&dummy; + + *ret_tab = 0; *ret_arr = 0; *ret_val = 0; + + for (i = 0; i < tab->nkval; i++) { + if (0 == strcmp(key, tab->kval[i]->key)) { + *ret_val = tab->kval[i]; + return 'v'; + } + } + for (i = 0; i < tab->narr; i++) { + if (0 == strcmp(key, tab->arr[i]->key)) { + *ret_arr = tab->arr[i]; + return 'a'; + } + } + for (i = 0; i < tab->ntab; i++) { + if (0 == strcmp(key, tab->tab[i]->key)) { + *ret_tab = tab->tab[i]; + return 't'; + } + } + return 0; +} + + +static int key_kind(toml_table_t* tab, const char* key) +{ + return check_key(tab, key, 0, 0, 0); +} + +/* Create a keyval in the table. + */ +static toml_keyval_t* create_keyval_in_table(context_t* ctx, toml_table_t* tab, token_t keytok) +{ + /* first, normalize the key to be used for lookup. + * remember to free it if we error out. + */ + char* newkey = normalize_key(ctx, keytok); + if (!newkey) return 0; + + /* if key exists: error out. */ + toml_keyval_t* dest = 0; + if (key_kind(tab, newkey)) { + xfree(newkey); + e_keyexists(ctx, keytok.lineno); + return 0; + } + + /* make a new entry */ + int n = tab->nkval; + toml_keyval_t** base; + if (0 == (base = (toml_keyval_t**)expand_ptrarr((void**)tab->kval, n))) { + xfree(newkey); + e_outofmemory(ctx, FLINE); + return 0; + } + tab->kval = base; + + if (0 == (base[n] = (toml_keyval_t*)CALLOC(1, sizeof(*base[n])))) { + xfree(newkey); + e_outofmemory(ctx, FLINE); + return 0; + } + dest = tab->kval[tab->nkval++]; + + /* save the key in the new value struct */ + dest->key = newkey; + return dest; +} + + +/* Create a table in the table. + */ +static toml_table_t* create_keytable_in_table(context_t* ctx, toml_table_t* tab, token_t keytok) +{ + /* first, normalize the key to be used for lookup. + * remember to free it if we error out. + */ + char* newkey = normalize_key(ctx, keytok); + if (!newkey) return 0; + + /* if key exists: error out */ + toml_table_t* dest = 0; + if (check_key(tab, newkey, 0, 0, &dest)) { + xfree(newkey); /* don't need this anymore */ + + /* special case: if table exists, but was created implicitly ... */ + if (dest && dest->implicit) { + /* we make it explicit now, and simply return it. */ + dest->implicit = false; + return dest; + } + e_keyexists(ctx, keytok.lineno); + return 0; + } + + /* create a new table entry */ + int n = tab->ntab; + toml_table_t** base; + if (0 == (base = (toml_table_t**)expand_ptrarr((void**)tab->tab, n))) { + xfree(newkey); + e_outofmemory(ctx, FLINE); + return 0; + } + tab->tab = base; + + if (0 == (base[n] = (toml_table_t*)CALLOC(1, sizeof(*base[n])))) { + xfree(newkey); + e_outofmemory(ctx, FLINE); + return 0; + } + dest = tab->tab[tab->ntab++]; + + /* save the key in the new table struct */ + dest->key = newkey; + return dest; +} + + +/* Create an array in the table. + */ +static toml_array_t* create_keyarray_in_table(context_t* ctx, + toml_table_t* tab, + token_t keytok, + char kind) +{ + /* first, normalize the key to be used for lookup. + * remember to free it if we error out. + */ + char* newkey = normalize_key(ctx, keytok); + if (!newkey) return 0; + + /* if key exists: error out */ + if (key_kind(tab, newkey)) { + xfree(newkey); /* don't need this anymore */ + e_keyexists(ctx, keytok.lineno); + return 0; + } + + /* make a new array entry */ + int n = tab->narr; + toml_array_t** base; + if (0 == (base = (toml_array_t**)expand_ptrarr((void**)tab->arr, n))) { + xfree(newkey); + e_outofmemory(ctx, FLINE); + return 0; + } + tab->arr = base; + + if (0 == (base[n] = (toml_array_t*)CALLOC(1, sizeof(*base[n])))) { + xfree(newkey); + e_outofmemory(ctx, FLINE); + return 0; + } + toml_array_t* dest = tab->arr[tab->narr++]; + + /* save the key in the new array struct */ + dest->key = newkey; + dest->kind = kind; + return dest; +} + + +static toml_arritem_t* create_value_in_array(context_t* ctx, + toml_array_t* parent) +{ + const int n = parent->nitem; + toml_arritem_t* base = expand_arritem(parent->item, n); + if (!base) { + e_outofmemory(ctx, FLINE); + return 0; + } + parent->item = base; + parent->nitem++; + return &parent->item[n]; +} + +/* Create an array in an array + */ +static toml_array_t* create_array_in_array(context_t* ctx, + toml_array_t* parent) +{ + const int n = parent->nitem; + toml_arritem_t* base = expand_arritem(parent->item, n); + if (!base) { + e_outofmemory(ctx, FLINE); + return 0; + } + toml_array_t* ret = (toml_array_t*)CALLOC(1, sizeof(toml_array_t)); + if (!ret) { + e_outofmemory(ctx, FLINE); + return 0; + } + base[n].arr = ret; + parent->item = base; + parent->nitem++; + return ret; +} + +/* Create a table in an array + */ +static toml_table_t* create_table_in_array(context_t* ctx, + toml_array_t* parent) +{ + int n = parent->nitem; + toml_arritem_t* base = expand_arritem(parent->item, n); + if (!base) { + e_outofmemory(ctx, FLINE); + return 0; + } + toml_table_t* ret = (toml_table_t*)CALLOC(1, sizeof(toml_table_t)); + if (!ret) { + e_outofmemory(ctx, FLINE); + return 0; + } + base[n].tab = ret; + parent->item = base; + parent->nitem++; + return ret; +} + + +static int skip_newlines(context_t* ctx, int isdotspecial) +{ + while (ctx->tok.tok == NEWLINE) { + if (next_token(ctx, isdotspecial)) return -1; + if (ctx->tok.eof) break; + } + return 0; +} + + +static int parse_keyval(context_t* ctx, toml_table_t* tab); + +static inline int eat_token(context_t* ctx, tokentype_t typ, int isdotspecial, const char* fline) +{ + if (ctx->tok.tok != typ) + return e_internal(ctx, fline); + + if (next_token(ctx, isdotspecial)) + return -1; + + return 0; +} + + + +/* We are at '{ ... }'. + * Parse the table. + */ +static int parse_inline_table(context_t* ctx, toml_table_t* tab) +{ + if (eat_token(ctx, LBRACE, 1, FLINE)) + return -1; + + for (;;) { + if (ctx->tok.tok == NEWLINE) + return e_syntax(ctx, ctx->tok.lineno, "newline not allowed in inline table"); + + /* until } */ + if (ctx->tok.tok == RBRACE) + break; + + if (ctx->tok.tok != STRING) + return e_syntax(ctx, ctx->tok.lineno, "expect a string"); + + if (parse_keyval(ctx, tab)) + return -1; + + if (ctx->tok.tok == NEWLINE) + return e_syntax(ctx, ctx->tok.lineno, "newline not allowed in inline table"); + + /* on comma, continue to scan for next keyval */ + if (ctx->tok.tok == COMMA) { + if (eat_token(ctx, COMMA, 1, FLINE)) + return -1; + continue; + } + break; + } + + if (eat_token(ctx, RBRACE, 1, FLINE)) + return -1; + + tab->readonly = 1; + + return 0; +} + +static int valtype(const char* val) +{ + toml_timestamp_t ts; + if (*val == '\'' || *val == '"') return 's'; + if (0 == toml_rtob(val, 0)) return 'b'; + if (0 == toml_rtoi(val, 0)) return 'i'; + if (0 == toml_rtod(val, 0)) return 'd'; + if (0 == toml_rtots(val, &ts)) { + if (ts.year && ts.hour) return 'T'; /* timestamp */ + if (ts.year) return 'D'; /* date */ + return 't'; /* time */ + } + return 'u'; /* unknown */ +} + + +/* We are at '[...]' */ +static int parse_array(context_t* ctx, toml_array_t* arr) +{ + if (eat_token(ctx, LBRACKET, 0, FLINE)) return -1; + + for (;;) { + if (skip_newlines(ctx, 0)) return -1; + + /* until ] */ + if (ctx->tok.tok == RBRACKET) break; + + switch (ctx->tok.tok) { + case STRING: + { + /* set array kind if this will be the first entry */ + if (arr->kind == 0) + arr->kind = 'v'; + else if (arr->kind != 'v') + arr->kind = 'm'; + + char* val = ctx->tok.ptr; + int vlen = ctx->tok.len; + + /* make a new value in array */ + toml_arritem_t* newval = create_value_in_array(ctx, arr); + if (!newval) + return e_outofmemory(ctx, FLINE); + + if (!(newval->val = STRNDUP(val, vlen))) + return e_outofmemory(ctx, FLINE); + + newval->valtype = valtype(newval->val); + + /* set array type if this is the first entry */ + if (arr->nitem == 1) + arr->type = newval->valtype; + else if (arr->type != newval->valtype) + arr->type = 'm'; /* mixed */ + + if (eat_token(ctx, STRING, 0, FLINE)) return -1; + break; + } + + case LBRACKET: + { /* [ [array], [array] ... ] */ + /* set the array kind if this will be the first entry */ + if (arr->kind == 0) + arr->kind = 'a'; + else if (arr->kind != 'a') + arr->kind = 'm'; + + toml_array_t* subarr = create_array_in_array(ctx, arr); + if (!subarr) return -1; + if (parse_array(ctx, subarr)) return -1; + break; + } + + case LBRACE: + { /* [ {table}, {table} ... ] */ + /* set the array kind if this will be the first entry */ + if (arr->kind == 0) + arr->kind = 't'; + else if (arr->kind != 't') + arr->kind = 'm'; + + toml_table_t* subtab = create_table_in_array(ctx, arr); + if (!subtab) return -1; + if (parse_inline_table(ctx, subtab)) return -1; + break; + } + + default: + return e_syntax(ctx, ctx->tok.lineno, "syntax error"); + } + + if (skip_newlines(ctx, 0)) return -1; + + /* on comma, continue to scan for next element */ + if (ctx->tok.tok == COMMA) { + if (eat_token(ctx, COMMA, 0, FLINE)) return -1; + continue; + } + break; + } + + if (eat_token(ctx, RBRACKET, 1, FLINE)) return -1; + return 0; +} + + +/* handle lines like these: + key = "value" + key = [ array ] + key = { table } +*/ +static int parse_keyval(context_t* ctx, toml_table_t* tab) +{ + if (tab->readonly) { + return e_forbid(ctx, ctx->tok.lineno, "cannot insert new entry into existing table"); + } + + token_t key = ctx->tok; + if (eat_token(ctx, STRING, 1, FLINE)) return -1; + + if (ctx->tok.tok == DOT) { + /* handle inline dotted key. + e.g. + physical.color = "orange" + physical.shape = "round" + */ + toml_table_t* subtab = 0; + { + char* subtabstr = normalize_key(ctx, key); + if (!subtabstr) return -1; + + subtab = toml_table_in(tab, subtabstr); + xfree(subtabstr); + } + if (!subtab) { + subtab = create_keytable_in_table(ctx, tab, key); + if (!subtab) return -1; + } + if (next_token(ctx, 1)) return -1; + if (parse_keyval(ctx, subtab)) return -1; + return 0; + } + + if (ctx->tok.tok != EQUAL) { + return e_syntax(ctx, ctx->tok.lineno, "missing ="); + } + + if (next_token(ctx, 0)) return -1; + + switch (ctx->tok.tok) { + case STRING: + { /* key = "value" */ + toml_keyval_t* keyval = create_keyval_in_table(ctx, tab, key); + if (!keyval) return -1; + token_t val = ctx->tok; + + assert(keyval->val == 0); + if (!(keyval->val = STRNDUP(val.ptr, val.len))) + return e_outofmemory(ctx, FLINE); + + if (next_token(ctx, 1)) return -1; + + return 0; + } + + case LBRACKET: + { /* key = [ array ] */ + toml_array_t* arr = create_keyarray_in_table(ctx, tab, key, 0); + if (!arr) return -1; + if (parse_array(ctx, arr)) return -1; + return 0; + } + + case LBRACE: + { /* key = { table } */ + toml_table_t* nxttab = create_keytable_in_table(ctx, tab, key); + if (!nxttab) return -1; + if (parse_inline_table(ctx, nxttab)) return -1; + return 0; + } + + default: + return e_syntax(ctx, ctx->tok.lineno, "syntax error"); + } + return 0; +} + + +typedef struct tabpath_t tabpath_t; +struct tabpath_t { + int cnt; + token_t key[10]; +}; + +/* at [x.y.z] or [[x.y.z]] + * Scan forward and fill tabpath until it enters ] or ]] + * There will be at least one entry on return. + */ +static int fill_tabpath(context_t* ctx) +{ + int lineno = ctx->tok.lineno; + int i; + + /* clear tpath */ + for (i = 0; i < ctx->tpath.top; i++) { + char** p = &ctx->tpath.key[i]; + xfree(*p); + *p = 0; + } + ctx->tpath.top = 0; + + for (;;) { + if (ctx->tpath.top >= 10) + return e_syntax(ctx, lineno, "table path is too deep; max allowed is 10."); + + if (ctx->tok.tok != STRING) + return e_syntax(ctx, lineno, "invalid or missing key"); + + char* key = normalize_key(ctx, ctx->tok); + if (!key) return -1; + ctx->tpath.tok[ctx->tpath.top] = ctx->tok; + ctx->tpath.key[ctx->tpath.top] = key; + ctx->tpath.top++; + + if (next_token(ctx, 1)) return -1; + + if (ctx->tok.tok == RBRACKET) break; + + if (ctx->tok.tok != DOT) + return e_syntax(ctx, lineno, "invalid key"); + + if (next_token(ctx, 1)) return -1; + } + + if (ctx->tpath.top <= 0) + return e_syntax(ctx, lineno, "empty table selector"); + + return 0; +} + + +/* Walk tabpath from the root, and create new tables on the way. + * Sets ctx->curtab to the final table. + */ +static int walk_tabpath(context_t* ctx) +{ + /* start from root */ + toml_table_t* curtab = ctx->root; + + for (int i = 0; i < ctx->tpath.top; i++) { + const char* key = ctx->tpath.key[i]; + + toml_keyval_t* nextval = 0; + toml_array_t* nextarr = 0; + toml_table_t* nexttab = 0; + switch (check_key(curtab, key, &nextval, &nextarr, &nexttab)) { + case 't': + /* found a table. nexttab is where we will go next. */ + break; + + case 'a': + /* found an array. nexttab is the last table in the array. */ + if (nextarr->kind != 't') + return e_internal(ctx, FLINE); + + if (nextarr->nitem == 0) + return e_internal(ctx, FLINE); + + nexttab = nextarr->item[nextarr->nitem - 1].tab; + break; + + case 'v': + return e_keyexists(ctx, ctx->tpath.tok[i].lineno); + + default: + { /* Not found. Let's create an implicit table. */ + int n = curtab->ntab; + toml_table_t** base = (toml_table_t**)expand_ptrarr((void**)curtab->tab, n); + if (0 == base) + return e_outofmemory(ctx, FLINE); + + curtab->tab = base; + + if (0 == (base[n] = (toml_table_t*)CALLOC(1, sizeof(*base[n])))) + return e_outofmemory(ctx, FLINE); + + if (0 == (base[n]->key = STRDUP(key))) + return e_outofmemory(ctx, FLINE); + + nexttab = curtab->tab[curtab->ntab++]; + + /* tabs created by walk_tabpath are considered implicit */ + nexttab->implicit = true; + } + break; + } + + /* switch to next tab */ + curtab = nexttab; + } + + /* save it */ + ctx->curtab = curtab; + + return 0; +} + + +/* handle lines like [x.y.z] or [[x.y.z]] */ +static int parse_select(context_t* ctx) +{ + assert(ctx->tok.tok == LBRACKET); + + /* true if [[ */ + int llb = (ctx->tok.ptr + 1 < ctx->stop && ctx->tok.ptr[1] == '['); + /* need to detect '[[' on our own because next_token() will skip whitespace, + and '[ [' would be taken as '[[', which is wrong. */ + + /* eat [ or [[ */ + if (eat_token(ctx, LBRACKET, 1, FLINE)) return -1; + if (llb) { + assert(ctx->tok.tok == LBRACKET); + if (eat_token(ctx, LBRACKET, 1, FLINE)) return -1; + } + + if (fill_tabpath(ctx)) return -1; + + /* For [x.y.z] or [[x.y.z]], remove z from tpath. + */ + token_t z = ctx->tpath.tok[ctx->tpath.top - 1]; + xfree(ctx->tpath.key[ctx->tpath.top - 1]); + ctx->tpath.top--; + + /* set up ctx->curtab */ + if (walk_tabpath(ctx)) return -1; + + if (!llb) { + /* [x.y.z] -> create z = {} in x.y */ + toml_table_t* curtab = create_keytable_in_table(ctx, ctx->curtab, z); + if (!curtab) return -1; + ctx->curtab = curtab; + } + else { + /* [[x.y.z]] -> create z = [] in x.y */ + toml_array_t* arr = 0; + { + char* zstr = normalize_key(ctx, z); + if (!zstr) return -1; + arr = toml_array_in(ctx->curtab, zstr); + xfree(zstr); + } + if (!arr) { + arr = create_keyarray_in_table(ctx, ctx->curtab, z, 't'); + if (!arr) return -1; + } + if (arr->kind != 't') + return e_syntax(ctx, z.lineno, "array mismatch"); + + /* add to z[] */ + toml_table_t* dest; + { + toml_table_t* t = create_table_in_array(ctx, arr); + if (!t) return -1; + + if (0 == (t->key = STRDUP("__anon__"))) + return e_outofmemory(ctx, FLINE); + + dest = t; + } + + ctx->curtab = dest; + } + + if (ctx->tok.tok != RBRACKET) { + return e_syntax(ctx, ctx->tok.lineno, "expects ]"); + } + if (llb) { + if (!(ctx->tok.ptr + 1 < ctx->stop && ctx->tok.ptr[1] == ']')) { + return e_syntax(ctx, ctx->tok.lineno, "expects ]]"); + } + if (eat_token(ctx, RBRACKET, 1, FLINE)) return -1; + } + + if (eat_token(ctx, RBRACKET, 1, FLINE)) + return -1; + + if (ctx->tok.tok != NEWLINE) + return e_syntax(ctx, ctx->tok.lineno, "extra chars after ] or ]]"); + + return 0; +} + + + + +toml_table_t* toml_parse(char* conf, + char* errbuf, + int errbufsz) +{ + context_t ctx; + + // clear errbuf + if (errbufsz <= 0) errbufsz = 0; + if (errbufsz > 0) errbuf[0] = 0; + + // init context + memset(&ctx, 0, sizeof(ctx)); + ctx.start = conf; + ctx.stop = ctx.start + strlen(conf); + ctx.errbuf = errbuf; + ctx.errbufsz = errbufsz; + + // start with an artificial newline of length 0 + ctx.tok.tok = NEWLINE; + ctx.tok.lineno = 1; + ctx.tok.ptr = conf; + ctx.tok.len = 0; + + // make a root table + if (0 == (ctx.root = CALLOC(1, sizeof(*ctx.root)))) { + e_outofmemory(&ctx, FLINE); + // Do not goto fail, root table not set up yet + return 0; + } + + // set root as default table + ctx.curtab = ctx.root; + + /* Scan forward until EOF */ + for (token_t tok = ctx.tok; !tok.eof; tok = ctx.tok) { + switch (tok.tok) { + + case NEWLINE: + if (next_token(&ctx, 1)) goto fail; + break; + + case STRING: + if (parse_keyval(&ctx, ctx.curtab)) goto fail; + + if (ctx.tok.tok != NEWLINE) { + e_syntax(&ctx, ctx.tok.lineno, "extra chars after value"); + goto fail; + } + + if (eat_token(&ctx, NEWLINE, 1, FLINE)) goto fail; + break; + + case LBRACKET: /* [ x.y.z ] or [[ x.y.z ]] */ + if (parse_select(&ctx)) goto fail; + break; + + default: + e_syntax(&ctx, tok.lineno, "syntax error"); + goto fail; + } + } + + /* success */ + for (int i = 0; i < ctx.tpath.top; i++) xfree(ctx.tpath.key[i]); + return ctx.root; + +fail: + // Something bad has happened. Free resources and return error. + for (int i = 0; i < ctx.tpath.top; i++) xfree(ctx.tpath.key[i]); + toml_free(ctx.root); + return 0; +} + + +toml_table_t* toml_parse_file(FILE* fp, + char* errbuf, + int errbufsz) +{ + int bufsz = 0; + char* buf = 0; + int off = 0; + + /* read from fp into buf */ + while (!feof(fp)) { + + if (off == bufsz) { + int xsz = bufsz + 1000; + char* x = expand(buf, bufsz, xsz); + if (!x) { + snprintf(errbuf, errbufsz, "out of memory"); + xfree(buf); + return 0; + } + buf = x; + bufsz = xsz; + } + + errno = 0; + int n = fread(buf + off, 1, bufsz - off, fp); + if (ferror(fp)) { + if (errno) { + strerror_s(errbuf, errbufsz, errno); + } + else { + snprintf(errbuf, errbufsz, "%s", "Error reading file"); + } + xfree(buf); + return 0; + } + off += n; + } + + /* tag on a NUL to cap the string */ + if (off == bufsz) { + int xsz = bufsz + 1; + char* x = expand(buf, bufsz, xsz); + if (!x) { + snprintf(errbuf, errbufsz, "out of memory"); + xfree(buf); + return 0; + } + buf = x; + bufsz = xsz; + } + buf[off] = 0; + + /* parse it, cleanup and finish */ + toml_table_t* ret = toml_parse(buf, errbuf, errbufsz); + xfree(buf); + return ret; +} + + +static void xfree_kval(toml_keyval_t* p) +{ + if (!p) return; + xfree(p->key); + xfree(p->val); + xfree(p); +} + +static void xfree_tab(toml_table_t* p); + +static void xfree_arr(toml_array_t* p) +{ + if (!p) return; + + xfree(p->key); + const int n = p->nitem; + for (int i = 0; i < n; i++) { + toml_arritem_t* a = &p->item[i]; + if (a->val) + xfree(a->val); + else if (a->arr) + xfree_arr(a->arr); + else if (a->tab) + xfree_tab(a->tab); + } + xfree(p->item); + xfree(p); +} + + +static void xfree_tab(toml_table_t* p) +{ + int i; + + if (!p) return; + + xfree(p->key); + + for (i = 0; i < p->nkval; i++) xfree_kval(p->kval[i]); + xfree(p->kval); + + for (i = 0; i < p->narr; i++) xfree_arr(p->arr[i]); + xfree(p->arr); + + for (i = 0; i < p->ntab; i++) xfree_tab(p->tab[i]); + xfree(p->tab); + + xfree(p); +} + + +void toml_free(toml_table_t* tab) +{ + xfree_tab(tab); +} + + +static void set_token(context_t* ctx, tokentype_t tok, int lineno, char* ptr, int len) +{ + token_t t; + t.tok = tok; + t.lineno = lineno; + t.ptr = ptr; + t.len = len; + t.eof = 0; + ctx->tok = t; +} + +static void set_eof(context_t* ctx, int lineno) +{ + set_token(ctx, NEWLINE, lineno, ctx->stop, 0); + ctx->tok.eof = 1; +} + + +/* Scan p for n digits compositing entirely of [0-9] */ +static int scan_digits(const char* p, int n) +{ + int ret = 0; + for (; n > 0 && isdigit(*p); n--, p++) { + ret = 10 * ret + (*p - '0'); + } + return n ? -1 : ret; +} + +static int scan_date(const char* p, int* YY, int* MM, int* DD) +{ + int year, month, day; + year = scan_digits(p, 4); + month = (year >= 0 && p[4] == '-') ? scan_digits(p + 5, 2) : -1; + day = (month >= 0 && p[7] == '-') ? scan_digits(p + 8, 2) : -1; + if (YY) *YY = year; + if (MM) *MM = month; + if (DD) *DD = day; + return (year >= 0 && month >= 0 && day >= 0) ? 0 : -1; +} + +static int scan_time(const char* p, int* hh, int* mm, int* ss) +{ + int hour, minute, second; + hour = scan_digits(p, 2); + minute = (hour >= 0 && p[2] == ':') ? scan_digits(p + 3, 2) : -1; + second = (minute >= 0 && p[5] == ':') ? scan_digits(p + 6, 2) : -1; + if (hh) *hh = hour; + if (mm) *mm = minute; + if (ss) *ss = second; + return (hour >= 0 && minute >= 0 && second >= 0) ? 0 : -1; +} + + +static int scan_string(context_t* ctx, char* p, int lineno, int dotisspecial) +{ + char* orig = p; + if (0 == strncmp(p, "'''", 3)) { + char* q = p + 3; + + while (1) { + q = strstr(q, "'''"); + if (0 == q) { + return e_syntax(ctx, lineno, "unterminated triple-s-quote"); + } + while (q[3] == '\'') q++; + break; + } + + set_token(ctx, STRING, lineno, orig, q + 3 - orig); + return 0; + } + + if (0 == strncmp(p, "\"\"\"", 3)) { + char* q = p + 3; + + while (1) { + q = strstr(q, "\"\"\""); + if (0 == q) { + return e_syntax(ctx, lineno, "unterminated triple-d-quote"); + } + if (q[-1] == '\\') { + q++; + continue; + } + while (q[3] == '\"') q++; + break; + } + + // the string is [p+3, q-1] + + int hexreq = 0; /* #hex required */ + int escape = 0; + for (p += 3; p < q; p++) { + if (escape) { + escape = 0; + if (strchr("btnfr\"\\", *p)) continue; + if (*p == 'u') { hexreq = 4; continue; } + if (*p == 'U') { hexreq = 8; continue; } + if (p[strspn(p, " \t\r")] == '\n') continue; /* allow for line ending backslash */ + return e_syntax(ctx, lineno, "bad escape char"); + } + if (hexreq) { + hexreq--; + if (strchr("0123456789ABCDEF", *p)) continue; + return e_syntax(ctx, lineno, "expect hex char"); + } + if (*p == '\\') { escape = 1; continue; } + } + if (escape) + return e_syntax(ctx, lineno, "expect an escape char"); + if (hexreq) + return e_syntax(ctx, lineno, "expected more hex char"); + + set_token(ctx, STRING, lineno, orig, q + 3 - orig); + return 0; + } + + if ('\'' == *p) { + for (p++; *p && *p != '\n' && *p != '\''; p++); + if (*p != '\'') { + return e_syntax(ctx, lineno, "unterminated s-quote"); + } + + set_token(ctx, STRING, lineno, orig, p + 1 - orig); + return 0; + } + + if ('\"' == *p) { + int hexreq = 0; /* #hex required */ + int escape = 0; + for (p++; *p; p++) { + if (escape) { + escape = 0; + if (strchr("btnfr\"\\", *p)) continue; + if (*p == 'u') { hexreq = 4; continue; } + if (*p == 'U') { hexreq = 8; continue; } + return e_syntax(ctx, lineno, "bad escape char"); + } + if (hexreq) { + hexreq--; + if (strchr("0123456789ABCDEF", *p)) continue; + return e_syntax(ctx, lineno, "expect hex char"); + } + if (*p == '\\') { escape = 1; continue; } + if (*p == '\'') { + if (p[1] == '\'' && p[2] == '\'') { + return e_syntax(ctx, lineno, "triple-s-quote inside string lit"); + } + continue; + } + if (*p == '\n') break; + if (*p == '"') break; + } + if (*p != '"') { + return e_syntax(ctx, lineno, "unterminated quote"); + } + + set_token(ctx, STRING, lineno, orig, p + 1 - orig); + return 0; + } + + /* check for timestamp without quotes */ + if (0 == scan_date(p, 0, 0, 0) || 0 == scan_time(p, 0, 0, 0)) { + // forward thru the timestamp + for (; strchr("0123456789.:+-T Z", toupper(*p)); p++); + // squeeze out any spaces at end of string + for (; p[-1] == ' '; p--); + // tokenize + set_token(ctx, STRING, lineno, orig, p - orig); + return 0; + } + + /* literals */ + for (; *p && *p != '\n'; p++) { + int ch = *p; + if (ch == '.' && dotisspecial) break; + if ('A' <= ch && ch <= 'Z') continue; + if ('a' <= ch && ch <= 'z') continue; + if (strchr("0123456789+-_.", ch)) continue; + break; + } + + set_token(ctx, STRING, lineno, orig, p - orig); + return 0; +} + + +static int next_token(context_t* ctx, int dotisspecial) +{ + int lineno = ctx->tok.lineno; + char* p = ctx->tok.ptr; + int i; + + /* eat this tok */ + for (i = 0; i < ctx->tok.len; i++) { + if (*p++ == '\n') + lineno++; + } + + /* make next tok */ + while (p < ctx->stop) { + /* skip comment. stop just before the \n. */ + if (*p == '#') { + for (p++; p < ctx->stop && *p != '\n'; p++); + continue; + } + + if (dotisspecial && *p == '.') { + set_token(ctx, DOT, lineno, p, 1); + return 0; + } + + switch (*p) { + case ',': set_token(ctx, COMMA, lineno, p, 1); return 0; + case '=': set_token(ctx, EQUAL, lineno, p, 1); return 0; + case '{': set_token(ctx, LBRACE, lineno, p, 1); return 0; + case '}': set_token(ctx, RBRACE, lineno, p, 1); return 0; + case '[': set_token(ctx, LBRACKET, lineno, p, 1); return 0; + case ']': set_token(ctx, RBRACKET, lineno, p, 1); return 0; + case '\n': set_token(ctx, NEWLINE, lineno, p, 1); return 0; + case '\r': case ' ': case '\t': + /* ignore white spaces */ + p++; + continue; + } + + return scan_string(ctx, p, lineno, dotisspecial); + } + + set_eof(ctx, lineno); + return 0; +} + + +const char* toml_key_in(const toml_table_t* tab, int keyidx) +{ + if (keyidx < tab->nkval) return tab->kval[keyidx]->key; + + keyidx -= tab->nkval; + if (keyidx < tab->narr) return tab->arr[keyidx]->key; + + keyidx -= tab->narr; + if (keyidx < tab->ntab) return tab->tab[keyidx]->key; + + return 0; +} + +toml_raw_t toml_raw_in(const toml_table_t* tab, const char* key) +{ + int i; + for (i = 0; i < tab->nkval; i++) { + if (0 == strcmp(key, tab->kval[i]->key)) + return tab->kval[i]->val; + } + return 0; +} + +toml_array_t* toml_array_in(const toml_table_t* tab, const char* key) +{ + int i; + for (i = 0; i < tab->narr; i++) { + if (0 == strcmp(key, tab->arr[i]->key)) + return tab->arr[i]; + } + return 0; +} + + +toml_table_t* toml_table_in(const toml_table_t* tab, const char* key) +{ + int i; + for (i = 0; i < tab->ntab; i++) { + if (0 == strcmp(key, tab->tab[i]->key)) + return tab->tab[i]; + } + return 0; +} + +toml_raw_t toml_raw_at(const toml_array_t* arr, int idx) +{ + return (0 <= idx && idx < arr->nitem) ? arr->item[idx].val : 0; +} + +char toml_array_kind(const toml_array_t* arr) +{ + return arr->kind; +} + +char toml_array_type(const toml_array_t* arr) +{ + if (arr->kind != 'v') + return 0; + + if (arr->nitem == 0) + return 0; + + return arr->type; +} + + +int toml_array_nelem(const toml_array_t* arr) +{ + return arr->nitem; +} + +const char* toml_array_key(const toml_array_t* arr) +{ + return arr ? arr->key : (const char*)NULL; +} + +int toml_table_nkval(const toml_table_t* tab) +{ + return tab->nkval; +} + +int toml_table_narr(const toml_table_t* tab) +{ + return tab->narr; +} + +int toml_table_ntab(const toml_table_t* tab) +{ + return tab->ntab; +} + +const char* toml_table_key(const toml_table_t* tab) +{ + return tab ? tab->key : (const char*)NULL; +} + +toml_array_t* toml_array_at(const toml_array_t* arr, int idx) +{ + return (0 <= idx && idx < arr->nitem) ? arr->item[idx].arr : 0; +} + +toml_table_t* toml_table_at(const toml_array_t* arr, int idx) +{ + return (0 <= idx && idx < arr->nitem) ? arr->item[idx].tab : 0; +} + + +int toml_rtots(toml_raw_t src_, toml_timestamp_t* ret) +{ + if (!src_) return -1; + + const char* p = src_; + int must_parse_time = 0; + + memset(ret, 0, sizeof(*ret)); + + int* year = &ret->__buffer.year; + int* month = &ret->__buffer.month; + int* day = &ret->__buffer.day; + int* hour = &ret->__buffer.hour; + int* minute = &ret->__buffer.minute; + int* second = &ret->__buffer.second; + int* millisec = &ret->__buffer.millisec; + + /* parse date YYYY-MM-DD */ + if (0 == scan_date(p, year, month, day)) { + ret->year = year; + ret->month = month; + ret->day = day; + + p += 10; + if (*p) { + // parse the T or space separator + if (*p != 'T' && *p != ' ') return -1; + must_parse_time = 1; + p++; + } + } + + /* parse time HH:MM:SS */ + if (0 == scan_time(p, hour, minute, second)) { + ret->hour = hour; + ret->minute = minute; + ret->second = second; + + /* optionally, parse millisec */ + p += 8; + if (*p == '.') { + char* qq; + p++; + errno = 0; + *millisec = strtol(p, &qq, 0); + if (errno) { + return -1; + } + while (*millisec > 999) { + *millisec /= 10; + } + + ret->millisec = millisec; + p = qq; + } + + if (*p) { + /* parse and copy Z */ + char* z = ret->__buffer.z; + ret->z = z; + if (*p == 'Z' || *p == 'z') { + *z++ = 'Z'; p++; + *z = 0; + + } + else if (*p == '+' || *p == '-') { + *z++ = *p++; + + if (!(isdigit(p[0]) && isdigit(p[1]))) return -1; + *z++ = *p++; + *z++ = *p++; + + if (*p == ':') { + *z++ = *p++; + + if (!(isdigit(p[0]) && isdigit(p[1]))) return -1; + *z++ = *p++; + *z++ = *p++; + } + + *z = 0; + } + } + } + if (*p != 0) + return -1; + + if (must_parse_time && !ret->hour) + return -1; + + return 0; +} + + +/* Raw to boolean */ +int toml_rtob(toml_raw_t src, int* ret_) +{ + if (!src) return -1; + int dummy; + int* ret = ret_ ? ret_ : &dummy; + + if (0 == strcmp(src, "true")) { + *ret = 1; + return 0; + } + if (0 == strcmp(src, "false")) { + *ret = 0; + return 0; + } + return -1; +} + + +/* Raw to integer */ +int toml_rtoi(toml_raw_t src, int64_t* ret_) +{ + if (!src) return -1; + + char buf[100]; + char* p = buf; + char* q = p + sizeof(buf); + const char* s = src; + int base = 0; + int64_t dummy; + int64_t* ret = ret_ ? ret_ : &dummy; + + + /* allow +/- */ + if (s[0] == '+' || s[0] == '-') + *p++ = *s++; + + /* disallow +_100 */ + if (s[0] == '_') + return -1; + + /* if 0* ... */ + if ('0' == s[0]) { + switch (s[1]) { + case 'x': base = 16; s += 2; break; + case 'o': base = 8; s += 2; break; + case 'b': base = 2; s += 2; break; + case '\0': return *ret = 0, 0; + default: + /* ensure no other digits after it */ + if (s[1]) return -1; + } + } + + /* just strip underscores and pass to strtoll */ + while (*s && p < q) { + int ch = *s++; + if (ch == '_') { + // disallow '__' + if (s[0] == '_') return -1; + // numbers cannot end with '_' + if (s[0] == '\0') return -1; + continue; /* skip _ */ + } + *p++ = ch; + } + + // if not at end-of-string or we ran out of buffer ... + if (*s || p == q) return -1; + + /* cap with NUL */ + *p = 0; + + /* Run strtoll on buf to get the integer */ + char* endp; + errno = 0; + *ret = strtoll(buf, &endp, base); + return (errno || *endp) ? -1 : 0; +} + + +int toml_rtod_ex(toml_raw_t src, double* ret_, char* buf, int buflen) +{ + if (!src) return -1; + + char* p = buf; + char* q = p + buflen; + const char* s = src; + double dummy; + double* ret = ret_ ? ret_ : &dummy; + + + /* allow +/- */ + if (s[0] == '+' || s[0] == '-') + *p++ = *s++; + + /* disallow +_1.00 */ + if (s[0] == '_') + return -1; + + /* decimal point, if used, must be surrounded by at least one digit on each side */ + { + char* dot = strchr(s, '.'); + if (dot) { + if (dot == s || !isdigit(dot[-1]) || !isdigit(dot[1])) + return -1; + } + } + + /* zero must be followed by . or 'e', or NUL */ + if (s[0] == '0' && s[1] && !strchr("eE.", s[1])) + return -1; + + /* just strip underscores and pass to strtod */ + while (*s && p < q) { + int ch = *s++; + if (ch == '_') { + // disallow '__' + if (s[0] == '_') return -1; + // disallow last char '_' + if (s[0] == 0) return -1; + continue; /* skip _ */ + } + *p++ = ch; + } + if (*s || p == q) return -1; /* reached end of string or buffer is full? */ + + /* cap with NUL */ + *p = 0; + + /* Run strtod on buf to get the value */ + char* endp; + errno = 0; + *ret = strtod(buf, &endp); + return (errno || *endp) ? -1 : 0; +} + +int toml_rtod(toml_raw_t src, double* ret_) +{ + char buf[100]; + return toml_rtod_ex(src, ret_, buf, sizeof(buf)); +} + + + + +int toml_rtos(toml_raw_t src, char** ret) +{ + int multiline = 0; + const char* sp; + const char* sq; + + *ret = 0; + if (!src) return -1; + + int qchar = src[0]; + int srclen = strlen(src); + if (!(qchar == '\'' || qchar == '"')) { + return -1; + } + + // triple quotes? + if (qchar == src[1] && qchar == src[2]) { + multiline = 1; + sp = src + 3; + sq = src + srclen - 3; + /* last 3 chars in src must be qchar */ + if (!(sp <= sq && sq[0] == qchar && sq[1] == qchar && sq[2] == qchar)) + return -1; + + /* skip new line immediate after qchar */ + if (sp[0] == '\n') + sp++; + else if (sp[0] == '\r' && sp[1] == '\n') + sp += 2; + + } + else { + sp = src + 1; + sq = src + srclen - 1; + /* last char in src must be qchar */ + if (!(sp <= sq && *sq == qchar)) + return -1; + } + + if (qchar == '\'') { + *ret = norm_lit_str(sp, sq - sp, + multiline, + 0, 0); + } + else { + *ret = norm_basic_str(sp, sq - sp, + multiline, + 0, 0); + } + + return *ret ? 0 : -1; +} + + +toml_datum_t toml_string_at(const toml_array_t* arr, int idx) +{ + toml_datum_t ret; + memset(&ret, 0, sizeof(ret)); + ret.ok = (0 == toml_rtos(toml_raw_at(arr, idx), &ret.u.s)); + return ret; +} + +toml_datum_t toml_bool_at(const toml_array_t* arr, int idx) +{ + toml_datum_t ret; + memset(&ret, 0, sizeof(ret)); + ret.ok = (0 == toml_rtob(toml_raw_at(arr, idx), &ret.u.b)); + return ret; +} + +toml_datum_t toml_int_at(const toml_array_t* arr, int idx) +{ + toml_datum_t ret; + memset(&ret, 0, sizeof(ret)); + ret.ok = (0 == toml_rtoi(toml_raw_at(arr, idx), &ret.u.i)); + return ret; +} + +toml_datum_t toml_double_at(const toml_array_t* arr, int idx) +{ + toml_datum_t ret; + memset(&ret, 0, sizeof(ret)); + ret.ok = (0 == toml_rtod(toml_raw_at(arr, idx), &ret.u.d)); + return ret; +} + +toml_datum_t toml_timestamp_at(const toml_array_t* arr, int idx) +{ + toml_timestamp_t ts; + toml_datum_t ret; + memset(&ret, 0, sizeof(ret)); + ret.ok = (0 == toml_rtots(toml_raw_at(arr, idx), &ts)); + if (ret.ok) { + ret.ok = !!(ret.u.ts = MALLOC(sizeof(*ret.u.ts))); + if (ret.ok) { + *ret.u.ts = ts; + if (ret.u.ts->year) ret.u.ts->year = &ret.u.ts->__buffer.year; + if (ret.u.ts->month) ret.u.ts->month = &ret.u.ts->__buffer.month; + if (ret.u.ts->day) ret.u.ts->day = &ret.u.ts->__buffer.day; + if (ret.u.ts->hour) ret.u.ts->hour = &ret.u.ts->__buffer.hour; + if (ret.u.ts->minute) ret.u.ts->minute = &ret.u.ts->__buffer.minute; + if (ret.u.ts->second) ret.u.ts->second = &ret.u.ts->__buffer.second; + if (ret.u.ts->millisec) ret.u.ts->millisec = &ret.u.ts->__buffer.millisec; + if (ret.u.ts->z) ret.u.ts->z = ret.u.ts->__buffer.z; + } + } + return ret; +} + +toml_datum_t toml_string_in(const toml_table_t* arr, const char* key) +{ + toml_datum_t ret; + memset(&ret, 0, sizeof(ret)); + toml_raw_t raw = toml_raw_in(arr, key); + if (raw) { + ret.ok = (0 == toml_rtos(raw, &ret.u.s)); + } + return ret; +} + +toml_datum_t toml_bool_in(const toml_table_t* arr, const char* key) +{ + toml_datum_t ret; + memset(&ret, 0, sizeof(ret)); + ret.ok = (0 == toml_rtob(toml_raw_in(arr, key), &ret.u.b)); + return ret; +} + +toml_datum_t toml_int_in(const toml_table_t* arr, const char* key) +{ + toml_datum_t ret; + memset(&ret, 0, sizeof(ret)); + ret.ok = (0 == toml_rtoi(toml_raw_in(arr, key), &ret.u.i)); + return ret; +} + +toml_datum_t toml_double_in(const toml_table_t* arr, const char* key) +{ + toml_datum_t ret; + memset(&ret, 0, sizeof(ret)); + ret.ok = (0 == toml_rtod(toml_raw_in(arr, key), &ret.u.d)); + return ret; +} + +toml_datum_t toml_timestamp_in(const toml_table_t* arr, const char* key) +{ + toml_timestamp_t ts; + toml_datum_t ret; + memset(&ret, 0, sizeof(ret)); + ret.ok = (0 == toml_rtots(toml_raw_in(arr, key), &ts)); + if (ret.ok) { + ret.ok = !!(ret.u.ts = MALLOC(sizeof(*ret.u.ts))); + if (ret.ok) { + *ret.u.ts = ts; + if (ret.u.ts->year) ret.u.ts->year = &ret.u.ts->__buffer.year; + if (ret.u.ts->month) ret.u.ts->month = &ret.u.ts->__buffer.month; + if (ret.u.ts->day) ret.u.ts->day = &ret.u.ts->__buffer.day; + if (ret.u.ts->hour) ret.u.ts->hour = &ret.u.ts->__buffer.hour; + if (ret.u.ts->minute) ret.u.ts->minute = &ret.u.ts->__buffer.minute; + if (ret.u.ts->second) ret.u.ts->second = &ret.u.ts->__buffer.second; + if (ret.u.ts->millisec) ret.u.ts->millisec = &ret.u.ts->__buffer.millisec; + if (ret.u.ts->z) ret.u.ts->z = ret.u.ts->__buffer.z; + } + } + return ret; +} diff --git a/thirdparty/toml/toml.h b/thirdparty/toml/toml.h new file mode 100644 index 0000000..25a3feb --- /dev/null +++ b/thirdparty/toml/toml.h @@ -0,0 +1,175 @@ +/* + MIT License + + Copyright (c) 2017 - 2019 CK Tan + https://github.com/cktan/tomlc99 + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ +#ifndef TOML_H +#define TOML_H + + +#include +#include + + +#ifdef __cplusplus +#define TOML_EXTERN extern "C" +#else +#define TOML_EXTERN extern +#endif + +typedef struct toml_timestamp_t toml_timestamp_t; +typedef struct toml_table_t toml_table_t; +typedef struct toml_array_t toml_array_t; +typedef struct toml_datum_t toml_datum_t; + +/* Parse a file. Return a table on success, or 0 otherwise. + * Caller must toml_free(the-return-value) after use. + */ +TOML_EXTERN toml_table_t* toml_parse_file(FILE* fp, + char* errbuf, + int errbufsz); + +/* Parse a string containing the full config. + * Return a table on success, or 0 otherwise. + * Caller must toml_free(the-return-value) after use. + */ +TOML_EXTERN toml_table_t* toml_parse(char* conf, /* NUL terminated, please. */ + char* errbuf, + int errbufsz); + +/* Free the table returned by toml_parse() or toml_parse_file(). Once + * this function is called, any handles accessed through this tab + * directly or indirectly are no longer valid. + */ +TOML_EXTERN void toml_free(toml_table_t* tab); + + +/* Timestamp types. The year, month, day, hour, minute, second, z + * fields may be NULL if they are not relevant. e.g. In a DATE + * type, the hour, minute, second and z fields will be NULLs. + */ +struct toml_timestamp_t { + struct { /* internal. do not use. */ + int year, month, day; + int hour, minute, second, millisec; + char z[10]; + } __buffer; + int* year, * month, * day; + int* hour, * minute, * second, * millisec; + char* z; +}; + + +/*----------------------------------------------------------------- + * Enhanced access methods + */ +struct toml_datum_t { + int ok; + union { + toml_timestamp_t* ts; /* ts must be freed after use */ + char* s; /* string value. s must be freed after use */ + int b; /* bool value */ + int64_t i; /* int value */ + double d; /* double value */ + } u; +}; + +/* on arrays: */ +/* ... retrieve size of array. */ +TOML_EXTERN int toml_array_nelem(const toml_array_t* arr); +/* ... retrieve values using index. */ +TOML_EXTERN toml_datum_t toml_string_at(const toml_array_t* arr, int idx); +TOML_EXTERN toml_datum_t toml_bool_at(const toml_array_t* arr, int idx); +TOML_EXTERN toml_datum_t toml_int_at(const toml_array_t* arr, int idx); +TOML_EXTERN toml_datum_t toml_double_at(const toml_array_t* arr, int idx); +TOML_EXTERN toml_datum_t toml_timestamp_at(const toml_array_t* arr, int idx); +/* ... retrieve array or table using index. */ +TOML_EXTERN toml_array_t* toml_array_at(const toml_array_t* arr, int idx); +TOML_EXTERN toml_table_t* toml_table_at(const toml_array_t* arr, int idx); + +/* on tables: */ +/* ... retrieve the key in table at keyidx. Return 0 if out of range. */ +TOML_EXTERN const char* toml_key_in(const toml_table_t* tab, int keyidx); +/* ... retrieve values using key. */ +TOML_EXTERN toml_datum_t toml_string_in(const toml_table_t* arr, const char* key); +TOML_EXTERN toml_datum_t toml_bool_in(const toml_table_t* arr, const char* key); +TOML_EXTERN toml_datum_t toml_int_in(const toml_table_t* arr, const char* key); +TOML_EXTERN toml_datum_t toml_double_in(const toml_table_t* arr, const char* key); +TOML_EXTERN toml_datum_t toml_timestamp_in(const toml_table_t* arr, const char* key); +/* .. retrieve array or table using key. */ +TOML_EXTERN toml_array_t* toml_array_in(const toml_table_t* tab, + const char* key); +TOML_EXTERN toml_table_t* toml_table_in(const toml_table_t* tab, + const char* key); + +/*----------------------------------------------------------------- + * lesser used + */ + /* Return the array kind: 't'able, 'a'rray, 'v'alue, 'm'ixed */ +TOML_EXTERN char toml_array_kind(const toml_array_t* arr); + +/* For array kind 'v'alue, return the type of values + i:int, d:double, b:bool, s:string, t:time, D:date, T:timestamp, 'm'ixed + 0 if unknown +*/ +TOML_EXTERN char toml_array_type(const toml_array_t* arr); + +/* Return the key of an array */ +TOML_EXTERN const char* toml_array_key(const toml_array_t* arr); + +/* Return the number of key-values in a table */ +TOML_EXTERN int toml_table_nkval(const toml_table_t* tab); + +/* Return the number of arrays in a table */ +TOML_EXTERN int toml_table_narr(const toml_table_t* tab); + +/* Return the number of sub-tables in a table */ +TOML_EXTERN int toml_table_ntab(const toml_table_t* tab); + +/* Return the key of a table*/ +TOML_EXTERN const char* toml_table_key(const toml_table_t* tab); + +/*-------------------------------------------------------------- + * misc + */ +TOML_EXTERN int toml_utf8_to_ucs(const char* orig, int len, int64_t* ret); +TOML_EXTERN int toml_ucs_to_utf8(int64_t code, char buf[6]); +TOML_EXTERN void toml_set_memutil(void* (*xxmalloc)(size_t), + void (*xxfree)(void*)); + + +/*-------------------------------------------------------------- + * deprecated + */ + /* A raw value, must be processed by toml_rto* before using. */ +typedef const char* toml_raw_t; +TOML_EXTERN toml_raw_t toml_raw_in(const toml_table_t* tab, const char* key); +TOML_EXTERN toml_raw_t toml_raw_at(const toml_array_t* arr, int idx); +TOML_EXTERN int toml_rtos(toml_raw_t s, char** ret); +TOML_EXTERN int toml_rtob(toml_raw_t s, int* ret); +TOML_EXTERN int toml_rtoi(toml_raw_t s, int64_t* ret); +TOML_EXTERN int toml_rtod(toml_raw_t s, double* ret); +TOML_EXTERN int toml_rtod_ex(toml_raw_t s, double* ret, char* buf, int buflen); +TOML_EXTERN int toml_rtots(toml_raw_t s, toml_timestamp_t* ret); + + +#endif /* TOML_H */ diff --git a/toml-LICENSE.txt b/toml-LICENSE.txt new file mode 100644 index 0000000..a3292b1 --- /dev/null +++ b/toml-LICENSE.txt @@ -0,0 +1,22 @@ +MIT License + +Copyright (c) 2017 CK Tan +https://github.com/cktan/tomlc99 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE.